about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/components/components/media_gallery.jsx36
-rw-r--r--app/assets/javascripts/components/components/status.jsx3
-rw-r--r--app/assets/javascripts/components/containers/status_container.jsx3
-rw-r--r--app/assets/javascripts/components/features/account/components/header.jsx23
-rw-r--r--app/assets/javascripts/components/features/status/components/detailed_status.jsx3
-rw-r--r--app/assets/javascripts/components/features/status/index.jsx10
-rw-r--r--app/controllers/settings/preferences_controller.rb5
-rw-r--r--app/models/user.rb4
-rw-r--r--app/views/home/initial_state.json.rabl1
-rw-r--r--app/views/settings/preferences/show.html.haml3
-rw-r--r--config/locales/simple_form.en.yml1
-rw-r--r--config/settings.yml1
12 files changed, 65 insertions, 28 deletions
diff --git a/app/assets/javascripts/components/components/media_gallery.jsx b/app/assets/javascripts/components/components/media_gallery.jsx
index 325fd8157..c6c726a4e 100644
--- a/app/assets/javascripts/components/components/media_gallery.jsx
+++ b/app/assets/javascripts/components/components/media_gallery.jsx
@@ -78,7 +78,8 @@ const Item = React.createClass({
     attachment: ImmutablePropTypes.map.isRequired,
     index: React.PropTypes.number.isRequired,
     size: React.PropTypes.number.isRequired,
-    onClick: React.PropTypes.func.isRequired
+    onClick: React.PropTypes.func.isRequired,
+    autoPlayGif: React.PropTypes.bool.isRequired
   },
 
   mixins: [PureRenderMixin],
@@ -158,16 +159,24 @@ const Item = React.createClass({
         />
       );
     } else if (attachment.get('type') === 'gifv') {
-      thumbnail = (
-        <video
-          src={attachment.get('url')}
-          onClick={this.handleClick}
-          autoPlay={!isIOS()}
-          loop={true}
-          muted={true}
-          style={gifvThumbStyle}
-        />
-      );
+      if (isIOS() || !this.props.autoPlayGif) {
+        return (
+          <div  key={attachment.get('id')} style={{ ...itemStyle, background: `url(${attachment.get('preview_url')}) no-repeat center`, left: left, top: top, right: right, bottom: bottom, width: `${width}%`, height: `${height}%` }} onClick={this.handleClick}>
+            <div style={{ position: 'absolute', top: '50%', left: '50%', fontSize: '36px', transform: 'translate(-50%, -50%)', padding: '5px', borderRadius: '100px', color: 'rgba(255, 255, 255, 0.8)' }}><i className='fa fa-play' /></div>
+          </div>
+        );
+      } else {
+        thumbnail = (
+            <video
+              src={attachment.get('url')}
+              onClick={this.handleClick}
+              autoPlay
+              loop={true}
+              muted={true}
+              style={gifvThumbStyle}
+            />
+        );
+      }
     }
 
     return (
@@ -192,7 +201,8 @@ const MediaGallery = React.createClass({
     media: ImmutablePropTypes.list.isRequired,
     height: React.PropTypes.number.isRequired,
     onOpenMedia: React.PropTypes.func.isRequired,
-    intl: React.PropTypes.object.isRequired
+    intl: React.PropTypes.object.isRequired,
+    autoPlayGif: React.PropTypes.bool.isRequired
   },
 
   mixins: [PureRenderMixin],
@@ -227,7 +237,7 @@ const MediaGallery = React.createClass({
       );
     } else {
       const size = media.take(4).size;
-      children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} index={i} size={size} />);
+      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} />);
     }
 
     return (
diff --git a/app/assets/javascripts/components/components/status.jsx b/app/assets/javascripts/components/components/status.jsx
index d2d2aaf20..abc123f26 100644
--- a/app/assets/javascripts/components/components/status.jsx
+++ b/app/assets/javascripts/components/components/status.jsx
@@ -29,6 +29,7 @@ const Status = React.createClass({
     onBlock: React.PropTypes.func,
     me: React.PropTypes.number,
     boostModal: React.PropTypes.bool,
+    autoPlayGif: React.PropTypes.bool,
     muted: React.PropTypes.bool
   },
 
@@ -79,7 +80,7 @@ const Status = React.createClass({
       if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
         media = <VideoPlayer media={status.getIn(['media_attachments', 0])} sensitive={status.get('sensitive')} onOpenVideo={this.props.onOpenVideo} />;
       } else {
-        media = <MediaGallery media={status.get('media_attachments')} sensitive={status.get('sensitive')} height={110} onOpenMedia={this.props.onOpenMedia} />;
+        media = <MediaGallery media={status.get('media_attachments')} sensitive={status.get('sensitive')} height={110} onOpenMedia={this.props.onOpenMedia} autoPlayGif={this.props.autoPlayGif} />;
       }
     }
 
diff --git a/app/assets/javascripts/components/containers/status_container.jsx b/app/assets/javascripts/components/containers/status_container.jsx
index f704ac722..df091de04 100644
--- a/app/assets/javascripts/components/containers/status_container.jsx
+++ b/app/assets/javascripts/components/containers/status_container.jsx
@@ -27,7 +27,8 @@ const makeMapStateToProps = () => {
   const mapStateToProps = (state, props) => ({
     status: getStatus(state, props.id),
     me: state.getIn(['meta', 'me']),
-    boostModal: state.getIn(['meta', 'boost_modal'])
+    boostModal: state.getIn(['meta', 'boost_modal']),
+    autoPlayGif: state.getIn(['meta', 'auto_play_gif'])
   });
 
   return mapStateToProps;
diff --git a/app/assets/javascripts/components/features/account/components/header.jsx b/app/assets/javascripts/components/features/account/components/header.jsx
index c4619a3c7..c097fbbd6 100644
--- a/app/assets/javascripts/components/features/account/components/header.jsx
+++ b/app/assets/javascripts/components/features/account/components/header.jsx
@@ -5,6 +5,7 @@ import escapeTextContentForBrowser from 'escape-html';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import IconButton from '../../../components/icon_button';
 import { Motion, spring } from 'react-motion';
+import { connect } from 'react-redux';
 
 const messages = defineMessages({
   unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
@@ -12,10 +13,19 @@ const messages = defineMessages({
   requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' }
 });
 
+const makeMapStateToProps = () => {
+  const mapStateToProps = (state, props) => ({
+    autoPlayGif: state.getIn(['meta', 'auto_play_gif'])
+  });
+
+  return mapStateToProps;
+};
+
 const Avatar = React.createClass({
 
   propTypes: {
-    account: ImmutablePropTypes.map.isRequired
+    account: ImmutablePropTypes.map.isRequired,
+    autoPlayGif: React.PropTypes.bool.isRequired
   },
 
   getInitialState () {
@@ -37,7 +47,7 @@ const Avatar = React.createClass({
   },
 
   render () {
-    const { account }   = this.props;
+    const { account, autoPlayGif }   = this.props;
     const { isHovered } = this.state;
 
     return (
@@ -53,7 +63,7 @@ const Avatar = React.createClass({
             onMouseOut={this.handleMouseOut}
             onFocus={this.handleMouseOver}
             onBlur={this.handleMouseOut}>
-            <img src={account.get('avatar')} alt={account.get('acct')} style={{ display: 'block', width: '90px', height: '90px' }} />
+            <img src={autoPlayGif || isHovered ? account.get('avatar') : account.get('avatar_static')} alt={account.get('acct')} style={{ display: 'block', width: '90px', height: '90px' }} />
           </a>
         }
       </Motion>
@@ -68,7 +78,8 @@ const Header = React.createClass({
     account: ImmutablePropTypes.map,
     me: React.PropTypes.number.isRequired,
     onFollow: React.PropTypes.func.isRequired,
-    intl: React.PropTypes.object.isRequired
+    intl: React.PropTypes.object.isRequired,
+    autoPlayGif: React.PropTypes.bool.isRequired
   },
 
   mixins: [PureRenderMixin],
@@ -119,7 +130,7 @@ const Header = React.createClass({
     return (
       <div className='account__header' style={{ backgroundImage: `url(${account.get('header')})` }}>
         <div style={{ padding: '20px 10px' }}>
-          <Avatar account={account} />
+          <Avatar account={account} autoPlayGif={this.props.autoPlayGif} />
 
           <span style={{ display: 'inline-block', fontSize: '20px', lineHeight: '27px', fontWeight: '500' }} className='account__header__display-name' dangerouslySetInnerHTML={displayNameHTML} />
           <span className='account__header__username' style={{ fontSize: '14px', fontWeight: '400', display: 'block', marginBottom: '10px' }}>@{account.get('acct')} {lockedIcon}</span>
@@ -134,4 +145,4 @@ const Header = React.createClass({
 
 });
 
-export default injectIntl(Header);
+export default connect(makeMapStateToProps)(injectIntl(Header));
diff --git a/app/assets/javascripts/components/features/status/components/detailed_status.jsx b/app/assets/javascripts/components/features/status/components/detailed_status.jsx
index ceafc1a32..bd386b251 100644
--- a/app/assets/javascripts/components/features/status/components/detailed_status.jsx
+++ b/app/assets/javascripts/components/features/status/components/detailed_status.jsx
@@ -19,6 +19,7 @@ const DetailedStatus = React.createClass({
     status: ImmutablePropTypes.map.isRequired,
     onOpenMedia: React.PropTypes.func.isRequired,
     onOpenVideo: React.PropTypes.func.isRequired,
+    autoPlayGif: React.PropTypes.bool,
   },
 
   mixins: [PureRenderMixin],
@@ -42,7 +43,7 @@ const DetailedStatus = React.createClass({
       if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
         media = <VideoPlayer sensitive={status.get('sensitive')} media={status.getIn(['media_attachments', 0])} width={300} height={150} onOpenVideo={this.props.onOpenVideo} autoplay />;
       } else {
-        media = <MediaGallery sensitive={status.get('sensitive')} media={status.get('media_attachments')} height={300} onOpenMedia={this.props.onOpenMedia} />;
+        media = <MediaGallery sensitive={status.get('sensitive')} media={status.get('media_attachments')} height={300} onOpenMedia={this.props.onOpenMedia} autoPlayGif={this.props.autoPlayGif} />;
       }
     } else {
       media = <CardContainer statusId={status.get('id')} />;
diff --git a/app/assets/javascripts/components/features/status/index.jsx b/app/assets/javascripts/components/features/status/index.jsx
index 7ead68807..ca6e08cdc 100644
--- a/app/assets/javascripts/components/features/status/index.jsx
+++ b/app/assets/javascripts/components/features/status/index.jsx
@@ -39,7 +39,8 @@ const makeMapStateToProps = () => {
     ancestorsIds: state.getIn(['timelines', 'ancestors', Number(props.params.statusId)]),
     descendantsIds: state.getIn(['timelines', 'descendants', Number(props.params.statusId)]),
     me: state.getIn(['meta', 'me']),
-    boostModal: state.getIn(['meta', 'boost_modal'])
+    boostModal: state.getIn(['meta', 'boost_modal']),
+    autoPlayGif: state.getIn(['meta', 'auto_play_gif'])
   });
 
   return mapStateToProps;
@@ -57,7 +58,8 @@ const Status = React.createClass({
     ancestorsIds: ImmutablePropTypes.list,
     descendantsIds: ImmutablePropTypes.list,
     me: React.PropTypes.number,
-    boostModal: React.PropTypes.bool
+    boostModal: React.PropTypes.bool,
+    autoPlayGif: React.PropTypes.bool
   },
 
   mixins: [PureRenderMixin],
@@ -126,7 +128,7 @@ const Status = React.createClass({
 
   render () {
     let ancestors, descendants;
-    const { status, ancestorsIds, descendantsIds, me } = this.props;
+    const { status, ancestorsIds, descendantsIds, me, autoPlayGif } = this.props;
 
     if (status === null) {
       return (
@@ -155,7 +157,7 @@ const Status = React.createClass({
           <div className='scrollable'>
             {ancestors}
 
-            <DetailedStatus status={status} me={me} onOpenVideo={this.handleOpenVideo} onOpenMedia={this.handleOpenMedia} />
+            <DetailedStatus status={status} autoPlayGif={autoPlayGif} me={me} onOpenVideo={this.handleOpenVideo} onOpenMedia={this.handleOpenMedia} />
             <ActionBar status={status} me={me} onReply={this.handleReplyClick} onFavourite={this.handleFavouriteClick} onReblog={this.handleReblogClick} onDelete={this.handleDeleteClick} onMention={this.handleMentionClick} onReport={this.handleReport} />
 
             {descendants}
diff --git a/app/controllers/settings/preferences_controller.rb b/app/controllers/settings/preferences_controller.rb
index c758e4ef2..f66eb9752 100644
--- a/app/controllers/settings/preferences_controller.rb
+++ b/app/controllers/settings/preferences_controller.rb
@@ -24,8 +24,9 @@ class Settings::PreferencesController < ApplicationController
 
     current_user.settings['default_privacy'] = user_params[:setting_default_privacy]
     current_user.settings['boost_modal'] = user_params[:setting_boost_modal] == '1'
+    current_user.settings['auto_play_gif'] = user_params[:setting_auto_play_gif] == '1'
 
-    if current_user.update(user_params.except(:notification_emails, :interactions, :setting_default_privacy, :setting_boost_modal))
+    if current_user.update(user_params.except(:notification_emails, :interactions, :setting_default_privacy, :setting_boost_modal, :setting_auto_play_gif))
       redirect_to settings_preferences_path, notice: I18n.t('generic.changes_saved_msg')
     else
       render action: :show
@@ -35,6 +36,6 @@ class Settings::PreferencesController < ApplicationController
   private
 
   def user_params
-    params.require(:user).permit(:locale, :setting_default_privacy, :setting_boost_modal, notification_emails: [:follow, :follow_request, :reblog, :favourite, :mention, :digest], interactions: [:must_be_follower, :must_be_following])
+    params.require(:user).permit(:locale, :setting_default_privacy, :setting_boost_modal, :setting_auto_play_gif, notification_emails: [:follow, :follow_request, :reblog, :favourite, :mention, :digest], interactions: [:must_be_follower, :must_be_following])
   end
 end
diff --git a/app/models/user.rb b/app/models/user.rb
index 27a38674e..d50101baf 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -32,4 +32,8 @@ class User < ApplicationRecord
   def setting_boost_modal
     settings.boost_modal
   end
+
+  def setting_auto_play_gif
+    settings.auto_play_gif
+  end
 end
diff --git a/app/views/home/initial_state.json.rabl b/app/views/home/initial_state.json.rabl
index ce7bfbd44..a2ab2d060 100644
--- a/app/views/home/initial_state.json.rabl
+++ b/app/views/home/initial_state.json.rabl
@@ -9,6 +9,7 @@ node(:meta) do
     me: current_account.id,
     admin: @admin.try(:id),
     boost_modal: current_account.user.setting_boost_modal,
+    auto_play_gif: current_account.user.setting_auto_play_gif,
   }
 end
 
diff --git a/app/views/settings/preferences/show.html.haml b/app/views/settings/preferences/show.html.haml
index e819429b6..3fdcca041 100644
--- a/app/views/settings/preferences/show.html.haml
+++ b/app/views/settings/preferences/show.html.haml
@@ -25,5 +25,8 @@
   .fields-group
     = f.input :setting_boost_modal, as: :boolean, wrapper: :with_label
 
+  .fields-group
+    = f.input :setting_auto_play_gif, as: :boolean, wrapper: :with_label
+
   .actions
     = f.button :button, t('generic.save_changes'), type: :submit
diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml
index c25407f2b..5335b0927 100644
--- a/config/locales/simple_form.en.yml
+++ b/config/locales/simple_form.en.yml
@@ -28,6 +28,7 @@ en:
         note: Bio
         otp_attempt: Two-factor code
         password: Password
+        setting_auto_play_gif: Auto-play animated GIFs
         setting_boost_modal: Show confirmation dialog before boosting
         setting_default_privacy: Post privacy
         severity: Severity
diff --git a/config/settings.yml b/config/settings.yml
index 04213fd0b..9813963b2 100644
--- a/config/settings.yml
+++ b/config/settings.yml
@@ -15,6 +15,7 @@ defaults: &defaults
   open_registrations: true
   closed_registrations_message: ''
   boost_modal: false
+  auto_play_gif: true
   notification_emails:
     follow: false
     reblog: false