Refactor initial state: reduce_motion and auto_play_gif (#5501)

This commit is contained in:
Nolan Lawson 2017-10-27 08:04:44 -07:00 committed by unarist
parent e4080772b5
commit 3de22a82bf
13 changed files with 27 additions and 52 deletions

View file

@ -6,6 +6,7 @@ import IconButton from './icon_button';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { isIOS } from '../is_mobile'; import { isIOS } from '../is_mobile';
import classNames from 'classnames'; import classNames from 'classnames';
import { autoPlayGif } from '../initial_state';
const messages = defineMessages({ const messages = defineMessages({
toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Toggle visibility' }, toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Toggle visibility' },
@ -23,11 +24,9 @@ class Item extends React.PureComponent {
index: PropTypes.number.isRequired, index: PropTypes.number.isRequired,
size: PropTypes.number.isRequired, size: PropTypes.number.isRequired,
onClick: PropTypes.func.isRequired, onClick: PropTypes.func.isRequired,
autoPlayGif: PropTypes.bool,
}; };
static defaultProps = { static defaultProps = {
autoPlayGif: false,
standalone: false, standalone: false,
index: 0, index: 0,
size: 1, size: 1,
@ -47,7 +46,7 @@ class Item extends React.PureComponent {
} }
hoverToPlay () { hoverToPlay () {
const { attachment, autoPlayGif } = this.props; const { attachment } = this.props;
return !autoPlayGif && attachment.get('type') === 'gifv'; return !autoPlayGif && attachment.get('type') === 'gifv';
} }
@ -139,7 +138,7 @@ class Item extends React.PureComponent {
</a> </a>
); );
} else if (attachment.get('type') === 'gifv') { } else if (attachment.get('type') === 'gifv') {
const autoPlay = !isIOS() && this.props.autoPlayGif; const autoPlay = !isIOS() && autoPlayGif;
thumbnail = ( thumbnail = (
<div className={classNames('media-gallery__gifv', { autoplay: autoPlay })}> <div className={classNames('media-gallery__gifv', { autoplay: autoPlay })}>
@ -181,11 +180,9 @@ export default class MediaGallery extends React.PureComponent {
height: PropTypes.number.isRequired, height: PropTypes.number.isRequired,
onOpenMedia: PropTypes.func.isRequired, onOpenMedia: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
autoPlayGif: PropTypes.bool,
}; };
static defaultProps = { static defaultProps = {
autoPlayGif: false,
standalone: false, standalone: false,
}; };
@ -261,9 +258,9 @@ export default class MediaGallery extends React.PureComponent {
const size = media.take(4).size; const size = media.take(4).size;
if (this.isStandaloneEligible()) { if (this.isStandaloneEligible()) {
children = <Item standalone onClick={this.handleClick} attachment={media.get(0)} autoPlayGif={this.props.autoPlayGif} />; children = <Item standalone onClick={this.handleClick} attachment={media.get(0)} />;
} else { } else {
children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} autoPlayGif={this.props.autoPlayGif} index={i} size={size} />); children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} index={i} size={size} />);
} }
} }

View file

@ -38,7 +38,6 @@ export default class Status extends ImmutablePureComponent {
onHeightChange: PropTypes.func, onHeightChange: PropTypes.func,
me: PropTypes.string, me: PropTypes.string,
boostModal: PropTypes.bool, boostModal: PropTypes.bool,
autoPlayGif: PropTypes.bool,
muted: PropTypes.bool, muted: PropTypes.bool,
hidden: PropTypes.bool, hidden: PropTypes.bool,
onMoveUp: PropTypes.func, onMoveUp: PropTypes.func,
@ -56,7 +55,6 @@ export default class Status extends ImmutablePureComponent {
'account', 'account',
'me', 'me',
'boostModal', 'boostModal',
'autoPlayGif',
'muted', 'muted',
'hidden', 'hidden',
] ]
@ -197,7 +195,7 @@ export default class Status extends ImmutablePureComponent {
} else { } else {
media = ( media = (
<Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery} > <Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery} >
{Component => <Component media={status.get('media_attachments')} sensitive={status.get('sensitive')} height={110} onOpenMedia={this.props.onOpenMedia} autoPlayGif={this.props.autoPlayGif} />} {Component => <Component media={status.get('media_attachments')} sensitive={status.get('sensitive')} height={110} onOpenMedia={this.props.onOpenMedia} />}
</Bundle> </Bundle>
); );
} }

