[feature] Move use_blurhash to app settings, enable by default (#5)

Does what it says on the tin! This serves as a decent model (imo) for how to move something from Mastodon's rather opaque server-side settings to client settings.

Reviewed-on: https://codeberg.org/superseriousbusiness/masto-fe-standalone/pulls/5
Co-authored-by: tobi <tobi.smethurst@protonmail.com>
Co-committed-by: tobi <tobi.smethurst@protonmail.com>
This commit is contained in:
tobi
2024-12-27 11:54:18 +00:00
committed by tobi
parent 9f520d3608
commit 4df14235c6
11 changed files with 113 additions and 21 deletions

View File

@@ -11,7 +11,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { Blurhash } from 'flavours/glitch/components/blurhash'; import { Blurhash } from 'flavours/glitch/components/blurhash';
import { autoPlayGif, displayMedia, useBlurhash } from 'flavours/glitch/initial_state'; import { autoPlayGif, displayMedia } from 'flavours/glitch/initial_state';
import { IconButton } from './icon_button'; import { IconButton } from './icon_button';
@@ -51,6 +51,7 @@ class Item extends PureComponent {
displayWidth: PropTypes.number, displayWidth: PropTypes.number,
visible: PropTypes.bool.isRequired, visible: PropTypes.bool.isRequired,
autoplay: PropTypes.bool, autoplay: PropTypes.bool,
useBlurhash: PropTypes.bool,
}; };
static defaultProps = { static defaultProps = {
@@ -105,7 +106,17 @@ class Item extends PureComponent {
}; };
render () { render () {
const { attachment, lang, index, size, standalone, letterbox, displayWidth, visible } = this.props; const {
attachment,
lang,
index,
size,
standalone,
letterbox,
displayWidth,
visible,
useBlurhash,
} = this.props;
let badges = [], thumbnail; let badges = [], thumbnail;
@@ -243,6 +254,7 @@ class MediaGallery extends PureComponent {
visible: PropTypes.bool, visible: PropTypes.bool,
autoplay: PropTypes.bool, autoplay: PropTypes.bool,
onToggleVisibility: PropTypes.func, onToggleVisibility: PropTypes.func,
useBlurhash: PropTypes.bool,
}; };
static defaultProps = { static defaultProps = {
@@ -326,7 +338,7 @@ class MediaGallery extends PureComponent {
} }
render () { render () {
const { media, lang, intl, sensitive, letterbox, fullwidth, defaultWidth, autoplay } = this.props; const { media, lang, intl, sensitive, letterbox, fullwidth, defaultWidth, autoplay, useBlurhash } = this.props;
const { visible } = this.state; const { visible } = this.state;
const size = media.take(4).size; const size = media.take(4).size;
const uncached = media.every(attachment => attachment.get('type') === 'unknown'); const uncached = media.every(attachment => attachment.get('type') === 'unknown');
@@ -346,9 +358,33 @@ class MediaGallery extends PureComponent {
} }
if (this.isStandaloneEligible()) { if (this.isStandaloneEligible()) {
children = <Item standalone autoplay={autoplay} onClick={this.handleClick} attachment={media.get(0)} lang={lang} displayWidth={width} visible={visible} />; children = (
<Item
standalone
autoplay={autoplay}
onClick={this.handleClick}
attachment={media.get(0)}
lang={lang}
displayWidth={width}
visible={visible}
useBlurhash={useBlurhash}
/>
);
} else { } else {
children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} autoplay={autoplay} onClick={this.handleClick} attachment={attachment} index={i} lang={lang} size={size} letterbox={letterbox} displayWidth={width} visible={visible || uncached} />); children = media.take(4).map((attachment, i) => (
<Item
key={attachment.get('id')}
autoplay={autoplay}
onClick={this.handleClick}
attachment={attachment}
index={i}
lang={lang}
size={size}
letterbox={letterbox}
displayWidth={width}
visible={visible || uncached}
useBlurhash={useBlurhash} />
));
} }
if (uncached) { if (uncached) {

View File

@@ -667,6 +667,7 @@ class Status extends ImmutablePureComponent {
blurhash={attachment.get('blurhash')} blurhash={attachment.get('blurhash')}
visible={this.state.showMedia} visible={this.state.showMedia}
onToggleVisibility={this.handleToggleMediaVisibility} onToggleVisibility={this.handleToggleMediaVisibility}
useBlurhash={settings.getIn(['media', 'use_blurhash'])}
/> />
)} )}
</Bundle>, </Bundle>,
@@ -694,6 +695,7 @@ class Status extends ImmutablePureComponent {
deployPictureInPicture={pictureInPicture.get('available') ? this.handleDeployPictureInPicture : undefined} deployPictureInPicture={pictureInPicture.get('available') ? this.handleDeployPictureInPicture : undefined}
visible={this.state.showMedia} visible={this.state.showMedia}
onToggleVisibility={this.handleToggleMediaVisibility} onToggleVisibility={this.handleToggleMediaVisibility}
useBlurhash={settings.getIn(['media', 'use_blurhash'])}
/>)} />)}
</Bundle>, </Bundle>,
); );
@@ -714,6 +716,7 @@ class Status extends ImmutablePureComponent {
defaultWidth={this.props.cachedMediaWidth} defaultWidth={this.props.cachedMediaWidth}
visible={this.state.showMedia} visible={this.state.showMedia}
onToggleVisibility={this.handleToggleMediaVisibility} onToggleVisibility={this.handleToggleMediaVisibility}
useBlurhash={settings.getIn(['media', 'use_blurhash'])}
/> />
)} )}
</Bundle>, </Bundle>,
@@ -731,6 +734,7 @@ class Status extends ImmutablePureComponent {
card={status.get('card')} card={status.get('card')}
compact compact
sensitive={status.get('sensitive')} sensitive={status.get('sensitive')}
useBlurhash={settings.getIn(['media', 'use_blurhash'])}
/>, />,
); );
mediaIcons.push('link'); mediaIcons.push('link');

