about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--app/controllers/settings/preferences_controller.rb1
-rw-r--r--app/javascript/mastodon/components/collapsable.js2
-rw-r--r--app/javascript/mastodon/components/dropdown_menu.js2
-rw-r--r--app/javascript/mastodon/components/icon_button.js2
-rw-r--r--app/javascript/mastodon/features/account/components/header.js2
-rw-r--r--app/javascript/mastodon/features/compose/components/privacy_dropdown.js2
-rw-r--r--app/javascript/mastodon/features/compose/components/search.js2
-rw-r--r--app/javascript/mastodon/features/compose/components/upload.js2
-rw-r--r--app/javascript/mastodon/features/compose/components/upload_progress.js2
-rw-r--r--app/javascript/mastodon/features/compose/components/warning.js2
-rw-r--r--app/javascript/mastodon/features/compose/containers/sensitive_button_container.js2
-rw-r--r--app/javascript/mastodon/features/compose/index.js2
-rw-r--r--app/javascript/mastodon/features/ui/components/upload_area.js2
-rw-r--r--app/javascript/mastodon/features/ui/util/optional_motion.js34
-rw-r--r--app/lib/user_settings_decorator.rb5
-rw-r--r--app/models/user.rb4
-rw-r--r--app/serializers/initial_state_serializer.rb1
-rw-r--r--app/views/settings/preferences/show.html.haml1
-rw-r--r--app/views/stream_entries/_detailed_status.html.haml2
-rw-r--r--config/locales/simple_form.en.yml1
-rw-r--r--config/settings.yml1
21 files changed, 61 insertions, 13 deletions
diff --git a/app/controllers/settings/preferences_controller.rb b/app/controllers/settings/preferences_controller.rb
index 207c7b324..069026715 100644
--- a/app/controllers/settings/preferences_controller.rb
+++ b/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,
diff --git a/app/javascript/mastodon/components/collapsable.js b/app/javascript/mastodon/components/collapsable.js
index ad1453589..42ea37ec2 100644
--- a/app/javascript/mastodon/components/collapsable.js
+++ b/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';
 
diff --git a/app/javascript/mastodon/components/dropdown_menu.js b/app/javascript/mastodon/components/dropdown_menu.js
index 73ad46bb7..3a3ebf487 100644
--- a/app/javascript/mastodon/components/dropdown_menu.js
+++ b/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';
 
diff --git a/app/javascript/mastodon/components/icon_button.js b/app/javascript/mastodon/components/icon_button.js
index 68d1a2735..d8e445cef 100644
--- a/app/javascript/mastodon/components/icon_button.js
+++ b/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';
diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js
index 9ee7a56d9..07a6c5dec 100644
--- a/app/javascript/mastodon/features/account/components/header.js
+++ b/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';
diff --git a/app/javascript/mastodon/features/compose/components/privacy_dropdown.js b/app/javascript/mastodon/features/compose/components/privacy_dropdown.js
index e38ed38c1..c1e85aee3 100644
--- a/app/javascript/mastodon/features/compose/components/privacy_dropdown.js
+++ b/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';
diff --git a/app/javascript/mastodon/features/compose/components/search.js b/app/javascript/mastodon/features/compose/components/search.js
index f57d54618..398fc44ce 100644
--- a/app/javascript/mastodon/features/compose/components/search.js
+++ b/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({
diff --git a/app/javascript/mastodon/features/compose/components/upload.js b/app/javascript/mastodon/features/compose/components/upload.js
index cd9e08360..5d8d66cf7 100644
--- a/app/javascript/mastodon/features/compose/components/upload.js
+++ b/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';
diff --git a/app/javascript/mastodon/features/compose/components/upload_progress.js b/app/javascript/mastodon/features/compose/components/upload_progress.js
index 3e49098c7..d5e6f19cd 100644
--- a/app/javascript/mastodon/features/compose/components/upload_progress.js
+++ b/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';
 
diff --git a/app/javascript/mastodon/features/compose/components/warning.js b/app/javascript/mastodon/features/compose/components/warning.js
index a0814e984..803b7f86a 100644
--- a/app/javascript/mastodon/features/compose/components/warning.js
+++ b/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 {
diff --git a/app/javascript/mastodon/features/compose/containers/sensitive_button_container.js b/app/javascript/mastodon/features/compose/containers/sensitive_button_container.js
index 8624849f3..e4bd5a743 100644
--- a/app/javascript/mastodon/features/compose/containers/sensitive_button_container.js
+++ b/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';
 
diff --git a/app/javascript/mastodon/features/compose/index.js b/app/javascript/mastodon/features/compose/index.js
index 6166fce3c..0c66585c9 100644
--- a/app/javascript/mastodon/features/compose/index.js
+++ b/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';
diff --git a/app/javascript/mastodon/features/ui/components/upload_area.js b/app/javascript/mastodon/features/ui/components/upload_area.js
index dda28feeb..c19065be6 100644
--- a/app/javascript/mastodon/features/ui/components/upload_area.js
+++ b/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';
 
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
index 000000000..4276eeaa4
--- /dev/null
+++ b/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);
diff --git a/app/lib/user_settings_decorator.rb b/app/lib/user_settings_decorator.rb
index cd4cf4b32..d48e1da65 100644
--- a/app/lib/user_settings_decorator.rb
+++ b/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
diff --git a/app/models/user.rb b/app/models/user.rb
index 3bf069a31..325e27f44 100644
--- a/app/models/user.rb
+++ b/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
diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb
index e2f15a601..a8db73161 100644
--- a/app/serializers/initial_state_serializer.rb
+++ b/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
diff --git a/app/views/settings/preferences/show.html.haml b/app/views/settings/preferences/show.html.haml
index 7475e3fd2..69e26a7be 100644
--- a/app/views/settings/preferences/show.html.haml
+++ b/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
diff --git a/app/views/stream_entries/_detailed_status.html.haml b/app/views/stream_entries/_detailed_status.html.haml
index fa9ccd1f0..ceb796743 100644
--- a/app/views/stream_entries/_detailed_status.html.haml
+++ b/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) }}
 
diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml
index 86c80290c..5a58e8667 100644
--- a/config/locales/simple_form.en.yml
+++ b/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
diff --git a/config/settings.yml b/config/settings.yml
index c437b4ccb..11681d7ec 100644
--- a/config/settings.yml
+++ b/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'