View file

@ -6,15 +6,14 @@ import { hydrateStore } from '../actions/store';
import { IntlProvider, addLocaleData } from 'react-intl'; import { IntlProvider, addLocaleData } from 'react-intl';
import { getLocale } from '../locales'; import { getLocale } from '../locales';
import Compose from '../features/standalone/compose'; import Compose from '../features/standalone/compose';
import initialState from '../initial_state';
const { localeData, messages } = getLocale(); const { localeData, messages } = getLocale();
addLocaleData(localeData); addLocaleData(localeData);
const store = configureStore(); const store = configureStore();
const initialStateContainer = document.getElementById('initial-state');
if (initialStateContainer !== null) { if (initialState) {
const initialState = JSON.parse(initialStateContainer.textContent);
store.dispatch(hydrateStore(initialState)); store.dispatch(hydrateStore(initialState));
} }

View file

@ -10,12 +10,13 @@ import { hydrateStore } from '../actions/store';
import { connectUserStream } from '../actions/streaming'; import { connectUserStream } from '../actions/streaming';
import { IntlProvider, addLocaleData } from 'react-intl'; import { IntlProvider, addLocaleData } from 'react-intl';
import { getLocale } from '../locales'; import { getLocale } from '../locales';
import initialState from '../initial_state';
const { localeData, messages } = getLocale(); const { localeData, messages } = getLocale();
addLocaleData(localeData); addLocaleData(localeData);
export const store = configureStore(); export const store = configureStore();
const hydrateAction = hydrateStore(JSON.parse(document.getElementById('initial-state').textContent)); const hydrateAction = hydrateStore(initialState);
store.dispatch(hydrateAction); store.dispatch(hydrateAction);
export default class Mastodon extends React.PureComponent { export default class Mastodon extends React.PureComponent {

View file

@ -38,7 +38,6 @@ const makeMapStateToProps = () => {
me: state.getIn(['meta', 'me']), me: state.getIn(['meta', 'me']),
boostModal: state.getIn(['meta', 'boost_modal']), boostModal: state.getIn(['meta', 'boost_modal']),
deleteModal: state.getIn(['meta', 'delete_modal']), deleteModal: state.getIn(['meta', 'delete_modal']),
autoPlayGif: state.getIn(['meta', 'auto_play_gif']),
}); });
return mapStateToProps; return mapStateToProps;

View file

@ -7,15 +7,14 @@ import { IntlProvider, addLocaleData } from 'react-intl';
import { getLocale } from '../locales'; import { getLocale } from '../locales';
import PublicTimeline from '../features/standalone/public_timeline'; import PublicTimeline from '../features/standalone/public_timeline';
import HashtagTimeline from '../features/standalone/hashtag_timeline'; import HashtagTimeline from '../features/standalone/hashtag_timeline';
import initialState from '../initial_state';
const { localeData, messages } = getLocale(); const { localeData, messages } = getLocale();
addLocaleData(localeData); addLocaleData(localeData);
const store = configureStore(); const store = configureStore();
const initialStateContainer = document.getElementById('initial-state');
if (initialStateContainer !== null) { if (initialState) {
const initialState = JSON.parse(initialStateContainer.textContent);
store.dispatch(hydrateStore(initialState)); store.dispatch(hydrateStore(initialState));
} }

View file

