diff options
-rw-r--r-- | app/javascript/mastodon/components/media_gallery.js | 12 | ||||
-rw-r--r-- | app/javascript/mastodon/components/status.js | 23 | ||||
-rw-r--r-- | app/javascript/mastodon/components/video_player.js | 17 | ||||
-rw-r--r-- | app/javascript/mastodon/features/status/components/detailed_status.js | 24 | ||||
-rw-r--r-- | app/javascript/mastodon/features/status/index.js | 5 | ||||
-rw-r--r-- | app/javascript/mastodon/features/ui/components/settings_modal.js | 24 | ||||
-rw-r--r-- | app/javascript/mastodon/locales/defaultMessages.json | 10 | ||||
-rw-r--r-- | app/javascript/mastodon/locales/en.json | 4 | ||||
-rw-r--r-- | app/javascript/mastodon/reducers/local_settings.js | 3 | ||||
-rw-r--r-- | app/javascript/styles/components.scss | 51 | ||||
-rw-r--r-- | app/javascript/styles/custom.scss | 103 |
11 files changed, 132 insertions, 144 deletions
diff --git a/app/javascript/mastodon/components/media_gallery.js b/app/javascript/mastodon/components/media_gallery.js index 2cb1ce51c..fe462d245 100644 --- a/app/javascript/mastodon/components/media_gallery.js +++ b/app/javascript/mastodon/components/media_gallery.js @@ -15,6 +15,7 @@ class Item extends React.PureComponent { attachment: ImmutablePropTypes.map.isRequired, index: PropTypes.number.isRequired, size: PropTypes.number.isRequired, + letterbox: PropTypes.bool, onClick: PropTypes.func.isRequired, autoPlayGif: PropTypes.bool.isRequired, }; @@ -31,7 +32,7 @@ class Item extends React.PureComponent { } render () { - const { attachment, index, size } = this.props; + const { attachment, index, size, letterbox } = this.props; let width = 50; let height = 100; @@ -101,7 +102,7 @@ class Item extends React.PureComponent { onClick={this.handleClick} target='_blank' > - <img src={previewUrl} srcSet={srcSet} sizes={sizes} alt='' /> + <img className={letterbox ? 'letterbox' : ''} src={previewUrl} srcSet={srcSet} sizes={sizes} alt='' /> </a> ); } else if (attachment.get('type') === 'gifv') { @@ -110,7 +111,7 @@ class Item extends React.PureComponent { thumbnail = ( <div className={`media-gallery__gifv ${autoPlay ? 'autoplay' : ''}`}> <video - className='media-gallery__item-gifv-thumbnail' + className={`media-gallery__item-gifv-thumbnail${letterbox ? ' letterbox' : ''}`} role='application' src={attachment.get('url')} onClick={this.handleClick} @@ -139,6 +140,7 @@ export default class MediaGallery extends React.PureComponent { static propTypes = { sensitive: PropTypes.bool, media: ImmutablePropTypes.list.isRequired, + letterbox: PropTypes.bool, height: PropTypes.number.isRequired, onOpenMedia: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, @@ -158,7 +160,7 @@ export default class MediaGallery extends React.PureComponent { } render () { - const { media, intl, sensitive } = this.props; + const { media, intl, sensitive, letterbox } = this.props; let children; @@ -179,7 +181,7 @@ export default class MediaGallery extends React.PureComponent { ); } else { const size = media.take(4).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} />); + 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} letterbox={letterbox} />); } return ( diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index a200570a1..28f89a783 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -174,7 +174,7 @@ class Status extends ImmutablePureComponent { if (collapse !== undefined) this.collapse(collapse); else if (settings.getIn(['collapsed', 'auto', 'all'])) this.collapse(); - else if (settings.getIn(['collapsed', 'auto', 'lengthy']) && node.clientHeight > 400) this.collapse(); + else if (settings.getIn(['collapsed', 'auto', 'lengthy']) && node.clientHeight > (status.get('media_attachments').size > 0 && !this.props.muted ? 650 : 400)) this.collapse(); else if (settings.getIn(['collapsed', 'auto', 'replies']) && status.get('in_reply_to_id', null) !== null) this.collapse(); else if (settings.getIn(['collapsed', 'auto', 'media']) && !(status.get('spoiler_text').length > 0) && status.get('media_attachments').size > 0) this.collapse(); @@ -296,10 +296,27 @@ class Status extends ImmutablePureComponent { if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) { } else 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')} + letterbox={settings.getIn(['media', 'letterbox'])} + height={250} + onOpenVideo={this.props.onOpenVideo} + /> + ); mediaIcon = 'video-camera'; } else { - media = <MediaGallery media={status.get('media_attachments')} sensitive={status.get('sensitive')} height={110} onOpenMedia={this.props.onOpenMedia} autoPlayGif={this.props.autoPlayGif} />; + media = ( + <MediaGallery + media={status.get('media_attachments')} + sensitive={status.get('sensitive')} + letterbox={settings.getIn(['media', 'letterbox'])} + height={250} + onOpenMedia={this.props.onOpenMedia} + autoPlayGif={this.props.autoPlayGif} + /> + ); mediaIcon = 'picture-o'; } diff --git a/app/javascript/mastodon/components/video_player.js b/app/javascript/mastodon/components/video_player.js index 452a84319..55e2d09e3 100644 --- a/app/javascript/mastodon/components/video_player.js +++ b/app/javascript/mastodon/components/video_player.js @@ -16,7 +16,7 @@ export default class VideoPlayer extends React.PureComponent { static propTypes = { media: ImmutablePropTypes.map.isRequired, - width: PropTypes.number, + letterbox: PropTypes.bool, height: PropTypes.number, sensitive: PropTypes.bool, intl: PropTypes.object.isRequired, @@ -25,7 +25,6 @@ export default class VideoPlayer extends React.PureComponent { }; static defaultProps = { - width: 239, height: 110, }; @@ -111,7 +110,7 @@ export default class VideoPlayer extends React.PureComponent { } render () { - const { media, intl, width, height, sensitive, autoplay } = this.props; + const { media, intl, letterbox, height, sensitive, autoplay } = this.props; let spoilerButton = ( <div className={`status__video-player-spoiler ${this.state.visible ? 'status__video-player-spoiler--visible' : ''}`}> @@ -138,7 +137,7 @@ export default class VideoPlayer extends React.PureComponent { if (!this.state.visible) { if (sensitive) { return ( - <div role='button' tabIndex='0' style={{ width: `${width}px`, height: `${height}px` }} className='media-spoiler' onClick={this.handleVisibility}> + <div role='button' tabIndex='0' style={{ height: `${height}px` }} className='media-spoiler' onClick={this.handleVisibility}> {spoilerButton} <span className='media-spoiler__warning'><FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' /></span> <span className='media-spoiler__trigger'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span> @@ -146,7 +145,7 @@ export default class VideoPlayer extends React.PureComponent { ); } else { return ( - <div role='button' tabIndex='0' style={{ width: `${width}px`, height: `${height}px` }} className='media-spoiler' onClick={this.handleVisibility}> + <div role='button' tabIndex='0' style={{ height: `${height}px` }} className='media-spoiler' onClick={this.handleVisibility}> {spoilerButton} <span className='media-spoiler__warning'><FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' /></span> <span className='media-spoiler__trigger'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span> @@ -157,7 +156,7 @@ export default class VideoPlayer extends React.PureComponent { if (this.state.preview && !autoplay) { return ( - <div role='button' tabIndex='0' className='media-spoiler-video' style={{ width: `${width}px`, height: `${height}px`, backgroundImage: `url(${media.get('preview_url')})` }} onClick={this.handleOpen}> + <div role='button' tabIndex='0' className='media-spoiler-video' style={{ height: `${height}px`, backgroundImage: `url(${media.get('preview_url')})` }} onClick={this.handleOpen}> {spoilerButton} <div className='media-spoiler-video-play-icon'><i className='fa fa-play' /></div> </div> @@ -166,20 +165,20 @@ export default class VideoPlayer extends React.PureComponent { if (this.state.videoError) { return ( - <div style={{ width: `${width}px`, height: `${height}px` }} className='video-error-cover' > + <div style={{ height: `${height}px` }} className='video-error-cover' > <span className='media-spoiler__warning'><FormattedMessage id='video_player.video_error' defaultMessage='Video could not be played' /></span> </div> ); } return ( - <div className='status__video-player' style={{ width: `${width}px`, height: `${height}px` }}> + <div className='status__video-player' style={{ height: `${height}px` }}> {spoilerButton} {muteButton} {expandButton} <video - className='status__video-player-video' + className={`status__video-player-video${letterbox ? ' letterbox' : ''}`} role='button' tabIndex='0' ref={this.setRef} diff --git a/app/javascript/mastodon/features/status/components/detailed_status.js b/app/javascript/mastodon/features/status/components/detailed_status.js index e6bbcf6a0..6c3585489 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.js +++ b/app/javascript/mastodon/features/status/components/detailed_status.js @@ -20,6 +20,7 @@ export default class DetailedStatus extends ImmutablePureComponent { static propTypes = { status: ImmutablePropTypes.map.isRequired, + settings: ImmutablePropTypes.map.isRequired, onOpenMedia: PropTypes.func.isRequired, onOpenVideo: PropTypes.func.isRequired, autoPlayGif: PropTypes.bool, @@ -36,6 +37,7 @@ export default class DetailedStatus extends ImmutablePureComponent { render () { const status = this.props.status.get('reblog') ? this.props.status.get('reblog') : this.props.status; + const { settings } = this.props; let media = ''; let mediaIcon = null; @@ -45,10 +47,28 @@ export default class DetailedStatus extends ImmutablePureComponent { if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) { media = <AttachmentList media={status.get('media_attachments')} />; } else 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])} + letterbox={settings.getIn(['media', 'letterbox'])} + height={250} + onOpenVideo={this.props.onOpenVideo} + autoplay + /> + ); mediaIcon = 'video-camera'; } else { - media = <MediaGallery sensitive={status.get('sensitive')} media={status.get('media_attachments')} height={300} onOpenMedia={this.props.onOpenMedia} autoPlayGif={this.props.autoPlayGif} />; + media = ( + <MediaGallery + sensitive={status.get('sensitive')} + media={status.get('media_attachments')} + letterbox={settings.getIn(['media', 'letterbox'])} + height={250} + onOpenMedia={this.props.onOpenMedia} + autoPlayGif={this.props.autoPlayGif} + /> + ); mediaIcon = 'picture-o'; } } else media = <CardContainer statusId={status.get('id')} />; diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js index cbabdd5bc..5ec26c2d5 100644 --- a/app/javascript/mastodon/features/status/index.js +++ b/app/javascript/mastodon/features/status/index.js @@ -37,6 +37,7 @@ const makeMapStateToProps = () => { const mapStateToProps = (state, props) => ({ status: getStatus(state, Number(props.params.statusId)), + settings: state.get('local_settings'), ancestorsIds: state.getIn(['contexts', 'ancestors', Number(props.params.statusId)]), descendantsIds: state.getIn(['contexts', 'descendants', Number(props.params.statusId)]), me: state.getIn(['meta', 'me']), @@ -60,6 +61,7 @@ export default class Status extends ImmutablePureComponent { params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, status: ImmutablePropTypes.map, + settings: ImmutablePropTypes.map.isRequired, ancestorsIds: ImmutablePropTypes.list, descendantsIds: ImmutablePropTypes.list, me: PropTypes.number, @@ -143,7 +145,7 @@ export default class Status extends ImmutablePureComponent { render () { let ancestors, descendants; - const { status, ancestorsIds, descendantsIds, me, autoPlayGif } = this.props; + const { status, settings, ancestorsIds, descendantsIds, me, autoPlayGif } = this.props; if (status === null) { return ( @@ -172,6 +174,7 @@ export default class Status extends ImmutablePureComponent { <DetailedStatus status={status} + settings={settings} autoPlayGif={autoPlayGif} me={me} onOpenVideo={this.handleOpenVideo} diff --git a/app/javascript/mastodon/features/ui/components/settings_modal.js b/app/javascript/mastodon/features/ui/components/settings_modal.js index 4fc059ac2..70c004a63 100644 --- a/app/javascript/mastodon/features/ui/components/settings_modal.js +++ b/app/javascript/mastodon/features/ui/components/settings_modal.js @@ -168,12 +168,28 @@ export default class SettingsModal extends React.PureComponent { ); } + Media = () => { + return ( + <div> + <h1><FormattedMessage id='settings.media' defaultMessage='Media' /></h1> + <SettingsItem + settings={this.props.settings} + item={['media', 'letterbox']} + id='mastodon-settings--media-letterbox' + onChange={this.props.toggleSetting} + > + <FormattedMessage id='settings.media_letterbox' defaultMessage='Letterbox media' /> + </SettingsItem> + </div> + ); + } + navigateTo = (e) => this.setState({ currentIndex: +e.currentTarget.getAttribute('data-mastodon-navigation_index') }); render () { - const { General, CollapsedStatuses, navigateTo } = this; + const { General, CollapsedStatuses, Media, navigateTo } = this; const { onClose } = this.props; const { currentIndex } = this.state; @@ -187,8 +203,11 @@ export default class SettingsModal extends React.PureComponent { <a onClick={navigateTo} role='button' data-mastodon-navigation_index='1' tabIndex='0' className={`settings-modal__navigation-item${currentIndex === 1 ? ' active' : ''}`}> <FormattedMessage id='settings.collapsed_statuses' defaultMessage='Collapsed toots' /> </a> + <a onClick={navigateTo} role='button' data-mastodon-navigation_index='2' tabIndex='0' className={`settings-modal__navigation-item${currentIndex === 2 ? ' active' : ''}`}> + <FormattedMessage id='settings.media' defaultMessage='Media' /> + </a> <a href='/settings/preferences' className='settings-modal__navigation-item'> - <i className='fa fa-fw fa-cogs' /> <FormattedMessage id='settings.preferences' defaultMessage='User preferences' /> + <i className='fa fa-fw fa-cog' /> <FormattedMessage id='settings.preferences' defaultMessage='User preferences' /> </a> <a onClick={onClose} role='button' tabIndex='0' className='settings-modal__navigation-close'> <FormattedMessage id='settings.close' defaultMessage='Close' /> @@ -201,6 +220,7 @@ export default class SettingsModal extends React.PureComponent { [ <General />, <CollapsedStatuses />, + <Media />, ][currentIndex] || <General /> } </div> diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index 147f6a971..a5ff686a0 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -1227,8 +1227,16 @@ "id": "settings.image_backgrounds_media" }, { + "defaultMessage": "Media", + "id": "settings.media" + }, + { + "defaultMessage": "Letterbox media", + "id": "settings.media_letterbox" + }, + { "defaultMessage": "User preferences", - "id": "settings.global_settings" + "id": "settings.preferences" }, { "defaultMessage": "Close", diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index fb81aba4a..20bbb24db 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -157,10 +157,12 @@ "settings.collapsed_statuses": "Collapsed toots", "settings.enable_collapsed": "Enable collapsed toots", "settings.general": "General", - "settings.global_settings": "User preferences", "settings.image_backgrounds": "Image backgrounds", "settings.image_backgrounds_media": "Preview collapsed toot media", "settings.image_backgrounds_users": "Give collapsed toots an image background", + "settings.media": "Media", + "settings.media_letterbox": "Letterbox media", + "settings.preferences": "User preferences", "settings.wide_view": "Wide view (Desktop mode only)", "status.cannot_reblog": "This post cannot be boosted", "status.collapse": "Collapse", diff --git a/app/javascript/mastodon/reducers/local_settings.js b/app/javascript/mastodon/reducers/local_settings.js index 4f00c20d4..0e08f8e2a 100644 --- a/app/javascript/mastodon/reducers/local_settings.js +++ b/app/javascript/mastodon/reducers/local_settings.js @@ -19,6 +19,9 @@ const initialState = Immutable.fromJS({ preview_images : false, }, }, + media : { + letterbox : true, + }, }); const hydrate = (state, localSettings) => state.mergeDeep(localSettings); diff --git a/app/javascript/styles/components.scss b/app/javascript/styles/components.scss index 145854697..304a056b3 100644 --- a/app/javascript/styles/components.scss +++ b/app/javascript/styles/components.scss @@ -3640,10 +3640,17 @@ button.icon-button.active i.fa-retweet { /* Media Gallery */ .media-gallery { box-sizing: border-box; - margin-top: 8px; + margin-top: 15px; + margin-left: -68px; overflow: hidden; position: relative; - width: 100%; + width: calc(100% + 80px); + background: $base-shadow-color; + + .detailed-status & { + margin-left:-10px; + width: calc(100% + 22px); + } } .media-gallery__item { @@ -3655,18 +3662,20 @@ button.icon-button.active i.fa-retweet { } .media-gallery__item-thumbnail { - background-position: center; - background-repeat: no-repeat; - background-size: cover; cursor: zoom-in; display: flex; align-items: center; text-decoration: none; + width: 100%; height: 100%; - &, img { width: 100%; + + &:not(.letterbox) { + height: 100%; + object-fit: cover; + } } } @@ -3680,12 +3689,13 @@ button.icon-button.active i.fa-retweet { .media-gallery__item-gifv-thumbnail { cursor: zoom-in; height: 100%; - object-fit: cover; position: relative; - top: 50%; - transform: translateY(-50%); - width: 100%; z-index: 1; + + &:not(.letterbox) { + height: 100%; + object-fit: cover; + } } .media-gallery__item-thumbnail-label { @@ -3698,22 +3708,27 @@ button.icon-button.active i.fa-retweet { /* Status Video Player */ .status__video-player { - background: $base-overlay-background; + display: flex; + align-items: center; + background: $base-shadow-color; box-sizing: border-box; cursor: default; /* May not be needed */ - margin-top: 8px; + margin-top: 15px; + margin-left:-68px; + width: calc(100% + 80px); overflow: hidden; position: relative; } .status__video-player-video { - height: 100%; - object-fit: cover; position: relative; - top: 50%; - transform: translateY(-50%); width: 100%; z-index: 1; + + &:not(.letterbox) { + height: 100%; + object-fit: cover; + } } .status__video-player-expand, @@ -3754,7 +3769,9 @@ button.icon-button.active i.fa-retweet { background-repeat: no-repeat; background-position: center; cursor: pointer; - margin-top: 8px; + margin-top: 15px; + margin-left:-68px; + width: calc(100% + 80px); position: relative; } diff --git a/app/javascript/styles/custom.scss b/app/javascript/styles/custom.scss index 6a18fd628..97a981243 100644 --- a/app/javascript/styles/custom.scss +++ b/app/javascript/styles/custom.scss @@ -1,104 +1 @@ @import 'application'; - -.muted { - .status__content p, .status__content a { - color: lighten($ui-base-color, 35%); - } - - .status__display-name strong { - color: lighten($ui-base-color, 35%); - } -} - -.status time:after, -.detailed-status__datetime span:after { - font: normal normal normal 14px/1 FontAwesome; - content: "\00a0\00a0\f08e"; -} - -.compose-form__buttons button.active:last-child { - color:$ui-secondary-color; - background-color: $ui-highlight-color; - border-radius:3px; -} - -.about-body .mascot { - display:none; -} - -.screenshot-with-signup { - min-height:300px; -} - -.screenshot-with-signup .closed-registrations-message, -.screenshot-with-signup form { - background-color: rgba(0,0,0,0.7); - margin:auto; -} - -.screenshot-with-signup .closed-registrations-message .clock { - font-size:150%; -} - -.drawer .drawer__inner { - overflow: visible; -} - -.column { - // trying to fix @mdhughes safari problem - max-height:100vh; -} - - - -.media-gallery { - height:auto !important; - max-height:250px; - position:relative; - margin-top:20px; - margin-left:-68px; - width: calc(100% + 80px); -} - -.media-gallery:before{ - content: ""; - display: block; - padding-top: 100%; -} - -.media-gallery__item, -.media-gallery .media-spoiler{ - left: 0; - right: 0; - top: 0; - bottom: 0 !important; - position:absolute; -} - -.media-spoiler-video:before { - content:""; - display:block; - padding-top:100%; -} - -.media-spoiler-video, -.status__video-player, -.detailed-status > .media-spoiler, -.status > .media-spoiler { - height:auto !important; - max-height:250px; - position:relative; - margin-top:20px; - margin-left:-68px; - width: calc(100% + 80px) !important; -} - -.status__video-player-video { - transform:unset; -} - -.detailed-status > .media-spoiler, -.status > .media-spoiler { - height:250px !important; - vertical-align:middle; -} |