Explorar el Código

Add option to reduce motion (#5393)

* 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
Nolan Lawson hace 6 años
padre
commit
fa0be3f834

+ 1 - 0
app/controllers/settings/preferences_controller.rb

@@ -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,

+ 1 - 1
app/javascript/mastodon/components/collapsable.js

@@ -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';
 

+ 1 - 1
app/javascript/mastodon/components/dropdown_menu.js

@@ -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';
 

+ 1 - 1
app/javascript/mastodon/components/icon_button.js

@@ -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';

+ 1 - 1
app/javascript/mastodon/features/account/components/header.js

@@ -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';

+ 1 - 1
app/javascript/mastodon/features/compose/components/privacy_dropdown.js

@@ -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';

+ 1 - 1
app/javascript/mastodon/features/compose/components/search.js

@@ -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({

+ 1 - 1
app/javascript/mastodon/features/compose/components/upload.js

@@ -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';

+ 1 - 1
app/javascript/mastodon/features/compose/components/upload_progress.js

@@ -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';
 

+ 1 - 1
app/javascript/mastodon/features/compose/components/warning.js

@@ -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 {

+ 1 - 1
app/javascript/mastodon/features/compose/containers/sensitive_button_container.js

@@ -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';
 

+ 1 - 1
app/javascript/mastodon/features/compose/index.js

@@ -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';

+ 1 - 1
app/javascript/mastodon/features/ui/components/upload_area.js

@@ -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';
 

+ 34 - 0
app/javascript/mastodon/features/ui/util/optional_motion.js

@@ -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);

+ 5 - 0
app/lib/user_settings_decorator.rb

@@ -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

+ 4 - 0
app/models/user.rb

@@ -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

+ 1 - 0
app/serializers/initial_state_serializer.rb

@@ -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

+ 1 - 0
app/views/settings/preferences/show.html.haml

@@ -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

+ 1 - 1
app/views/stream_entries/_detailed_status.html.haml

@@ -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) }}
 

+ 1 - 0
config/locales/simple_form.en.yml

@@ -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

+ 1 - 0
config/settings.yml

@@ -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'