diff options
-rw-r--r-- | app/javascript/mastodon/components/media_gallery.js | 42 | ||||
-rw-r--r-- | app/javascript/mastodon/features/status/components/detailed_status.js | 11 | ||||
-rw-r--r-- | app/javascript/styles/components.scss | 7 | ||||
-rw-r--r-- | app/views/stream_entries/_detailed_status.html.haml | 2 | ||||
-rw-r--r-- | package.json | 1 | ||||
-rw-r--r-- | yarn.lock | 18 |
6 files changed, 71 insertions, 10 deletions
diff --git a/app/javascript/mastodon/components/media_gallery.js b/app/javascript/mastodon/components/media_gallery.js index 78a2f5dc4..d84e4229c 100644 --- a/app/javascript/mastodon/components/media_gallery.js +++ b/app/javascript/mastodon/components/media_gallery.js @@ -4,6 +4,8 @@ import PropTypes from 'prop-types'; import IconButton from './icon_button'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { isIOS } from '../is_mobile'; +import classNames from 'classnames'; +import sizeMe from 'react-sizeme'; const messages = defineMessages({ toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Toggle visibility' }, @@ -17,6 +19,7 @@ class Item extends React.PureComponent { static propTypes = { attachment: ImmutablePropTypes.map.isRequired, + standalone: PropTypes.bool, index: PropTypes.number.isRequired, size: PropTypes.number.isRequired, onClick: PropTypes.func.isRequired, @@ -25,6 +28,9 @@ class Item extends React.PureComponent { static defaultProps = { autoPlayGif: false, + standalone: false, + index: 0, + size: 1, }; handleMouseEnter = (e) => { @@ -57,7 +63,7 @@ class Item extends React.PureComponent { } render () { - const { attachment, index, size } = this.props; + const { attachment, index, size, standalone } = this.props; let width = 50; let height = 100; @@ -136,7 +142,7 @@ class Item extends React.PureComponent { const autoPlay = !isIOS() && this.props.autoPlayGif; thumbnail = ( - <div className={`media-gallery__gifv ${autoPlay ? 'autoplay' : ''}`}> + <div className={classNames('media-gallery__gifv', { autoplay: autoPlay })}> <video className='media-gallery__item-gifv-thumbnail' role='application' @@ -154,8 +160,10 @@ class Item extends React.PureComponent { ); } + const style = standalone ? {} : { left, top, right, bottom, width: `${width}%`, height: `${height}%` }; + return ( - <div className='media-gallery__item' key={attachment.get('id')} style={{ left: left, top: top, right: right, bottom: bottom, width: `${width}%`, height: `${height}%` }}> + <div className={classNames('media-gallery__item', { standalone })} key={attachment.get('id')} style={style}> {thumbnail} </div> ); @@ -164,11 +172,14 @@ class Item extends React.PureComponent { } @injectIntl +@sizeMe({}) export default class MediaGallery extends React.PureComponent { static propTypes = { sensitive: PropTypes.bool, + standalone: PropTypes.bool, media: ImmutablePropTypes.list.isRequired, + size: PropTypes.object, height: PropTypes.number.isRequired, onOpenMedia: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, @@ -177,6 +188,7 @@ export default class MediaGallery extends React.PureComponent { static defaultProps = { autoPlayGif: false, + standalone: false, }; state = { @@ -198,10 +210,19 @@ export default class MediaGallery extends React.PureComponent { } render () { - const { media, intl, sensitive } = this.props; + const { media, intl, sensitive, height, standalone, size } = this.props; let children; + const standaloneEligible = standalone && size.width && media.size === 1 && media.getIn([0, 'meta', 'small', 'aspect']); + const style = {}; + + if (standaloneEligible) { + style.height = size.width / media.getIn([0, 'meta', 'small', 'aspect']); + } else { + style.height = height; + } + if (!this.state.visible) { let warning; @@ -212,19 +233,24 @@ export default class MediaGallery extends React.PureComponent { } children = ( - <button className='media-spoiler' onClick={this.handleOpen}> + <button className='media-spoiler' onClick={this.handleOpen} style={style}> <span className='media-spoiler__warning'>{warning}</span> <span className='media-spoiler__trigger'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span> </button> ); } 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} />); + + if (standaloneEligible) { + children = <Item standalone onClick={this.handleClick} attachment={media.get(0)} autoPlayGif={this.props.autoPlayGif} />; + } else { + 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 ( - <div className='media-gallery' style={{ height: `${this.props.height}px` }}> - <div className={`spoiler-button ${this.state.visible ? 'spoiler-button--visible' : ''}`}> + <div className='media-gallery' style={style}> + <div className={classNames('spoiler-button', { 'spoiler-button--visible': this.state.visible })}> <IconButton title={intl.formatMessage(messages.toggle_visible)} icon={this.state.visible ? 'eye' : 'eye-slash'} overlay onClick={this.handleOpen} /> </div> diff --git a/app/javascript/mastodon/features/status/components/detailed_status.js b/app/javascript/mastodon/features/status/components/detailed_status.js index b11b41780..87fe01897 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.js +++ b/app/javascript/mastodon/features/status/components/detailed_status.js @@ -61,7 +61,16 @@ export default class DetailedStatus extends ImmutablePureComponent { /> ); } else { - media = <MediaGallery sensitive={status.get('sensitive')} media={status.get('media_attachments')} height={300} onOpenMedia={this.props.onOpenMedia} autoPlayGif={this.props.autoPlayGif} />; + media = ( + <MediaGallery + standalone + sensitive={status.get('sensitive')} + media={status.get('media_attachments')} + height={300} + onOpenMedia={this.props.onOpenMedia} + autoPlayGif={this.props.autoPlayGif} + /> + ); } } else if (status.get('spoiler_text').length === 0) { media = <CardContainer statusId={status.get('id')} />; diff --git a/app/javascript/styles/components.scss b/app/javascript/styles/components.scss index b6cf920b4..da479347b 100644 --- a/app/javascript/styles/components.scss +++ b/app/javascript/styles/components.scss @@ -3645,6 +3645,12 @@ button.icon-button.active i.fa-retweet { display: block; float: left; position: relative; + + &.standalone { + .media-gallery__item-gifv-thumbnail { + transform: none; + } + } } .media-gallery__item-thumbnail { @@ -3652,6 +3658,7 @@ button.icon-button.active i.fa-retweet { display: block; text-decoration: none; height: 100%; + line-height: 0; &, img { diff --git a/app/views/stream_entries/_detailed_status.html.haml b/app/views/stream_entries/_detailed_status.html.haml index 26e41a10d..fa9ccd1f0 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?, '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, 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/package.json b/package.json index be9b90875..7835a0440 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "react-router-dom": "^4.1.1", "react-router-scroll": "ytase/react-router-scroll#build", "react-simple-dropdown": "^3.0.0", + "react-sizeme": "^2.3.5", "react-swipeable-views": "^0.12.3", "react-textarea-autosize": "^5.0.7", "react-toggle": "^4.0.1", diff --git a/yarn.lock b/yarn.lock index 7b8328805..640d06a10 100644 --- a/yarn.lock +++ b/yarn.lock @@ -982,6 +982,10 @@ base64-js@^1.0.2: version "1.2.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.1.tgz#a91947da1f4a516ea38e5b4ec0ec3773675e0886" +batch-processor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/batch-processor/-/batch-processor-1.0.0.tgz#75c95c32b748e0850d10c2b168f6bdbe9891ace8" + batch@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" @@ -2053,6 +2057,12 @@ electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.14: version "1.3.15" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.15.tgz#08397934891cbcfaebbd18b82a95b5a481138369" +element-resize-detector@^1.1.12: + version "1.1.12" + resolved "https://registry.yarnpkg.com/element-resize-detector/-/element-resize-detector-1.1.12.tgz#8b3fd6eedda17f9c00b360a0ea2df9927ae80ba2" + dependencies: + batch-processor "^1.0.0" + elliptic@^6.0.0: version "6.4.0" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.0.tgz#cac9af8762c85836187003c8dfe193e5e2eae5df" @@ -5413,6 +5423,14 @@ react-simple-dropdown@^3.0.0: classnames "^2.1.2" prop-types "^15.5.8" +react-sizeme@^2.3.5: + version "2.3.5" + resolved "https://registry.yarnpkg.com/react-sizeme/-/react-sizeme-2.3.5.tgz#f14c0a15f9b24d7b8b6f196871b0af19aa01a422" + dependencies: + element-resize-detector "^1.1.12" + invariant "^2.2.2" + lodash "^4.17.4" + react-swipeable-views-core@^0.11.1: version "0.11.1" resolved "https://registry.yarnpkg.com/react-swipeable-views-core/-/react-swipeable-views-core-0.11.1.tgz#61d046799f90725bbf91a0eb3abcab805c774cac" |