]> cat aescling's git repositories - mastodon.git/blob - app/javascript/mastodon/components/media_gallery.js
Fix size of single media in media-gallery (#5098)
[mastodon.git] / 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 sizeMe from 'react-sizeme';
10
11 const messages = defineMessages({
12 toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Toggle visibility' },
13 });
14
15 class Item extends React.PureComponent {
16
17 static contextTypes = {
18 router: PropTypes.object,
19 };
20
21 static propTypes = {
22 attachment: ImmutablePropTypes.map.isRequired,
23 standalone: PropTypes.bool,
24 index: PropTypes.number.isRequired,
25 size: PropTypes.number.isRequired,
26 onClick: PropTypes.func.isRequired,
27 autoPlayGif: PropTypes.bool,
28 };
29
30 static defaultProps = {
31 autoPlayGif: false,
32 standalone: false,
33 index: 0,
34 size: 1,
35 };
36
37 handleMouseEnter = (e) => {
38 if (this.hoverToPlay()) {
39 e.target.play();
40 }
41 }
42
43 handleMouseLeave = (e) => {
44 if (this.hoverToPlay()) {
45 e.target.pause();
46 e.target.currentTime = 0;
47 }
48 }
49
50 hoverToPlay () {
51 const { attachment, autoPlayGif } = this.props;
52 return !autoPlayGif && attachment.get('type') === 'gifv';
53 }
54
55 handleClick = (e) => {
56 const { index, onClick } = this.props;
57
58 if (this.context.router && e.button === 0) {
59 e.preventDefault();
60 onClick(index);
61 }
62
63 e.stopPropagation();
64 }
65
66 render () {
67 const { attachment, index, size, standalone } = this.props;
68
69 let width = 50;
70 let height = 100;
71 let top = 'auto';
72 let left = 'auto';
73 let bottom = 'auto';
74 let right = 'auto';
75
76 if (size === 1) {
77 width = 100;
78 }
79
80 if (size === 4 || (size === 3 && index > 0)) {
81 height = 50;
82 }
83
84 if (size === 2) {
85 if (index === 0) {
86 right = '2px';
87 } else {
88 left = '2px';
89 }
90 } else if (size === 3) {
91 if (index === 0) {
92 right = '2px';
93 } else if (index > 0) {
94 left = '2px';
95 }
96
97 if (index === 1) {
98 bottom = '2px';
99 } else if (index > 1) {
100 top = '2px';
101 }
102 } else if (size === 4) {
103 if (index === 0 || index === 2) {
104 right = '2px';
105 }
106
107 if (index === 1 || index === 3) {
108 left = '2px';
109 }
110
111 if (index < 2) {
112 bottom = '2px';
113 } else {
114 top = '2px';
115 }
116 }
117
118 let thumbnail = '';
119
120 if (attachment.get('type') === 'image') {
121 const previewUrl = attachment.get('preview_url');
122 const previewWidth = attachment.getIn(['meta', 'small', 'width']);
123
124 const originalUrl = attachment.get('url');
125 const originalWidth = attachment.getIn(['meta', 'original', 'width']);
126
127 const hasSize = typeof originalWidth === 'number' && typeof previewWidth === 'number';
128
129 const srcSet = hasSize ? `${originalUrl} ${originalWidth}w, ${previewUrl} ${previewWidth}w` : null;
130 const sizes = hasSize ? `(min-width: 1025px) ${320 * (width / 100)}px, ${width}vw` : null;
131
132 thumbnail = (
133 <a
134 className='media-gallery__item-thumbnail'
135 href={attachment.get('remote_url') || originalUrl}
136 onClick={this.handleClick}
137 target='_blank'
138 >
139 <img src={previewUrl} srcSet={srcSet} sizes={sizes} alt='' />
140 </a>
141 );
142 } else if (attachment.get('type') === 'gifv') {
143 const autoPlay = !isIOS() && this.props.autoPlayGif;
144
145 thumbnail = (
146 <div className={classNames('media-gallery__gifv', { autoplay: autoPlay })}>
147 <video
148 className='media-gallery__item-gifv-thumbnail'
149 role='application'
150 src={attachment.get('url')}
151 onClick={this.handleClick}
152 onMouseEnter={this.handleMouseEnter}
153 onMouseLeave={this.handleMouseLeave}
154 autoPlay={autoPlay}
155 loop
156 muted
157 />
158
159 <span className='media-gallery__gifv__label'>GIF</span>
160 </div>
161 );
162 }
163
164 return (
165 <div className={classNames('media-gallery__item', { standalone })} key={attachment.get('id')} style={{ left: left, top: top, right: right, bottom: bottom, width: `${width}%`, height: `${height}%` }}>
166 {thumbnail}
167 </div>
168 );
169 }
170
171 }
172
173 @injectIntl
174 @sizeMe({})
175 export default class MediaGallery extends React.PureComponent {
176
177 static propTypes = {
178 sensitive: PropTypes.bool,
179 standalone: PropTypes.bool,
180 media: ImmutablePropTypes.list.isRequired,
181 size: PropTypes.object,
182 height: PropTypes.number.isRequired,
183 onOpenMedia: PropTypes.func.isRequired,
184 intl: PropTypes.object.isRequired,
185 autoPlayGif: PropTypes.bool,
186 };
187
188 static defaultProps = {
189 autoPlayGif: false,
190 standalone: false,
191 };
192
193 state = {
194 visible: !this.props.sensitive,
195 };
196
197 componentWillReceiveProps (nextProps) {
198 if (!is(nextProps.media, this.props.media)) {
199 this.setState({ visible: !nextProps.sensitive });
200 }
201 }
202
203 handleOpen = () => {
204 this.setState({ visible: !this.state.visible });
205 }
206
207 handleClick = (index) => {
208 this.props.onOpenMedia(this.props.media, index);
209 }
210
211 render () {
212 const { media, intl, sensitive, height, standalone, size } = this.props;
213
214 let children;
215
216 const standaloneEligible = standalone && size.width && media.size === 1 && media.getIn([0, 'meta', 'small', 'aspect']);
217 const style = {};
218
219 if (standaloneEligible) {
220 style.height = size.width / media.getIn([0, 'meta', 'small', 'aspect']);
221 } else {
222 style.height = height;
223 }
224
225 if (!this.state.visible) {
226 let warning;
227
228 if (sensitive) {
229 warning = <FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' />;
230 } else {
231 warning = <FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' />;
232 }
233
234 children = (
235 <button className='media-spoiler' onClick={this.handleOpen} style={style}>
236 <span className='media-spoiler__warning'>{warning}</span>
237 <span className='media-spoiler__trigger'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
238 </button>
239 );
240 } else {
241 const size = media.take(4).size;
242
243 if (standaloneEligible) {
244 children = <Item standalone onClick={this.handleClick} attachment={media.get(0)} autoPlayGif={this.props.autoPlayGif} />;
245 } else {
246 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} />);
247 }
248 }
249
250 return (
251 <div className='media-gallery' style={style}>
252 <div className={classNames('spoiler-button', { 'spoiler-button--visible': this.state.visible })}>
253 <IconButton title={intl.formatMessage(messages.toggle_visible)} icon={this.state.visible ? 'eye' : 'eye-slash'} overlay onClick={this.handleOpen} />
254 </div>
255
256 {children}
257 </div>
258 );
259 }
260
261 }
This page took 0.103344 seconds and 4 git commands to generate.