]>
cat aescling's git repositories - mastodon.git/blob - app/javascript/mastodon/components/media_gallery.js
1 import React
from 'react';
2 import ImmutablePropTypes
from 'react-immutable-proptypes';
3 import PropTypes
from 'prop-types';
4 import { is
} from 'immutable';
5 import IconButton
from './icon_button';
6 import { defineMessages
, injectIntl
, FormattedMessage
} from 'react-intl';
7 import { isIOS
} from '../is_mobile';
8 import classNames
from 'classnames';
9 import { autoPlayGif
, displaySensitiveMedia
} from '../initial_state';
11 const messages
= defineMessages({
12 toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Toggle visibility' },
15 class Item
extends React
.PureComponent
{
17 static contextTypes
= {
18 router: PropTypes
.object
,
22 attachment: ImmutablePropTypes
.map
.isRequired
,
23 standalone: PropTypes
.bool
,
24 index: PropTypes
.number
.isRequired
,
25 size: PropTypes
.number
.isRequired
,
26 onClick: PropTypes
.func
.isRequired
,
29 static defaultProps
= {
35 handleMouseEnter
= (e
) => {
36 if (this.hoverToPlay()) {
41 handleMouseLeave
= (e
) => {
42 if (this.hoverToPlay()) {
44 e
.target
.currentTime
= 0;
49 const { attachment
} = this.props
;
50 return !autoPlayGif
&& attachment
.get('type') === 'gifv';
53 handleClick
= (e
) => {
54 const { index
, onClick
} = this.props
;
56 if (this.context
.router
&& e
.button
=== 0) {
65 const { attachment
, index
, size
, standalone
} = this.props
;
78 if (size
=== 4 || (size
=== 3 && index
> 0)) {
88 } else if (size
=== 3) {
91 } else if (index
> 0) {
97 } else if (index
> 1) {
100 } else if (size
=== 4) {
101 if (index
=== 0 || index
=== 2) {
105 if (index
=== 1 || index
=== 3) {
118 if (attachment
.get('type') === 'image') {
119 const previewUrl
= attachment
.get('preview_url');
120 const previewWidth
= attachment
.getIn(['meta', 'small', 'width']);
122 const originalUrl
= attachment
.get('url');
123 const originalWidth
= attachment
.getIn(['meta', 'original', 'width']);
125 const hasSize
= typeof originalWidth
=== 'number' && typeof previewWidth
=== 'number';
127 const srcSet
= hasSize
? `${originalUrl} ${originalWidth}w, ${previewUrl} ${previewWidth}w` : null;
128 const sizes
= hasSize
? `(min-width: 1025px) ${320 * (width / 100)}px, ${width}vw` : null;
132 className
='media-gallery__item-thumbnail'
133 href
={attachment
.get('remote_url') || originalUrl
}
134 onClick
={this.handleClick
}
137 <img src
={previewUrl
} srcSet
={srcSet
} sizes
={sizes
} alt
={attachment
.get('description')} title
={attachment
.get('description')} />
140 } else if (attachment
.get('type') === 'gifv') {
141 const autoPlay
= !isIOS() && autoPlayGif
;
144 <div className
={classNames('media-gallery__gifv', { autoplay: autoPlay
})}>
146 className
='media-gallery__item-gifv-thumbnail'
147 aria
-label
={attachment
.get('description')}
149 src
={attachment
.get('url')}
150 onClick
={this.handleClick
}
151 onMouseEnter
={this.handleMouseEnter
}
152 onMouseLeave
={this.handleMouseLeave
}
158 <span className
='media-gallery__gifv__label'>GIF
</span
>
164 <div className
={classNames('media-gallery__item', { standalone
})} key
={attachment
.get('id')} style
={{ left: left
, top: top
, right: right
, bottom: bottom
, width: `${width}%`, height: `${height}%` }}>
173 export default class MediaGallery
extends React
.PureComponent
{
176 sensitive: PropTypes
.bool
,
177 standalone: PropTypes
.bool
,
178 media: ImmutablePropTypes
.list
.isRequired
,
179 size: PropTypes
.object
,
180 height: PropTypes
.number
.isRequired
,
181 onOpenMedia: PropTypes
.func
.isRequired
,
182 intl: PropTypes
.object
.isRequired
,
185 static defaultProps
= {
190 visible: !this.props
.sensitive
|| displaySensitiveMedia
,
193 componentWillReceiveProps (nextProps
) {
194 if (!is(nextProps
.media
, this.props
.media
)) {
195 this.setState({ visible: !nextProps
.sensitive
});
200 this.setState({ visible: !this.state
.visible
});
203 handleClick
= (index
) => {
204 this.props
.onOpenMedia(this.props
.media
, index
);
207 handleRef
= (node
) => {
208 if (node
&& this.isStandaloneEligible()) {
209 // offsetWidth triggers a layout, so only calculate when we need to
211 width: node
.offsetWidth
,
216 isStandaloneEligible() {
217 const { media
, standalone
} = this.props
;
218 return standalone
&& media
.size
=== 1 && media
.getIn([0, 'meta', 'small', 'aspect']);
222 const { media
, intl
, sensitive
, height
} = this.props
;
223 const { width
, visible
} = this.state
;
229 if (this.isStandaloneEligible()) {
230 if (!visible
&& width
) {
231 // only need to forcibly set the height in "sensitive" mode
232 style
.height
= width
/ this.props
.media
.getIn([0, 'meta', 'small', 'aspect']);
234 // layout automatically, using image's natural aspect ratio
239 style
.height
= height
;
246 warning
= <FormattedMessage id
='status.sensitive_warning' defaultMessage
='Sensitive content' />;
248 warning
= <FormattedMessage id
='status.media_hidden' defaultMessage
='Media hidden' />;
252 <button className
='media-spoiler' onClick
={this.handleOpen
} style
={style
} ref
={this.handleRef
}>
253 <span className
='media-spoiler__warning'>{warning
}</span
>
254 <span className
='media-spoiler__trigger'><FormattedMessage id
='status.sensitive_toggle' defaultMessage
='Click to view' /></span>
258 const size
= media
.take(4).size
;
260 if (this.isStandaloneEligible()) {
261 children
= <Item standalone onClick
={this.handleClick
} attachment
={media
.get(0)} />;
263 children
= media
.take(4).map((attachment
, i
) => <Item key
={attachment
.get('id')} onClick
={this.handleClick
} attachment
={attachment
} index
={i
} size
={size
} />);
268 <div className
='media-gallery' style
={style
}>
269 <div className
={classNames('spoiler-button', { 'spoiler-button--visible': visible
})}>
270 <IconButton title
={intl
.formatMessage(messages
.toggle_visible
)} icon
={visible
? 'eye' : 'eye-slash'} overlay onClick
={this.handleOpen
} />
This page took 0.20688 seconds and 4 git commands to generate.