@ -5,8 +5,8 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import IconButton from '../../../components/icon_button'; import IconButton from '../../../components/icon_button';
import Motion from '../../ui/util/optional_motion'; import Motion from '../../ui/util/optional_motion';
import spring from 'react-motion/lib/spring'; import spring from 'react-motion/lib/spring';
import { connect } from 'react-redux';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { autoPlayGif } from '../../../initial_state';
const messages = defineMessages({ const messages = defineMessages({
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
@ -14,19 +14,10 @@ const messages = defineMessages({
requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request' }, requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request' },
}); });
const makeMapStateToProps = () => {
const mapStateToProps = state => ({
autoPlayGif: state.getIn(['meta', 'auto_play_gif']),
});
return mapStateToProps;
};
class Avatar extends ImmutablePureComponent { class Avatar extends ImmutablePureComponent {
static propTypes = { static propTypes = {
account: ImmutablePropTypes.map.isRequired, account: ImmutablePropTypes.map.isRequired,
autoPlayGif: PropTypes.bool.isRequired,
}; };
state = { state = {
@ -44,7 +35,7 @@ class Avatar extends ImmutablePureComponent {
} }
render () { render () {
const { account, autoPlayGif } = this.props; const { account } = this.props;
const { isHovered } = this.state; const { isHovered } = this.state;
return ( return (
@ -71,7 +62,6 @@ class Avatar extends ImmutablePureComponent {
} }
@connect(makeMapStateToProps)
@injectIntl @injectIntl
export default class Header extends ImmutablePureComponent { export default class Header extends ImmutablePureComponent {
@ -80,7 +70,6 @@ export default class Header extends ImmutablePureComponent {
me: PropTypes.string.isRequired, me: PropTypes.string.isRequired,
onFollow: PropTypes.func.isRequired, onFollow: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
autoPlayGif: PropTypes.bool.isRequired,
}; };
render () { render () {
@ -124,7 +113,7 @@ export default class Header extends ImmutablePureComponent {
return ( return (
<div className='account__header' style={{ backgroundImage: `url(${account.get('header')})` }}> <div className='account__header' style={{ backgroundImage: `url(${account.get('header')})` }}>
<div> <div>
<Avatar account={account} autoPlayGif={this.props.autoPlayGif} /> <Avatar account={account} />
<span className='account__header__display-name' dangerouslySetInnerHTML={displayNameHtml} /> <span className='account__header__display-name' dangerouslySetInnerHTML={displayNameHtml} />
<span className='account__header__username'>@{account.get('acct')} {lockedIcon}</span> <span className='account__header__username'>@{account.get('acct')} {lockedIcon}</span>

View file

@ -19,7 +19,6 @@ const mapStateToProps = (state, props) => ({
medias: getAccountGallery(state, props.params.accountId), medias: getAccountGallery(state, props.params.accountId),
isLoading: state.getIn(['timelines', `account:${props.params.accountId}:media`, 'isLoading']), isLoading: state.getIn(['timelines', `account:${props.params.accountId}:media`, 'isLoading']),
hasMore: !!state.getIn(['timelines', `account:${props.params.accountId}:media`, 'next']), hasMore: !!state.getIn(['timelines', `account:${props.params.accountId}:media`, 'next']),
autoPlayGif: state.getIn(['meta', 'auto_play_gif']),
}); });
@connect(mapStateToProps) @connect(mapStateToProps)
@ -31,7 +30,6 @@ export default class AccountGallery extends ImmutablePureComponent {
medias: ImmutablePropTypes.list.isRequired, medias: ImmutablePropTypes.list.isRequired,
isLoading: PropTypes.bool, isLoading: PropTypes.bool,
hasMore: PropTypes.bool, hasMore: PropTypes.bool,
autoPlayGif: PropTypes.bool,
}; };
componentDidMount () { componentDidMount () {
@ -67,7 +65,7 @@ export default class AccountGallery extends ImmutablePureComponent {
} }
render () { render () {
const { medias, autoPlayGif, isLoading, hasMore } = this.props; const { medias, isLoading, hasMore } = this.props;
let loadMore = null; let loadMore = null;
@ -100,7 +98,6 @@ export default class AccountGallery extends ImmutablePureComponent {
<MediaItem <MediaItem
key={media.get('id')} key={media.get('id')}
media={media} media={media}
autoPlayGif={autoPlayGif}
/> />
)} )}
{loadMore} {loadMore}

View file

@ -22,7 +22,6 @@ export default class DetailedStatus extends ImmutablePureComponent {
status: ImmutablePropTypes.map.isRequired, status: ImmutablePropTypes.map.isRequired,
onOpenMedia: PropTypes.func.isRequired, onOpenMedia: PropTypes.func.isRequired,
onOpenVideo: PropTypes.func.isRequired, onOpenVideo: PropTypes.func.isRequired,
autoPlayGif: PropTypes.bool,
}; };
handleAccountClick = (e) => { handleAccountClick = (e) => {
@ -70,7 +69,6 @@ export default class DetailedStatus extends ImmutablePureComponent {
media={status.get('media_attachments')} media={status.get('media_attachments')}
height={300} height={300}
onOpenMedia={this.props.onOpenMedia} onOpenMedia={this.props.onOpenMedia}
autoPlayGif={this.props.autoPlayGif}
/> />
); );
} }

View file

@ -45,7 +45,6 @@ const makeMapStateToProps = () => {
me: state.getIn(['meta', 'me']), me: state.getIn(['meta', 'me']),
boostModal: state.getIn(['meta', 'boost_modal']), boostModal: state.getIn(['meta', 'boost_modal']),
deleteModal: state.getIn(['meta', 'delete_modal']), deleteModal: state.getIn(['meta', 'delete_modal']),
autoPlayGif: state.getIn(['meta', 'auto_play_gif']),
}); });
return mapStateToProps; return mapStateToProps;
@ -68,7 +67,6 @@ export default class Status extends ImmutablePureComponent {
me: PropTypes.string, me: PropTypes.string,
boostModal: PropTypes.bool, boostModal: PropTypes.bool,
deleteModal: PropTypes.bool, deleteModal: PropTypes.bool,
autoPlayGif: PropTypes.bool,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
}; };
@ -257,7 +255,7 @@ export default class Status extends ImmutablePureComponent {
render () { render () {
let ancestors, descendants; let ancestors, descendants;
const { status, ancestorsIds, descendantsIds, me, autoPlayGif } = this.props; const { status, ancestorsIds, descendantsIds, me } = this.props;
if (status === null) { if (status === null) {
return ( return (
@ -298,7 +296,6 @@ export default class Status extends ImmutablePureComponent {
<div className='focusable' tabIndex='0'> <div className='focusable' tabIndex='0'>
<DetailedStatus <DetailedStatus
status={status} status={status}
autoPlayGif={autoPlayGif}
me={me} me={me}
onOpenVideo={this.handleOpenVideo} onOpenVideo={this.handleOpenVideo}
onOpenMedia={this.handleOpenMedia} onOpenMedia={this.handleOpenMedia}

View file

@ -4,11 +4,10 @@
import React from 'react'; import React from 'react';
import Motion from 'react-motion/lib/Motion'; import Motion from 'react-motion/lib/Motion';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { reduceMotion } from '../../../initial_state';
const stylesToKeep = ['opacity', 'backgroundOpacity']; const stylesToKeep = ['opacity', 'backgroundOpacity'];
let reduceMotion;
const extractValue = (value) => { const extractValue = (value) => {
// This is either an object with a "val" property or it's a number // This is either an object with a "val" property or it's a number
return (typeof value === 'object' && value && 'val' in value) ? value.val : value; return (typeof value === 'object' && value && 'val' in value) ? value.val : value;
@ -26,12 +25,6 @@ class OptionalMotion extends React.Component {
const { style, defaultStyle, children } = this.props; const { style, defaultStyle, children } = this.props;
if (typeof reduceMotion !== 'boolean') {
// This never changes without a page reload, so we can just grab it
// once from the body classes as opposed to using Redux's connect(),
// which would unnecessarily update every state change
reduceMotion = document.body.classList.contains('reduce-motion');
}
if (reduceMotion) { if (reduceMotion) {
Object.keys(style).forEach(key => { Object.keys(style).forEach(key => {
if (stylesToKeep.includes(key)) { if (stylesToKeep.includes(key)) {

View file

@ -0,0 +1,9 @@
const element = document.getElementById('initial-state');
const initialState = element && JSON.parse(element.textContent);
const getMeta = (prop) => initialState && initialState.meta && initialState.meta[prop];
export const reduceMotion = getMeta('reduce_motion');
export const autoPlayGif = getMeta('auto_play_gif');
export default initialState;

View file

@ -27,7 +27,6 @@
= yield :header_tags = yield :header_tags
- body_classes ||= @body_classes || '' - body_classes ||= @body_classes || ''
- body_classes += ' reduce-motion' if current_account&.user&.setting_reduce_motion
- body_classes += ' system-font' if current_account&.user&.setting_system_font_ui - body_classes += ' system-font' if current_account&.user&.setting_system_font_ui
%body{ class: add_rtl_body_class(body_classes) } %body{ class: add_rtl_body_class(body_classes) }