fedibird-fe/app/javascript/mastodon/components/thumbnail_gallery.js
2023-01-16 12:58:29 +09:00

166 lines
4.7 KiB
JavaScript

import React, { Fragment } from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import { is } from 'immutable';
import classNames from 'classnames';
import { displayMedia, useBlurhash } from '../initial_state';
import Blurhash from 'mastodon/components/blurhash';
import { FormattedMessage } from 'react-intl';
class Item extends React.PureComponent {
static propTypes = {
attachment: ImmutablePropTypes.map.isRequired,
visible: PropTypes.bool.isRequired,
autoplay: PropTypes.bool,
};
state = {
loaded: false,
};
handleImageLoad = () => {
this.setState({ loaded: true });
}
render () {
const { attachment, visible } = this.props;
let thumbnail = '';
let typeLabel = '';
const id = attachment.get('id');
const type = attachment.get('type');
const hash = attachment.get('blurhash');
const preview = attachment.get('preview_url');
const description = attachment.get('description');
switch(type) {
case 'image':
const focusX = attachment.getIn(['meta', 'focus', 'x']) || 0;
const focusY = attachment.getIn(['meta', 'focus', 'y']) || 0;
const x = ((focusX / 2) + .5) * 100;
const y = ((focusY / -2) + .5) * 100;
thumbnail = (
<img
className='thumbnail-gallery__item-thumbnail'
src={preview}
alt={description}
title={description}
style={{ objectPosition: `${x}% ${y}%` }}
onLoad={this.handleImageLoad}
/>
);
break;
case 'gifv':
thumbnail = (
<div className='thumbnail-gallery__gifv'>
<video
className='thumbnail-gallery__item-gifv-thumbnail'
aria-label={description}
title={description}
role='application'
src={attachment.get('url')}
autoPlay={false}
muted
/>
</div>
);
typeLabel = <FormattedMessage id='thumbnail.type.gif' defaultMessage='(GIF)' />;
break;
case 'audio':
thumbnail = preview ? (
<img
className='thumbnail-gallery__item-thumbnail'
src={preview}
alt={description}
title={description}
onLoad={this.handleImageLoad}
/>
) : null;
typeLabel = <FormattedMessage id='thumbnail.type.audio' defaultMessage='(Audio)' />;
break;
case 'video':
thumbnail = (
<img
className='thumbnail-gallery__item-thumbnail'
src={preview}
alt={description}
title={description}
onLoad={this.handleImageLoad}
/>
);
typeLabel = <FormattedMessage id='thumbnail.type.video' defaultMessage='(Video)' />;
break;
default:
return hash ? (
<div className='thumbnail-gallery__item' key={id}>
<Blurhash
hash={hash}
className='thumbnail-gallery__preview'
dummy={!useBlurhash}
/>
</div>
) : null;
}
return (
<Fragment>
<div className='thumbnail-gallery__item' key={id}>
{hash && <Blurhash
hash={hash}
dummy={!useBlurhash}
className={classNames('thumbnail-gallery__preview', {
'thumbnail-gallery__preview--hidden': visible && this.state.loaded,
})}
/>}
{visible && thumbnail}
</div>
{typeLabel && <div className='thumbnail-gallery__type' key='type'>{typeLabel}</div>}
</Fragment>
);
}
}
export default
class ThumbnailGallery extends React.PureComponent {
static propTypes = {
sensitive: PropTypes.bool,
media: ImmutablePropTypes.list.isRequired,
visible: PropTypes.bool,
};
state = {
visible: this.props.visible !== undefined ? this.props.visible : (displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all'),
};
componentWillReceiveProps (nextProps) {
if (!is(nextProps.media, this.props.media) && nextProps.visible === undefined) {
this.setState({ visible: displayMedia !== 'hide_all' && !nextProps.sensitive || displayMedia === 'show_all' });
} else if (!is(nextProps.visible, this.props.visible) && nextProps.visible !== undefined) {
this.setState({ visible: nextProps.visible });
}
}
render () {
const { media } = this.props;
const { visible } = this.state;
const uncached = media.every(attachment => attachment.get('type') === 'unknown');
const children = media.take(4).map((attachment) => <Item key={attachment.get('id')} attachment={attachment} visible={visible || uncached} />);
return (
<div className='thumbnail-gallery' ref={this.handleRef}>
{children}
</div>
);
}
}