]> cat aescling's git repositories - mastodon.git/commitdiff
Add option to reduce motion (#5393)
authorNolan Lawson <nolan@nolanlawson.com>
Mon, 16 Oct 2017 07:36:15 +0000 (00:36 -0700)
committerEugen Rochko <eugen@zeonfederated.com>
Mon, 16 Oct 2017 07:36:15 +0000 (09:36 +0200)
* Add option to reduce motion

* Use HOC to wrap all Motion calls

* fix case-sensitive issue

* Avoid updating too frequently

* Get rid of unnecessary change to _simple_status.html.haml

21 files changed:
app/controllers/settings/preferences_controller.rb
app/javascript/mastodon/components/collapsable.js
app/javascript/mastodon/components/dropdown_menu.js
app/javascript/mastodon/components/icon_button.js
app/javascript/mastodon/features/account/components/header.js
app/javascript/mastodon/features/compose/components/privacy_dropdown.js
app/javascript/mastodon/features/compose/components/search.js
app/javascript/mastodon/features/compose/components/upload.js
app/javascript/mastodon/features/compose/components/upload_progress.js
app/javascript/mastodon/features/compose/components/warning.js
app/javascript/mastodon/features/compose/containers/sensitive_button_container.js
app/javascript/mastodon/features/compose/index.js
app/javascript/mastodon/features/ui/components/upload_area.js
app/javascript/mastodon/features/ui/util/optional_motion.js [new file with mode: 0644]
app/lib/user_settings_decorator.rb
app/models/user.rb
app/serializers/initial_state_serializer.rb
app/views/settings/preferences/show.html.haml
app/views/stream_entries/_detailed_status.html.haml
config/locales/simple_form.en.yml
config/settings.yml

index 207c7b3240b4c5b9a78c8a933c26554045a15116..0690267151c958864249603d469c389ca5116f19 100644 (file)
@@ -39,6 +39,7 @@ class Settings::PreferencesController < ApplicationController
       :setting_boost_modal,
       :setting_delete_modal,
       :setting_auto_play_gif,
+      :setting_reduce_motion,
       :setting_system_font_ui,
       :setting_noindex,
       :setting_theme,
index ad1453589ca457535c00d37bc9203b1ac0b10d6a..42ea37ec229cc2bb25426c7110142787e24ce370 100644 (file)
@@ -1,5 +1,5 @@
 import React from 'react';
-import Motion from 'react-motion/lib/Motion';
+import Motion from '../features/ui/util/optional_motion';
 import spring from 'react-motion/lib/spring';
 import PropTypes from 'prop-types';
 
index 73ad46bb7135f8d95e9146e434af20ddd9eeb532..3a3ebf4873d14b616acf35b5ebb528e770dfe9a9 100644 (file)
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import IconButton from './icon_button';
 import Overlay from 'react-overlays/lib/Overlay';
-import Motion from 'react-motion/lib/Motion';
+import Motion from '../features/ui/util/optional_motion';
 import spring from 'react-motion/lib/spring';
 import detectPassiveEvents from 'detect-passive-events';
 
index 68d1a2735c82068a5d33aefabddb363057bb1bd5..d8e445cefc8fc741cea84ea902d47d87456bb8a9 100644 (file)
@@ -1,5 +1,5 @@
 import React from 'react';
-import Motion from 'react-motion/lib/Motion';
+import Motion from '../features/ui/util/optional_motion';
 import spring from 'react-motion/lib/spring';
 import PropTypes from 'prop-types';
 import classNames from 'classnames';
index 9ee7a56d945a893c36cd975c2810bc0a80df3bb4..07a6c5dec65f6e601e82099db0545911540eec7b 100644 (file)
@@ -3,7 +3,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
 import PropTypes from 'prop-types';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import IconButton from '../../../components/icon_button';
-import Motion from 'react-motion/lib/Motion';
+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';
index e38ed38c1d72d428f61fac652142fb14f5f959c9..c1e85aee3afd1f72cd797390904163828bbcabc9 100644 (file)
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
 import { injectIntl, defineMessages } from 'react-intl';
 import IconButton from '../../../components/icon_button';
 import Overlay from 'react-overlays/lib/Overlay';
-import Motion from 'react-motion/lib/Motion';
+import Motion from '../../ui/util/optional_motion';
 import spring from 'react-motion/lib/spring';
 import detectPassiveEvents from 'detect-passive-events';
 import classNames from 'classnames';
index f57d54618de20f0031f4f68db3a39deae7f5aabb..398fc44ceebc25aa14f305cb8dbf287a9d7f3094 100644 (file)
@@ -2,7 +2,7 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import Overlay from 'react-overlays/lib/Overlay';
-import Motion from 'react-motion/lib/Motion';
+import Motion from '../../ui/util/optional_motion';
 import spring from 'react-motion/lib/spring';
 
 const messages = defineMessages({
index cd9e08360f28dd92b9bb0c29f647415f6ca2361f..5d8d66cf76a5da091cafcb80c045a2297bab5cd9 100644 (file)
@@ -2,7 +2,7 @@ import React from 'react';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import PropTypes from 'prop-types';
 import IconButton from '../../../components/icon_button';
-import Motion from 'react-motion/lib/Motion';
+import Motion from '../../ui/util/optional_motion';
 import spring from 'react-motion/lib/spring';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import { defineMessages, injectIntl } from 'react-intl';
index 3e49098c72cca94bc189c19a3beb0c0f8f6c370a..d5e6f19cda78e2eaa051c925a66ecbca9fcc3e40 100644 (file)
@@ -1,6 +1,6 @@
 import React from 'react';
 import PropTypes from 'prop-types';
-import Motion from 'react-motion/lib/Motion';
+import Motion from '../../ui/util/optional_motion';
 import spring from 'react-motion/lib/spring';
 import { FormattedMessage } from 'react-intl';
 
index a0814e984b15bdb8c14c581e51f0d233dcf368a0..803b7f86abd2d949a63ee502a7983d237a4dd17e 100644 (file)
@@ -1,6 +1,6 @@
 import React from 'react';
 import PropTypes from 'prop-types';
-import Motion from 'react-motion/lib/Motion';
+import Motion from '../../ui/util/optional_motion';
 import spring from 'react-motion/lib/spring';
 
 export default class Warning extends React.PureComponent {
index 8624849f3d6b74582f026eb2d2386b4348f3e5a1..e4bd5a743147b50ee3bb2440053336e7501781c9 100644 (file)
@@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
 import classNames from 'classnames';
 import IconButton from '../../../components/icon_button';
 import { changeComposeSensitivity } from '../../../actions/compose';
-import Motion from 'react-motion/lib/Motion';
+import Motion from '../../ui/util/optional_motion';
 import spring from 'react-motion/lib/spring';
 import { injectIntl, defineMessages } from 'react-intl';
 
index 6166fce3cbd34f951a8ffd09f1f1d61a95c690cc..0c66585c98d72a70a0f45158ed779ff3cf39033b 100644 (file)
@@ -8,7 +8,7 @@ import { mountCompose, unmountCompose } from '../../actions/compose';
 import { Link } from 'react-router-dom';
 import { injectIntl, defineMessages } from 'react-intl';
 import SearchContainer from './containers/search_container';
-import Motion from 'react-motion/lib/Motion';
+import Motion from '../ui/util/optional_motion';
 import spring from 'react-motion/lib/spring';
 import SearchResultsContainer from './containers/search_results_container';
 import { changeComposing } from '../../actions/compose';
index dda28feeb74096e8595f112245d0ff53f5657902..c19065be64e71fb517a04c754cc3695bbcf9b6cb 100644 (file)
@@ -1,6 +1,6 @@
 import React from 'react';
 import PropTypes from 'prop-types';
-import Motion from 'react-motion/lib/Motion';
+import Motion from '../../ui/util/optional_motion';
 import spring from 'react-motion/lib/spring';
 import { FormattedMessage } from 'react-intl';
 
diff --git a/app/javascript/mastodon/features/ui/util/optional_motion.js b/app/javascript/mastodon/features/ui/util/optional_motion.js
new file mode 100644 (file)
index 0000000..4276eea
--- /dev/null
@@ -0,0 +1,34 @@
+// Like react-motion's Motion, but checks to see if the user prefers
+// reduced motion and uses a cross-fade in those cases.
+
+import Motion from 'react-motion/lib/Motion';
+import { connect } from 'react-redux';
+
+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;
+};
+
+const mapStateToProps = (state, ownProps) => {
+  const reduceMotion = state.getIn(['meta', 'reduce_motion']);
+
+  if (reduceMotion) {
+    const { style, defaultStyle } = ownProps;
+
+    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 { style, defaultStyle };
+  }
+  return {};
+};
+
+export default connect(mapStateToProps)(Motion);
index cd4cf4b32404ea35b17b4854bb923a25dfda1411..d48e1da65a7a0b4f02fb42973b8250e23a336af2 100644 (file)
@@ -23,6 +23,7 @@ class UserSettingsDecorator
     user.settings['boost_modal']         = boost_modal_preference if change?('setting_boost_modal')
     user.settings['delete_modal']        = delete_modal_preference if change?('setting_delete_modal')
     user.settings['auto_play_gif']       = auto_play_gif_preference if change?('setting_auto_play_gif')
+    user.settings['reduce_motion']       = reduce_motion_preference if change?('setting_reduce_motion')
     user.settings['system_font_ui']      = system_font_ui_preference if change?('setting_system_font_ui')
     user.settings['noindex']             = noindex_preference if change?('setting_noindex')
     user.settings['theme']               = theme_preference if change?('setting_theme')
@@ -64,6 +65,10 @@ class UserSettingsDecorator
     boolean_cast_setting 'setting_auto_play_gif'
   end
 
+  def reduce_motion_preference
+    boolean_cast_setting 'setting_reduce_motion'
+  end
+
   def noindex_preference
     boolean_cast_setting 'setting_noindex'
   end
index 3bf069a315d78acc267bde09f3b0be3a2a455b9e..325e27f441593f7a0cb06fc086f991fe77fddc99 100644 (file)
@@ -102,6 +102,10 @@ class User < ApplicationRecord
     settings.auto_play_gif
   end
 
+  def setting_reduce_motion
+    settings.reduce_motion
+  end
+
   def setting_system_font_ui
     settings.system_font_ui
   end
index e2f15a60100b5c5f53b2710eb9284badba7b6e24..a8db7316172586f0333823396e3e93299af57f5f 100644 (file)
@@ -25,6 +25,7 @@ class InitialStateSerializer < ActiveModel::Serializer
       store[:boost_modal]    = object.current_account.user.setting_boost_modal
       store[:delete_modal]   = object.current_account.user.setting_delete_modal
       store[:auto_play_gif]  = object.current_account.user.setting_auto_play_gif
+      store[:reduce_motion]  = object.current_account.user.setting_reduce_motion
     end
 
     store
index 7475e3fd260e982cc112b0e412583983590c9481..69e26a7be2cae6fe898008fea0304f8e0f347917 100644 (file)
@@ -35,6 +35,7 @@
 
   .fields-group
     = f.input :setting_auto_play_gif, as: :boolean, wrapper: :with_label
+    = f.input :setting_reduce_motion, as: :boolean, wrapper: :with_label
     = f.input :setting_system_font_ui, as: :boolean, wrapper: :with_label
 
   .actions
index fa9ccd1f084237596fcdfe58b8b7a15938fd1259..ceb79674326dac8a587c7e0350120ef283a916a4 100644 (file)
@@ -24,7 +24,7 @@
       - video = status.media_attachments.first
       %div{ data: { component: 'Video', props: Oj.dump(src: video.file.url(:original), preview: video.file.url(:small), sensitive: status.sensitive?, width: 670, height: 380) }}
     - else
-      %div{ data: { component: 'MediaGallery', props: Oj.dump(height: 380, sensitive: status.sensitive?, standalone: true, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }) }}
+      %div{ data: { component: 'MediaGallery', props: Oj.dump(height: 380, sensitive: status.sensitive?, standalone: true, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, 'reduceMotion': current_account&.user&.setting_reduce_motion, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }) }}
   - elsif status.preview_cards.first
     %div{ data: { component: 'Card', props: Oj.dump('maxDescription': 160, card: ActiveModelSerializers::SerializableResource.new(status.preview_cards.first, serializer: REST::PreviewCardSerializer).as_json) }}
 
index 86c80290ce7dfd54148d5df04c6ba9e54cab8b62..5a58e86675af1e815e31c32c0b17ab3f92b46eb8 100644 (file)
@@ -39,6 +39,7 @@ en:
         otp_attempt: Two-factor code
         password: Password
         setting_auto_play_gif: Auto-play animated GIFs
+        setting_reduce_motion: Reduce motion in animations
         setting_boost_modal: Show confirmation dialog before boosting
         setting_default_privacy: Post privacy
         setting_default_sensitive: Always mark media as sensitive
index c437b4ccbde027de8bdd03b998a17bcb01377997..11681d7ec737f347c0739fe89a200b61f8e0c2f1 100644 (file)
@@ -22,6 +22,7 @@ defaults: &defaults
   boost_modal: false
   delete_modal: true
   auto_play_gif: false
+  reduce_motion: false
   system_font_ui: false
   noindex: false
   theme: 'default'