View File

@@ -7,9 +7,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import { Blurhash } from 'flavours/glitch/components/blurhash'; import { Blurhash } from 'flavours/glitch/components/blurhash';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { autoPlayGif, displayMedia, useBlurhash } from 'flavours/glitch/initial_state'; import { autoPlayGif, displayMedia } from 'flavours/glitch/initial_state';
export default class MediaItem extends ImmutablePureComponent { export default class MediaItem extends ImmutablePureComponent {
@@ -17,6 +15,7 @@ export default class MediaItem extends ImmutablePureComponent {
attachment: ImmutablePropTypes.map.isRequired, attachment: ImmutablePropTypes.map.isRequired,
displayWidth: PropTypes.number.isRequired, displayWidth: PropTypes.number.isRequired,
onOpenMedia: PropTypes.func.isRequired, onOpenMedia: PropTypes.func.isRequired,
useBlurhash: PropTypes.bool,
}; };
state = { state = {
@@ -58,7 +57,7 @@ export default class MediaItem extends ImmutablePureComponent {
}; };
render () { render () {
const { attachment, displayWidth } = this.props; const { attachment, displayWidth, useBlurhash } = this.props;
const { visible, loaded } = this.state; const { visible, loaded } = this.state;
const width = `${Math.floor((displayWidth - 4) / 3) - 4}px`; const width = `${Math.floor((displayWidth - 4) / 3) - 4}px`;

View File

@@ -32,6 +32,7 @@ const mapStateToProps = (state, { params: { acct, id } }) => {
return { return {
accountId, accountId,
settings: state.get('local_settings'),
isAccount: !!state.getIn(['accounts', accountId]), isAccount: !!state.getIn(['accounts', accountId]),
attachments: getAccountGallery(state, accountId), attachments: getAccountGallery(state, accountId),
isLoading: state.getIn(['timelines', `account:${accountId}:media`, 'isLoading']), isLoading: state.getIn(['timelines', `account:${accountId}:media`, 'isLoading']),
@@ -69,6 +70,7 @@ class AccountGallery extends ImmutablePureComponent {
acct: PropTypes.string, acct: PropTypes.string,
id: PropTypes.string, id: PropTypes.string,
}).isRequired, }).isRequired,
settings: ImmutablePropTypes.map.isRequired,
accountId: PropTypes.string, accountId: PropTypes.string,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
attachments: ImmutablePropTypes.list.isRequired, attachments: ImmutablePropTypes.list.isRequired,
@@ -175,7 +177,7 @@ class AccountGallery extends ImmutablePureComponent {
}; };
render () { render () {
const { attachments, isLoading, hasMore, isAccount, multiColumn, suspended } = this.props; const { attachments, isLoading, hasMore, isAccount, settings, multiColumn, suspended } = this.props;
const { width } = this.state; const { width } = this.state;
if (!isAccount) { if (!isAccount) {
@@ -215,7 +217,13 @@ class AccountGallery extends ImmutablePureComponent {
{attachments.map((attachment, index) => attachment === null ? ( {attachments.map((attachment, index) => attachment === null ? (
<LoadMoreMedia key={'more:' + attachments.getIn(index + 1, 'id')} maxId={index > 0 ? attachments.getIn(index - 1, 'id') : null} onLoadMore={this.handleLoadMore} /> <LoadMoreMedia key={'more:' + attachments.getIn(index + 1, 'id')} maxId={index > 0 ? attachments.getIn(index - 1, 'id') : null} onLoadMore={this.handleLoadMore} />
) : ( ) : (
<MediaItem key={attachment.get('id')} attachment={attachment} displayWidth={width} onOpenMedia={this.handleOpenMedia} /> <MediaItem
key={attachment.get('id')}
attachment={attachment}
displayWidth={width}
onOpenMedia={this.handleOpenMedia}
useBlurhash={settings.getIn(['media', 'use_blurhash'])}
/>
))} ))}
{loadOlder} {loadOlder}

View File

@@ -12,7 +12,7 @@ import { throttle, debounce } from 'lodash';
import { Blurhash } from 'flavours/glitch/components/blurhash'; import { Blurhash } from 'flavours/glitch/components/blurhash';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { formatTime, getPointerPosition, fileNameFromURL } from 'flavours/glitch/features/video'; import { formatTime, getPointerPosition, fileNameFromURL } from 'flavours/glitch/features/video';
import { displayMedia, useBlurhash } from 'flavours/glitch/initial_state'; import { displayMedia } from 'flavours/glitch/initial_state';
import Visualizer from './visualizer'; import Visualizer from './visualizer';
@@ -56,6 +56,7 @@ class Audio extends PureComponent {
volume: PropTypes.number, volume: PropTypes.number,
muted: PropTypes.bool, muted: PropTypes.bool,
deployPictureInPicture: PropTypes.func, deployPictureInPicture: PropTypes.func,
useBlurhash: PropTypes.bool,
}; };
state = { state = {
@@ -472,7 +473,7 @@ class Audio extends PureComponent {
}; };
render () { render () {
const { src, intl, alt, lang, editable, autoPlay, sensitive, blurhash } = this.props; const { src, intl, alt, lang, editable, autoPlay, sensitive, blurhash, useBlurhash } = this.props;
const { paused, volume, currentTime, duration, buffer, dragging, revealed } = this.state; const { paused, volume, currentTime, duration, buffer, dragging, revealed } = this.state;
const progress = Math.min((currentTime / duration) * 100, 100); const progress = Math.min((currentTime / duration) * 100, 100);
const muted = this.state.muted || volume === 0; const muted = this.state.muted || volume === 0;

View File

@@ -466,6 +466,14 @@ class LocalSettingsPage extends PureComponent {
({ intl, onChange, settings }) => ( ({ intl, onChange, settings }) => (
<div className='glitch local-settings__page media'> <div className='glitch local-settings__page media'>
<h1><FormattedMessage id='settings.media' defaultMessage='Media' /></h1> <h1><FormattedMessage id='settings.media' defaultMessage='Media' /></h1>
<LocalSettingsPageItem
settings={settings}
item={['media', 'use_blurhash']}
id='mastodon-settings--media-use-blurhash'
onChange={onChange}
>
<FormattedMessage id='setting_use_blurhash' defaultMessage='Show colorful gradients for hidden media' />
</LocalSettingsPageItem>
<LocalSettingsPageItem <LocalSettingsPageItem
settings={settings} settings={settings}
item={['media', 'letterbox']} item={['media', 'letterbox']}

View File

@@ -10,7 +10,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import { Blurhash } from 'flavours/glitch/components/blurhash'; import { Blurhash } from 'flavours/glitch/components/blurhash';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { useBlurhash } from 'flavours/glitch/initial_state';
import { decode as decodeIDNA } from 'flavours/glitch/utils/idna'; import { decode as decodeIDNA } from 'flavours/glitch/utils/idna';
const getHostname = url => { const getHostname = url => {
@@ -49,6 +48,7 @@ export default class Card extends PureComponent {
onOpenMedia: PropTypes.func.isRequired, onOpenMedia: PropTypes.func.isRequired,
compact: PropTypes.bool, compact: PropTypes.bool,
sensitive: PropTypes.bool, sensitive: PropTypes.bool,
useBlurhash: PropTypes.bool,
}; };
static defaultProps = { static defaultProps = {
@@ -138,7 +138,7 @@ export default class Card extends PureComponent {
} }
render () { render () {
const { card, compact } = this.props; const { card, compact, useBlurhash } = this.props;
const { embedded, revealed } = this.state; const { embedded, revealed } = this.state;
if (card === null) { if (card === null) {

View File

@@ -185,6 +185,7 @@ class DetailedStatus extends ImmutablePureComponent {
blurhash={attachment.get('blurhash')} blurhash={attachment.get('blurhash')}
height={150} height={150}
onToggleVisibility={this.props.onToggleMediaVisibility} onToggleVisibility={this.props.onToggleMediaVisibility}
useBlurhash={settings.getIn(['media', 'use_blurhash'])}
/>, />,
); );
mediaIcons.push('music'); mediaIcons.push('music');
@@ -209,6 +210,7 @@ class DetailedStatus extends ImmutablePureComponent {
autoplay autoplay
visible={this.props.showMedia} visible={this.props.showMedia}
onToggleVisibility={this.props.onToggleMediaVisibility} onToggleVisibility={this.props.onToggleMediaVisibility}
useBlurhash={settings.getIn(['media', 'use_blurhash'])}
/>, />,
); );
mediaIcons.push('video-camera'); mediaIcons.push('video-camera');
@@ -225,12 +227,19 @@ class DetailedStatus extends ImmutablePureComponent {
onOpenMedia={this.props.onOpenMedia} onOpenMedia={this.props.onOpenMedia}
visible={this.props.showMedia} visible={this.props.showMedia}
onToggleVisibility={this.props.onToggleMediaVisibility} onToggleVisibility={this.props.onToggleMediaVisibility}
useBlurhash={settings.getIn(['media', 'use_blurhash'])}
/>, />,
); );
mediaIcons.push('picture-o'); mediaIcons.push('picture-o');
} }
} else if (status.get('card')) { } else if (status.get('card')) {
media.push(<Card sensitive={status.get('sensitive')} onOpenMedia={this.props.onOpenMedia} card={status.get('card')} />); media.push(
<Card
sensitive={status.get('sensitive')}
onOpenMedia={this.props.onOpenMedia}
card={status.get('card')}
useBlurhash={settings.getIn(['media', 'use_blurhash'])} />
);
mediaIcons.push('link'); mediaIcons.push('link');
} }

View File

@@ -11,7 +11,7 @@ import { throttle } from 'lodash';
import { Blurhash } from 'flavours/glitch/components/blurhash'; import { Blurhash } from 'flavours/glitch/components/blurhash';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { displayMedia, useBlurhash } from 'flavours/glitch/initial_state'; import { displayMedia } from 'flavours/glitch/initial_state';
import { isFullscreen, requestFullscreen, exitFullscreen } from '../ui/util/fullscreen'; import { isFullscreen, requestFullscreen, exitFullscreen } from '../ui/util/fullscreen';
@@ -129,6 +129,7 @@ class Video extends PureComponent {
muted: PropTypes.bool, muted: PropTypes.bool,
componentIndex: PropTypes.number, componentIndex: PropTypes.number,
autoFocus: PropTypes.bool, autoFocus: PropTypes.bool,
useBlurhash: PropTypes.bool,
}; };
static defaultProps = { static defaultProps = {
@@ -509,8 +510,35 @@ class Video extends PureComponent {
} }
render () { render () {
const { preview, src, inline, onOpenVideo, onCloseVideo, intl, alt, lang, letterbox, fullwidth, detailed, sensitive, editable, blurhash, autoFocus } = this.props; const {
const { currentTime, duration, volume, buffer, dragging, paused, fullscreen, hovered, revealed } = this.state; preview,
src,
inline,
onOpenVideo,
onCloseVideo,
intl,
alt,
lang,
letterbox,
fullwidth,
detailed,
sensitive,
editable,
blurhash,
autoFocus,
useBlurhash
} = this.props;
const {
currentTime,
duration,
volume,
buffer,
dragging,
paused,
fullscreen,
hovered,
revealed
} = this.state;
const progress = Math.min((currentTime / duration) * 100, 100); const progress = Math.min((currentTime / duration) * 100, 100);
const muted = this.state.muted || volume === 0; const muted = this.state.muted || volume === 0;

View File

@@ -79,7 +79,6 @@
* @property {boolean} show_trends * @property {boolean} show_trends
* @property {boolean} trends_as_landing_page * @property {boolean} trends_as_landing_page
* @property {boolean} unfollow_modal * @property {boolean} unfollow_modal
* @property {boolean} use_blurhash
* @property {boolean=} use_pending_items * @property {boolean=} use_pending_items
* @property {string} version * @property {string} version
* @property {string} sso_redirect * @property {string} sso_redirect
@@ -158,7 +157,6 @@ export const timelinePreview = getMeta('timeline_preview');
export const title = getMeta('title'); export const title = getMeta('title');
export const trendsAsLanding = getMeta('trends_as_landing_page'); export const trendsAsLanding = getMeta('trends_as_landing_page');
export const unfollowModal = getMeta('unfollow_modal'); export const unfollowModal = getMeta('unfollow_modal');
export const useBlurhash = getMeta('use_blurhash');
export const usePendingItems = getMeta('use_pending_items'); export const usePendingItems = getMeta('use_pending_items');
export const version = getMeta('version'); export const version = getMeta('version');
export const languages = initialState?.languages; export const languages = initialState?.languages;

View File

@@ -45,6 +45,7 @@ const initialState = ImmutableMap({
show_action_bar : true, show_action_bar : true,
}), }),
media : ImmutableMap({ media : ImmutableMap({
use_blurhash : true,
letterbox : false, letterbox : false,
fullwidth : true, fullwidth : true,
reveal_behind_cw : false, reveal_behind_cw : false,