From e4080772b511e8dd436fddc79de10f44e4d83ff6 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Fri, 27 Oct 2017 23:54:20 +0900 Subject: [PATCH 01/89] Use contenthash for ExtractTextWebpackPlugin (#5462) [hash] is not documented. --- config/webpack/shared.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/webpack/shared.js b/config/webpack/shared.js index cd642a28a..5ff267fc5 100644 --- a/config/webpack/shared.js +++ b/config/webpack/shared.js @@ -55,7 +55,7 @@ module.exports = { resource.request = resource.request.replace(/^history/, 'history/es'); } ), - new ExtractTextPlugin(env.NODE_ENV === 'production' ? '[name]-[hash].css' : '[name].css'), + new ExtractTextPlugin(env.NODE_ENV === 'production' ? '[name]-[contenthash].css' : '[name].css'), new ManifestPlugin({ publicPath: output.publicPath, writeToFileEmit: true, From 3de22a82bf1c3c0a3b593be4075cf42a3ec9291e Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Fri, 27 Oct 2017 08:04:44 -0700 Subject: [PATCH 02/89] Refactor initial state: reduce_motion and auto_play_gif (#5501) --- .../mastodon/components/media_gallery.js | 13 +++++-------- app/javascript/mastodon/components/status.js | 4 +--- .../mastodon/containers/compose_container.js | 5 ++--- app/javascript/mastodon/containers/mastodon.js | 3 ++- .../mastodon/containers/status_container.js | 1 - .../mastodon/containers/timeline_container.js | 5 ++--- .../features/account/components/header.js | 17 +++-------------- .../mastodon/features/account_gallery/index.js | 5 +---- .../status/components/detailed_status.js | 2 -- .../mastodon/features/status/index.js | 5 +---- .../features/ui/util/optional_motion.js | 9 +-------- app/javascript/mastodon/initial_state.js | 9 +++++++++ app/views/layouts/application.html.haml | 1 - 13 files changed, 27 insertions(+), 52 deletions(-) create mode 100644 app/javascript/mastodon/initial_state.js diff --git a/app/javascript/mastodon/components/media_gallery.js b/app/javascript/mastodon/components/media_gallery.js index fb71d8c5c..20febdb16 100644 --- a/app/javascript/mastodon/components/media_gallery.js +++ b/app/javascript/mastodon/components/media_gallery.js @@ -6,6 +6,7 @@ import IconButton from './icon_button'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { isIOS } from '../is_mobile'; import classNames from 'classnames'; +import { autoPlayGif } from '../initial_state'; const messages = defineMessages({ toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Toggle visibility' }, @@ -23,11 +24,9 @@ class Item extends React.PureComponent { index: PropTypes.number.isRequired, size: PropTypes.number.isRequired, onClick: PropTypes.func.isRequired, - autoPlayGif: PropTypes.bool, }; static defaultProps = { - autoPlayGif: false, standalone: false, index: 0, size: 1, @@ -47,7 +46,7 @@ class Item extends React.PureComponent { } hoverToPlay () { - const { attachment, autoPlayGif } = this.props; + const { attachment } = this.props; return !autoPlayGif && attachment.get('type') === 'gifv'; } @@ -139,7 +138,7 @@ class Item extends React.PureComponent { ); } else if (attachment.get('type') === 'gifv') { - const autoPlay = !isIOS() && this.props.autoPlayGif; + const autoPlay = !isIOS() && autoPlayGif; thumbnail = (
@@ -181,11 +180,9 @@ export default class MediaGallery extends React.PureComponent { height: PropTypes.number.isRequired, onOpenMedia: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, - autoPlayGif: PropTypes.bool, }; static defaultProps = { - autoPlayGif: false, standalone: false, }; @@ -261,9 +258,9 @@ export default class MediaGallery extends React.PureComponent { const size = media.take(4).size; if (this.isStandaloneEligible()) { - children = ; + children = ; } else { - children = media.take(4).map((attachment, i) => ); + children = media.take(4).map((attachment, i) => ); } } diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index 70005436b..bed354059 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -38,7 +38,6 @@ export default class Status extends ImmutablePureComponent { onHeightChange: PropTypes.func, me: PropTypes.string, boostModal: PropTypes.bool, - autoPlayGif: PropTypes.bool, muted: PropTypes.bool, hidden: PropTypes.bool, onMoveUp: PropTypes.func, @@ -56,7 +55,6 @@ export default class Status extends ImmutablePureComponent { 'account', 'me', 'boostModal', - 'autoPlayGif', 'muted', 'hidden', ] @@ -197,7 +195,7 @@ export default class Status extends ImmutablePureComponent { } else { media = ( - {Component => } + {Component => } ); } diff --git a/app/javascript/mastodon/containers/compose_container.js b/app/javascript/mastodon/containers/compose_container.js index db452d03a..5ee1d2f14 100644 --- a/app/javascript/mastodon/containers/compose_container.js +++ b/app/javascript/mastodon/containers/compose_container.js @@ -6,15 +6,14 @@ import { hydrateStore } from '../actions/store'; import { IntlProvider, addLocaleData } from 'react-intl'; import { getLocale } from '../locales'; import Compose from '../features/standalone/compose'; +import initialState from '../initial_state'; const { localeData, messages } = getLocale(); addLocaleData(localeData); const store = configureStore(); -const initialStateContainer = document.getElementById('initial-state'); -if (initialStateContainer !== null) { - const initialState = JSON.parse(initialStateContainer.textContent); +if (initialState) { store.dispatch(hydrateStore(initialState)); } diff --git a/app/javascript/mastodon/containers/mastodon.js b/app/javascript/mastodon/containers/mastodon.js index 56b7bda46..e1d89a5b8 100644 --- a/app/javascript/mastodon/containers/mastodon.js +++ b/app/javascript/mastodon/containers/mastodon.js @@ -10,12 +10,13 @@ import { hydrateStore } from '../actions/store'; import { connectUserStream } from '../actions/streaming'; import { IntlProvider, addLocaleData } from 'react-intl'; import { getLocale } from '../locales'; +import initialState from '../initial_state'; const { localeData, messages } = getLocale(); addLocaleData(localeData); export const store = configureStore(); -const hydrateAction = hydrateStore(JSON.parse(document.getElementById('initial-state').textContent)); +const hydrateAction = hydrateStore(initialState); store.dispatch(hydrateAction); export default class Mastodon extends React.PureComponent { diff --git a/app/javascript/mastodon/containers/status_container.js b/app/javascript/mastodon/containers/status_container.js index c61b7d00d..29eb5f955 100644 --- a/app/javascript/mastodon/containers/status_container.js +++ b/app/javascript/mastodon/containers/status_container.js @@ -38,7 +38,6 @@ const makeMapStateToProps = () => { me: state.getIn(['meta', 'me']), boostModal: state.getIn(['meta', 'boost_modal']), deleteModal: state.getIn(['meta', 'delete_modal']), - autoPlayGif: state.getIn(['meta', 'auto_play_gif']), }); return mapStateToProps; diff --git a/app/javascript/mastodon/containers/timeline_container.js b/app/javascript/mastodon/containers/timeline_container.js index 4be037955..e84c921ee 100644 --- a/app/javascript/mastodon/containers/timeline_container.js +++ b/app/javascript/mastodon/containers/timeline_container.js @@ -7,15 +7,14 @@ import { IntlProvider, addLocaleData } from 'react-intl'; import { getLocale } from '../locales'; import PublicTimeline from '../features/standalone/public_timeline'; import HashtagTimeline from '../features/standalone/hashtag_timeline'; +import initialState from '../initial_state'; const { localeData, messages } = getLocale(); addLocaleData(localeData); const store = configureStore(); -const initialStateContainer = document.getElementById('initial-state'); -if (initialStateContainer !== null) { - const initialState = JSON.parse(initialStateContainer.textContent); +if (initialState) { store.dispatch(hydrateStore(initialState)); } diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js index 07a6c5dec..99ead014e 100644 --- a/app/javascript/mastodon/features/account/components/header.js +++ b/app/javascript/mastodon/features/account/components/header.js @@ -5,8 +5,8 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import IconButton from '../../../components/icon_button'; import Motion from '../../ui/util/optional_motion'; import spring from 'react-motion/lib/spring'; -import { connect } from 'react-redux'; import ImmutablePureComponent from 'react-immutable-pure-component'; +import { autoPlayGif } from '../../../initial_state'; const messages = defineMessages({ 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' }, }); -const makeMapStateToProps = () => { - const mapStateToProps = state => ({ - autoPlayGif: state.getIn(['meta', 'auto_play_gif']), - }); - - return mapStateToProps; -}; - class Avatar extends ImmutablePureComponent { static propTypes = { account: ImmutablePropTypes.map.isRequired, - autoPlayGif: PropTypes.bool.isRequired, }; state = { @@ -44,7 +35,7 @@ class Avatar extends ImmutablePureComponent { } render () { - const { account, autoPlayGif } = this.props; + const { account } = this.props; const { isHovered } = this.state; return ( @@ -71,7 +62,6 @@ class Avatar extends ImmutablePureComponent { } -@connect(makeMapStateToProps) @injectIntl export default class Header extends ImmutablePureComponent { @@ -80,7 +70,6 @@ export default class Header extends ImmutablePureComponent { me: PropTypes.string.isRequired, onFollow: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, - autoPlayGif: PropTypes.bool.isRequired, }; render () { @@ -124,7 +113,7 @@ export default class Header extends ImmutablePureComponent { return (
- + @{account.get('acct')} {lockedIcon} diff --git a/app/javascript/mastodon/features/account_gallery/index.js b/app/javascript/mastodon/features/account_gallery/index.js index 2a88addc4..6a5c07568 100644 --- a/app/javascript/mastodon/features/account_gallery/index.js +++ b/app/javascript/mastodon/features/account_gallery/index.js @@ -19,7 +19,6 @@ const mapStateToProps = (state, props) => ({ medias: getAccountGallery(state, props.params.accountId), isLoading: state.getIn(['timelines', `account:${props.params.accountId}:media`, 'isLoading']), hasMore: !!state.getIn(['timelines', `account:${props.params.accountId}:media`, 'next']), - autoPlayGif: state.getIn(['meta', 'auto_play_gif']), }); @connect(mapStateToProps) @@ -31,7 +30,6 @@ export default class AccountGallery extends ImmutablePureComponent { medias: ImmutablePropTypes.list.isRequired, isLoading: PropTypes.bool, hasMore: PropTypes.bool, - autoPlayGif: PropTypes.bool, }; componentDidMount () { @@ -67,7 +65,7 @@ export default class AccountGallery extends ImmutablePureComponent { } render () { - const { medias, autoPlayGif, isLoading, hasMore } = this.props; + const { medias, isLoading, hasMore } = this.props; let loadMore = null; @@ -100,7 +98,6 @@ export default class AccountGallery extends ImmutablePureComponent { )} {loadMore} diff --git a/app/javascript/mastodon/features/status/components/detailed_status.js b/app/javascript/mastodon/features/status/components/detailed_status.js index c10e2c531..81f71749b 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.js +++ b/app/javascript/mastodon/features/status/components/detailed_status.js @@ -22,7 +22,6 @@ export default class DetailedStatus extends ImmutablePureComponent { status: ImmutablePropTypes.map.isRequired, onOpenMedia: PropTypes.func.isRequired, onOpenVideo: PropTypes.func.isRequired, - autoPlayGif: PropTypes.bool, }; handleAccountClick = (e) => { @@ -70,7 +69,6 @@ export default class DetailedStatus extends ImmutablePureComponent { media={status.get('media_attachments')} height={300} onOpenMedia={this.props.onOpenMedia} - autoPlayGif={this.props.autoPlayGif} /> ); } diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js index 7ad3a7644..6e95fa939 100644 --- a/app/javascript/mastodon/features/status/index.js +++ b/app/javascript/mastodon/features/status/index.js @@ -45,7 +45,6 @@ const makeMapStateToProps = () => { me: state.getIn(['meta', 'me']), boostModal: state.getIn(['meta', 'boost_modal']), deleteModal: state.getIn(['meta', 'delete_modal']), - autoPlayGif: state.getIn(['meta', 'auto_play_gif']), }); return mapStateToProps; @@ -68,7 +67,6 @@ export default class Status extends ImmutablePureComponent { me: PropTypes.string, boostModal: PropTypes.bool, deleteModal: PropTypes.bool, - autoPlayGif: PropTypes.bool, intl: PropTypes.object.isRequired, }; @@ -257,7 +255,7 @@ export default class Status extends ImmutablePureComponent { render () { let ancestors, descendants; - const { status, ancestorsIds, descendantsIds, me, autoPlayGif } = this.props; + const { status, ancestorsIds, descendantsIds, me } = this.props; if (status === null) { return ( @@ -298,7 +296,6 @@ export default class Status extends ImmutablePureComponent {
{ // 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; @@ -26,12 +25,6 @@ class OptionalMotion extends React.Component { 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) { Object.keys(style).forEach(key => { if (stylesToKeep.includes(key)) { diff --git a/app/javascript/mastodon/initial_state.js b/app/javascript/mastodon/initial_state.js new file mode 100644 index 000000000..ac7315f68 --- /dev/null +++ b/app/javascript/mastodon/initial_state.js @@ -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; diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 831858bcf..ee995c987 100755 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -27,7 +27,6 @@ = yield :header_tags - 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{ class: add_rtl_body_class(body_classes) } From 37b267e2ab8e59ce1dea33f4217a61a90488aac3 Mon Sep 17 00:00:00 2001 From: David Yip Date: Fri, 27 Oct 2017 10:05:04 -0500 Subject: [PATCH 03/89] Add artist, title, and date metadata to boop.{mp3,ogg} (#5531) For boop.mp3, this commit adds both ID3v1 and ID3v2 tags. For boop.ogg, we use Vorbis metadata. In the case of boop.mp3, this also adds a cover image. Interestingly, it didn't seem to affect the size of boop.mp3 much, despite being ~8k. boop.ogg seemed to be much more affected and so no cover image was added to that version. --- public/sounds/boop.mp3 | Bin 12070 -> 12280 bytes public/sounds/boop.ogg | Bin 5164 -> 5247 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/public/sounds/boop.mp3 b/public/sounds/boop.mp3 index 02a035d91b02049949e92f4116d2a1be6c85a4f1..bf9c3c1aaff40dea76ad82e3e853d2d32382bcea 100644 GIT binary patch literal 12280 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 literal 12070 zcmeHLcU)6v7rsEifS`hiDEfH`AgE*~1R_%+LqTO&5l{)q4Ft$AgJmUxEjU0%K?Q_T z1sOuw;6hNem5PG1g4BVi6kM&iP{?MAp0ANB20Qk5s0j%vJ`E*7oBmx7WAU4+qb?-nU3S={FP|L{fICs7y6wG#s z7C>Ini@X`pp$sb~%Fb59I+7a6;d3An9gO6JafQ@K8x)<%3xuffciCku3Y43OLTykE zvKt`m40PlPAdrl)Kr?U_6p&(t!ILQz62%-O;P7NDj)*1V&;%ltgrkxO;K)MRYJk=P zCX4Dxa~g35|FS^^i$r`X78?-}fr%huc!D4--pa}fiz8qO1T<`c7DjPJ^hh*UXe5JB zV9+2TL%`;X*gP&M!=wlD#3CCM3ce4%$b`eExxa+x3NiA$Fbp0C3#W|5V{lkn6qn9n zGu$C28&34qy>Hk_SM22mkw2>V`?1 z4~MuSqhC7}iaeoT$a@C}*!&d$CgWuQe6b)*PCS!=g~A{X>`MsqgIB~1lSOrhmB6Hn z=rp!3)G1%qKpCZSnGG6T&dlE`o%WH=fMi9jY2NdyALLLLp1L1pm-96DT8x|qr4 zVL5y#NG?~<8cvPoPW2YR-6@m_ctpbU=s}8K3Pp#p<+o(&m6?lV=8NbYm~KZgw1mgz zia>X|AQTb^6*73mP$m|+;j8yp0vNzkUNaVngva4f*mrbE1Lg6))ur6(13C2)2QtFg z{I~jar)6$TFF$iE?6B*emIIz1A2UAxf!h)BjHA1c zQ^T@@PShn4u?L)-R_)D+&nEEdoFvyTUy&YW8*Z!`myMLZeX%kO)}`N8JEvZD^}4oD z5=wd?88&^keyD)jDBt`j=3e1LNqY^V5&)(V0pPPtzrm6ax5X;0E3FcToi#gE+{^M# z#YVb4CbUYxq)4__Im+$GCrR^WY1$B5PflsYnI>nMebc+>hja9<>t3xM6_-;JC;4}r zYC?XK@2xeRXb9}>NLjyLH8B8*nK})FMCu?AD%yauz4yhY*opZ~_TI-4$P6bWPyhyy z^;7`!wD0Q!pH}978r-+?klrpM)@`i^KF&3M?O%maXp|UTjZ;MWxMpAKV zS!zR0<>7{taYl&N)fuTv?`I@uq%8fJu%JyT*U9P0om9hb6Y4W0K^c-|kD}&0MX`)6 z@1q=Z)$&57=Pn(}^_|J+p2^$rWaGGj^N+{#XAif{9xlKC0Q-}UPs;VCjpMAl^B4TP{G(wsct|(C6m%`WN)bQbgg+86Ub)S9e$bFEAVb#=ftz-H052wsmzGgv3Udhxmd?a z_h@#|NYhPKF1+N&V9y+fs6KlY=Ubs}K>ESHt<;Xs17q!aY>K_Vhwu-7kOI#M)PAcvAoJ*X1iZ8|#u+iMI|7 zDs=u1#Qu+%$Q5VTyV{`)`Vk+F>?QATyZq6mHe_FR=!{FJYl^aO!FMZeG3Nn3akwvM z#Q_ydJ_j_;&b-I+)NLoZSryy>V@aDzPqkSM%c43@?L+t-CTOI!7_fCbc$3oWLN9CD zgP$;ut_%RQ)~E1AUgoC*PV^cBr0Uw>UQ0cnfQ8%nR|EQo)=lOgj>XK4IWrcQk(NPp zscA}}Xr1kfpWf0!3&b;SwAN0SLT?QBrU7Y)%Ppky7q2kWQ@UGeeatqZq8_xHe7yIihItlls4Sc zB;J!`-+dgfA7MLOD?K&*U}$))^f~T1t|9)xm6+U>N;>vHdvTQ8qwK}y^p&UK}cv@FdNg*I>44QQ@bnqQ)- zBRtAX`Yc6fLK87{=N-E+l7Xrk3IQIS>fqthK}b0<)=bmfUe_EkxYkXB|b6+)A?d z3CH;BuW>6D6N|?@sXLLhtGx_)YtZ&Y==T1hq3M#Y@(;ty$Bd#lRqs1`+=f^`i8cfc zJm3Bio~l(QoUk;DG(o|no(i`nO|z!cQBJPZ?oS!pM5nJJGs&t2Up|+bOpU>;`L3T` zyL2#R3$l6J?^4z{u8`cWQBfF zlhZSGwZs}kmW{`P8J8AhuAR~QZFTjm>s!Rj#Q$mA^Qgb0q};1dH2?QtqgNFF9PDnR ztrD&oSB7uud6;P2^YPKbIx1xwxuhWCEfIW7^ab940z$dj{xb*J}?pmbAAG`n()R zoO%?k2`HK}F5X^4gKV;(E-Usk6;&jt_j#QWsk&iTZ`BvZ+awB|4=CZQn$nZ(zrg>utrW*B@Fx`(r{F9uPYPyVZ0kegUslAn^Fr&pYxoSB%z36ynl t3~{wJGB7k319F`GeSKa1LTt10i&N9GQ}gmF^^)^*8CiiwZ(QCg3;mViD9bhj0_A6@<5!LmzVS-kOUmoNYl{SuA< From ec487166db4d9d532e6090c76b65c797780fa841 Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Fri, 27 Oct 2017 10:06:54 -0700 Subject: [PATCH 04/89] Directly use if not reducing motion (#5546) --- .../features/ui/util/optional_motion.js | 50 ++----------------- .../features/ui/util/reduced_motion.js | 44 ++++++++++++++++ 2 files changed, 47 insertions(+), 47 deletions(-) create mode 100644 app/javascript/mastodon/features/ui/util/reduced_motion.js diff --git a/app/javascript/mastodon/features/ui/util/optional_motion.js b/app/javascript/mastodon/features/ui/util/optional_motion.js index 82edbbe8f..df3a8b54a 100644 --- a/app/javascript/mastodon/features/ui/util/optional_motion.js +++ b/app/javascript/mastodon/features/ui/util/optional_motion.js @@ -1,49 +1,5 @@ -// Like react-motion's Motion, but checks to see if the user prefers -// reduced motion and uses a cross-fade in those cases. - -import React from 'react'; -import Motion from 'react-motion/lib/Motion'; -import PropTypes from 'prop-types'; import { reduceMotion } from '../../../initial_state'; +import ReducedMotion from './reduced_motion'; +import Motion from 'react-motion/lib/Motion'; -const stylesToKeep = ['opacity', 'backgroundOpacity']; - -const extractValue = (value) => { - // 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; -}; - -class OptionalMotion extends React.Component { - - static propTypes = { - defaultStyle: PropTypes.object, - style: PropTypes.object, - children: PropTypes.func, - } - - render() { - - const { style, defaultStyle, children } = this.props; - - if (reduceMotion) { - Object.keys(style).forEach(key => { - if (stylesToKeep.includes(key)) { - return; - } - // If it's setting an x or height or scale or some other value, we need - // to preserve the end-state value without actually animating it - style[key] = defaultStyle[key] = extractValue(style[key]); - }); - } - - return ( - - {children} - - ); - } - -} - - -export default OptionalMotion; +export default reduceMotion ? ReducedMotion : Motion; diff --git a/app/javascript/mastodon/features/ui/util/reduced_motion.js b/app/javascript/mastodon/features/ui/util/reduced_motion.js new file mode 100644 index 000000000..95519042b --- /dev/null +++ b/app/javascript/mastodon/features/ui/util/reduced_motion.js @@ -0,0 +1,44 @@ +// Like react-motion's Motion, but reduces all animations to cross-fades +// for the benefit of users with motion sickness. +import React from 'react'; +import Motion from 'react-motion/lib/Motion'; +import PropTypes from 'prop-types'; + +const stylesToKeep = ['opacity', 'backgroundOpacity']; + +const extractValue = (value) => { + // 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; +}; + +class ReducedMotion extends React.Component { + + static propTypes = { + defaultStyle: PropTypes.object, + style: PropTypes.object, + children: PropTypes.func, + } + + render() { + + const { style, defaultStyle, children } = this.props; + + Object.keys(style).forEach(key => { + if (stylesToKeep.includes(key)) { + return; + } + // If it's setting an x or height or scale or some other value, we need + // to preserve the end-state value without actually animating it + style[key] = defaultStyle[key] = extractValue(style[key]); + }); + + return ( + + {children} + + ); + } + +} + +export default ReducedMotion; From e843f62f479d9b8b2d177e587c3e10b5e3945f68 Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Fri, 27 Oct 2017 10:08:07 -0700 Subject: [PATCH 05/89] Avoid unnecessary Motion components in icon_button.js (#5544) --- .../mastodon/components/icon_button.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/app/javascript/mastodon/components/icon_button.js b/app/javascript/mastodon/components/icon_button.js index d8e445cef..06f53841d 100644 --- a/app/javascript/mastodon/components/icon_button.js +++ b/app/javascript/mastodon/components/icon_button.js @@ -72,6 +72,25 @@ export default class IconButton extends React.PureComponent { overlayed: overlay, }); + if (!animate) { + // Perf optimization: avoid unnecessary components unless + // we actually need to animate. + return ( +