diff options
-rw-r--r-- | app/assets/javascripts/components/components/media_gallery.jsx | 30 | ||||
-rw-r--r-- | app/assets/javascripts/components/components/status.jsx | 3 | ||||
-rw-r--r-- | app/assets/javascripts/components/containers/status_container.jsx | 3 | ||||
-rw-r--r-- | app/assets/javascripts/components/features/account/components/header.jsx | 28 | ||||
-rw-r--r-- | app/assets/javascripts/components/features/status/components/detailed_status.jsx | 3 | ||||
-rw-r--r-- | app/assets/javascripts/components/features/status/index.jsx | 10 | ||||
-rw-r--r-- | app/assets/stylesheets/components.scss | 31 | ||||
-rw-r--r-- | app/controllers/settings/preferences_controller.rb | 7 | ||||
-rw-r--r-- | app/models/user.rb | 4 | ||||
-rw-r--r-- | app/views/home/initial_state.json.rabl | 1 | ||||
-rw-r--r-- | app/views/settings/preferences/show.html.haml | 3 | ||||
-rw-r--r-- | config/locales/simple_form.en.yml | 1 | ||||
-rw-r--r-- | config/settings.yml | 1 |
13 files changed, 95 insertions, 30 deletions
diff --git a/app/assets/javascripts/components/components/media_gallery.jsx b/app/assets/javascripts/components/components/media_gallery.jsx index 325fd8157..f334af9cf 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,15 +159,21 @@ const Item = React.createClass({ /> ); } else if (attachment.get('type') === 'gifv') { + const autoPlay = !isIOS() && this.props.autoPlayGif; + thumbnail = ( - <video - src={attachment.get('url')} - onClick={this.handleClick} - autoPlay={!isIOS()} - loop={true} - muted={true} - style={gifvThumbStyle} - /> + <div style={{ position: 'relative', width: '100%', height: '100%', overflow: 'hidden' }} className={`media-gallery__gifv ${autoPlay ? 'autoplay' : ''}`}> + <video + src={attachment.get('url')} + onClick={this.handleClick} + autoPlay={autoPlay} + loop={true} + muted={true} + style={gifvThumbStyle} + /> + + <span className='media-gallery__gifv__label'>GIF</span> + </div> ); } @@ -192,7 +199,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 +235,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..a660dee37 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 ( @@ -48,13 +58,12 @@ const Avatar = React.createClass({ className='account__header__avatar' target='_blank' rel='noopener' - style={{ display: 'block', width: '90px', height: '90px', margin: '0 auto', marginBottom: '10px', borderRadius: `${radius}px`, overflow: 'hidden' }} + style={{ display: 'block', width: '90px', height: '90px', margin: '0 auto', marginBottom: '10px', borderRadius: `${radius}px`, overflow: 'hidden', backgroundSize: '90px 90px', backgroundImage: `url(${autoPlayGif || isHovered ? account.get('avatar') : account.get('avatar_static')})` }} onMouseOver={this.handleMouseOver} 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' }} /> - </a> + onBlur={this.handleMouseOut} + /> } </Motion> ); @@ -68,7 +77,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 +129,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 +144,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/assets/stylesheets/components.scss b/app/assets/stylesheets/components.scss index 6f407a6d5..b646b0c77 100644 --- a/app/assets/stylesheets/components.scss +++ b/app/assets/stylesheets/components.scss @@ -2315,3 +2315,34 @@ button.icon-button.active i.fa-retweet { top: 0; left: 0; } + +.media-gallery__gifv__label { + display: block; + position: absolute; + color: $color5; + background: rgba($color8, 0.5); + bottom: 6px; + left: 6px; + padding: 2px 6px; + border-radius: 2px; + font-size: 11px; + font-weight: 600; + z-index: 1; + pointer-events: none; + opacity: 0.9; + transition: opacity 0.1s ease; +} + +.media-gallery__gifv { + &.autoplay { + .media-gallery__gifv__label { + display: none; + } + } + + &:hover { + .media-gallery__gifv__label { + opacity: 1; + } + } +} diff --git a/app/controllers/settings/preferences_controller.rb b/app/controllers/settings/preferences_controller.rb index c758e4ef2..5d8cb7628 100644 --- a/app/controllers/settings/preferences_controller.rb +++ b/app/controllers/settings/preferences_controller.rb @@ -23,9 +23,10 @@ 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['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 110b52aa9..cd1f816ca 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -30,4 +30,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 104049387..b599b5cf0 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 4f4326763..ce3929629 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 |