Merge branch 'patf-pause-gif'
This commit is contained in:
commit
93c13fe691
13 changed files with 95 additions and 30 deletions
|
@ -78,7 +78,8 @@ const Item = React.createClass({
|
||||||
attachment: ImmutablePropTypes.map.isRequired,
|
attachment: ImmutablePropTypes.map.isRequired,
|
||||||
index: React.PropTypes.number.isRequired,
|
index: React.PropTypes.number.isRequired,
|
||||||
size: 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],
|
mixins: [PureRenderMixin],
|
||||||
|
@ -158,15 +159,21 @@ const Item = React.createClass({
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (attachment.get('type') === 'gifv') {
|
} else if (attachment.get('type') === 'gifv') {
|
||||||
|
const autoPlay = !isIOS() && this.props.autoPlayGif;
|
||||||
|
|
||||||
thumbnail = (
|
thumbnail = (
|
||||||
<video
|
<div style={{ position: 'relative', width: '100%', height: '100%', overflow: 'hidden' }} className={`media-gallery__gifv ${autoPlay ? 'autoplay' : ''}`}>
|
||||||
src={attachment.get('url')}
|
<video
|
||||||
onClick={this.handleClick}
|
src={attachment.get('url')}
|
||||||
autoPlay={!isIOS()}
|
onClick={this.handleClick}
|
||||||
loop={true}
|
autoPlay={autoPlay}
|
||||||
muted={true}
|
loop={true}
|
||||||
style={gifvThumbStyle}
|
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,
|
media: ImmutablePropTypes.list.isRequired,
|
||||||
height: React.PropTypes.number.isRequired,
|
height: React.PropTypes.number.isRequired,
|
||||||
onOpenMedia: React.PropTypes.func.isRequired,
|
onOpenMedia: React.PropTypes.func.isRequired,
|
||||||
intl: React.PropTypes.object.isRequired
|
intl: React.PropTypes.object.isRequired,
|
||||||
|
autoPlayGif: React.PropTypes.bool.isRequired
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [PureRenderMixin],
|
mixins: [PureRenderMixin],
|
||||||
|
@ -227,7 +235,7 @@ const MediaGallery = React.createClass({
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const size = media.take(4).size;
|
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 (
|
return (
|
||||||
|
|
|
@ -29,6 +29,7 @@ const Status = React.createClass({
|
||||||
onBlock: React.PropTypes.func,
|
onBlock: React.PropTypes.func,
|
||||||
me: React.PropTypes.number,
|
me: React.PropTypes.number,
|
||||||
boostModal: React.PropTypes.bool,
|
boostModal: React.PropTypes.bool,
|
||||||
|
autoPlayGif: React.PropTypes.bool,
|
||||||
muted: React.PropTypes.bool
|
muted: React.PropTypes.bool
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -79,7 +80,7 @@ const Status = React.createClass({
|
||||||
if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
||||||
media = <VideoPlayer media={status.getIn(['media_attachments', 0])} sensitive={status.get('sensitive')} onOpenVideo={this.props.onOpenVideo} />;
|
media = <VideoPlayer media={status.getIn(['media_attachments', 0])} sensitive={status.get('sensitive')} onOpenVideo={this.props.onOpenVideo} />;
|
||||||
} else {
|
} 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} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,8 @@ const makeMapStateToProps = () => {
|
||||||
const mapStateToProps = (state, props) => ({
|
const mapStateToProps = (state, props) => ({
|
||||||
status: getStatus(state, props.id),
|
status: getStatus(state, props.id),
|
||||||
me: state.getIn(['meta', 'me']),
|
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;
|
return mapStateToProps;
|
||||||
|
|
|
@ -5,6 +5,7 @@ import escapeTextContentForBrowser from 'escape-html';
|
||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||||
import IconButton from '../../../components/icon_button';
|
import IconButton from '../../../components/icon_button';
|
||||||
import { Motion, spring } from 'react-motion';
|
import { Motion, spring } from 'react-motion';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
|
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
|
||||||
|
@ -12,10 +13,19 @@ const messages = defineMessages({
|
||||||
requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' }
|
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({
|
const Avatar = React.createClass({
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
account: ImmutablePropTypes.map.isRequired
|
account: ImmutablePropTypes.map.isRequired,
|
||||||
|
autoPlayGif: React.PropTypes.bool.isRequired
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState () {
|
getInitialState () {
|
||||||
|
@ -37,7 +47,7 @@ const Avatar = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { account } = this.props;
|
const { account, autoPlayGif } = this.props;
|
||||||
const { isHovered } = this.state;
|
const { isHovered } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -48,13 +58,12 @@ const Avatar = React.createClass({
|
||||||
className='account__header__avatar'
|
className='account__header__avatar'
|
||||||
target='_blank'
|
target='_blank'
|
||||||
rel='noopener'
|
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}
|
onMouseOver={this.handleMouseOver}
|
||||||
onMouseOut={this.handleMouseOut}
|
onMouseOut={this.handleMouseOut}
|
||||||
onFocus={this.handleMouseOver}
|
onFocus={this.handleMouseOver}
|
||||||
onBlur={this.handleMouseOut}>
|
onBlur={this.handleMouseOut}
|
||||||
<img src={account.get('avatar')} alt={account.get('acct')} style={{ display: 'block', width: '90px', height: '90px' }} />
|
/>
|
||||||
</a>
|
|
||||||
}
|
}
|
||||||
</Motion>
|
</Motion>
|
||||||
);
|
);
|
||||||
|
@ -68,7 +77,8 @@ const Header = React.createClass({
|
||||||
account: ImmutablePropTypes.map,
|
account: ImmutablePropTypes.map,
|
||||||
me: React.PropTypes.number.isRequired,
|
me: React.PropTypes.number.isRequired,
|
||||||
onFollow: React.PropTypes.func.isRequired,
|
onFollow: React.PropTypes.func.isRequired,
|
||||||
intl: React.PropTypes.object.isRequired
|
intl: React.PropTypes.object.isRequired,
|
||||||
|
autoPlayGif: React.PropTypes.bool.isRequired
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [PureRenderMixin],
|
mixins: [PureRenderMixin],
|
||||||
|
@ -119,7 +129,7 @@ const Header = React.createClass({
|
||||||
return (
|
return (
|
||||||
<div className='account__header' style={{ backgroundImage: `url(${account.get('header')})` }}>
|
<div className='account__header' style={{ backgroundImage: `url(${account.get('header')})` }}>
|
||||||
<div style={{ padding: '20px 10px' }}>
|
<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 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>
|
<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));
|
||||||
|
|
|
@ -19,6 +19,7 @@ const DetailedStatus = React.createClass({
|
||||||
status: ImmutablePropTypes.map.isRequired,
|
status: ImmutablePropTypes.map.isRequired,
|
||||||
onOpenMedia: React.PropTypes.func.isRequired,
|
onOpenMedia: React.PropTypes.func.isRequired,
|
||||||
onOpenVideo: React.PropTypes.func.isRequired,
|
onOpenVideo: React.PropTypes.func.isRequired,
|
||||||
|
autoPlayGif: React.PropTypes.bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [PureRenderMixin],
|
mixins: [PureRenderMixin],
|
||||||
|
@ -42,7 +43,7 @@ const DetailedStatus = React.createClass({
|
||||||
if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
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 />;
|
media = <VideoPlayer sensitive={status.get('sensitive')} media={status.getIn(['media_attachments', 0])} width={300} height={150} onOpenVideo={this.props.onOpenVideo} autoplay />;
|
||||||
} else {
|
} 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 {
|
} else {
|
||||||
media = <CardContainer statusId={status.get('id')} />;
|
media = <CardContainer statusId={status.get('id')} />;
|
||||||
|
|
|
@ -39,7 +39,8 @@ const makeMapStateToProps = () => {
|
||||||
ancestorsIds: state.getIn(['timelines', 'ancestors', Number(props.params.statusId)]),
|
ancestorsIds: state.getIn(['timelines', 'ancestors', Number(props.params.statusId)]),
|
||||||
descendantsIds: state.getIn(['timelines', 'descendants', Number(props.params.statusId)]),
|
descendantsIds: state.getIn(['timelines', 'descendants', Number(props.params.statusId)]),
|
||||||
me: state.getIn(['meta', 'me']),
|
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;
|
return mapStateToProps;
|
||||||
|
@ -57,7 +58,8 @@ const Status = React.createClass({
|
||||||
ancestorsIds: ImmutablePropTypes.list,
|
ancestorsIds: ImmutablePropTypes.list,
|
||||||
descendantsIds: ImmutablePropTypes.list,
|
descendantsIds: ImmutablePropTypes.list,
|
||||||
me: React.PropTypes.number,
|
me: React.PropTypes.number,
|
||||||
boostModal: React.PropTypes.bool
|
boostModal: React.PropTypes.bool,
|
||||||
|
autoPlayGif: React.PropTypes.bool
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [PureRenderMixin],
|
mixins: [PureRenderMixin],
|
||||||
|
@ -126,7 +128,7 @@ const Status = React.createClass({
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let ancestors, descendants;
|
let ancestors, descendants;
|
||||||
const { status, ancestorsIds, descendantsIds, me } = this.props;
|
const { status, ancestorsIds, descendantsIds, me, autoPlayGif } = this.props;
|
||||||
|
|
||||||
if (status === null) {
|
if (status === null) {
|
||||||
return (
|
return (
|
||||||
|
@ -155,7 +157,7 @@ const Status = React.createClass({
|
||||||
<div className='scrollable'>
|
<div className='scrollable'>
|
||||||
{ancestors}
|
{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} />
|
<ActionBar status={status} me={me} onReply={this.handleReplyClick} onFavourite={this.handleFavouriteClick} onReblog={this.handleReblogClick} onDelete={this.handleDeleteClick} onMention={this.handleMentionClick} onReport={this.handleReport} />
|
||||||
|
|
||||||
{descendants}
|
{descendants}
|
||||||
|
|
|
@ -2315,3 +2315,34 @@ button.icon-button.active i.fa-retweet {
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -23,9 +23,10 @@ class Settings::PreferencesController < ApplicationController
|
||||||
}
|
}
|
||||||
|
|
||||||
current_user.settings['default_privacy'] = user_params[:setting_default_privacy]
|
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')
|
redirect_to settings_preferences_path, notice: I18n.t('generic.changes_saved_msg')
|
||||||
else
|
else
|
||||||
render action: :show
|
render action: :show
|
||||||
|
@ -35,6 +36,6 @@ class Settings::PreferencesController < ApplicationController
|
||||||
private
|
private
|
||||||
|
|
||||||
def user_params
|
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
|
||||||
end
|
end
|
||||||
|
|
|
@ -30,4 +30,8 @@ class User < ApplicationRecord
|
||||||
def setting_boost_modal
|
def setting_boost_modal
|
||||||
settings.boost_modal
|
settings.boost_modal
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def setting_auto_play_gif
|
||||||
|
settings.auto_play_gif
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,6 +9,7 @@ node(:meta) do
|
||||||
me: current_account.id,
|
me: current_account.id,
|
||||||
admin: @admin.try(:id),
|
admin: @admin.try(:id),
|
||||||
boost_modal: current_account.user.setting_boost_modal,
|
boost_modal: current_account.user.setting_boost_modal,
|
||||||
|
auto_play_gif: current_account.user.setting_auto_play_gif,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -25,5 +25,8 @@
|
||||||
.fields-group
|
.fields-group
|
||||||
= f.input :setting_boost_modal, as: :boolean, wrapper: :with_label
|
= f.input :setting_boost_modal, as: :boolean, wrapper: :with_label
|
||||||
|
|
||||||
|
.fields-group
|
||||||
|
= f.input :setting_auto_play_gif, as: :boolean, wrapper: :with_label
|
||||||
|
|
||||||
.actions
|
.actions
|
||||||
= f.button :button, t('generic.save_changes'), type: :submit
|
= f.button :button, t('generic.save_changes'), type: :submit
|
||||||
|
|
|
@ -28,6 +28,7 @@ en:
|
||||||
note: Bio
|
note: Bio
|
||||||
otp_attempt: Two-factor code
|
otp_attempt: Two-factor code
|
||||||
password: Password
|
password: Password
|
||||||
|
setting_auto_play_gif: Auto-play animated GIFs
|
||||||
setting_boost_modal: Show confirmation dialog before boosting
|
setting_boost_modal: Show confirmation dialog before boosting
|
||||||
setting_default_privacy: Post privacy
|
setting_default_privacy: Post privacy
|
||||||
severity: Severity
|
severity: Severity
|
||||||
|
|
|
@ -15,6 +15,7 @@ defaults: &defaults
|
||||||
open_registrations: true
|
open_registrations: true
|
||||||
closed_registrations_message: ''
|
closed_registrations_message: ''
|
||||||
boost_modal: false
|
boost_modal: false
|
||||||
|
auto_play_gif: true
|
||||||
notification_emails:
|
notification_emails:
|
||||||
follow: false
|
follow: false
|
||||||
reblog: false
|
reblog: false
|
||||||
|
|
Loading…
Reference in a new issue