diff options
author | Eugen Rochko <eugen@zeonfederated.com> | 2020-07-10 22:09:28 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-07-10 22:09:28 +0200 |
commit | 6cc5b822f5ca7df9b267ac2b5d24e2aac2dc0325 (patch) | |
tree | 645fe8d64ff5bddaac0e958290917032615d8e6d | |
parent | 96e89d1ef4cd79616eb6df3cf3ecad587a562515 (diff) |
Fix audio and video items in account gallery in web UI (#14282)
* Fix audio and video items in account gallery in web UI - Fix audio items not using thumbnails - Fix video items not using custom thumbnails - Fix video items autoplaying like GIFs * Change audio and video items in account gallery to autoplay when opened in web UI * Fix code style issue
5 files changed, 80 insertions, 53 deletions
diff --git a/app/javascript/mastodon/features/account_gallery/components/media_item.js b/app/javascript/mastodon/features/account_gallery/components/media_item.js index 9eb4ed0d3..c9a7af7f7 100644 --- a/app/javascript/mastodon/features/account_gallery/components/media_item.js +++ b/app/javascript/mastodon/features/account_gallery/components/media_item.js @@ -61,78 +61,82 @@ export default class MediaItem extends ImmutablePureComponent { const width = `${Math.floor((displayWidth - 4) / 3) - 4}px`; const height = width; const status = attachment.get('status'); - const title = status.get('spoiler_text') || attachment.get('description'); + const title = status.get('spoiler_text') || attachment.get('description'); - let thumbnail = ''; - let icon; + let thumbnail, label, icon, content; - if (attachment.get('type') === 'unknown') { - // Skip - } else if (attachment.get('type') === 'audio') { - thumbnail = ( + if (!visible) { + icon = ( <span className='account-gallery__item__icons'> - <Icon id='music' /> + <Icon id='eye-slash' /> </span> ); - } else if (attachment.get('type') === '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 - src={attachment.get('preview_url')} - alt={attachment.get('description')} - title={attachment.get('description')} - style={{ objectPosition: `${x}% ${y}%` }} - onLoad={this.handleImageLoad} - /> - ); - } else if (['gifv', 'video'].indexOf(attachment.get('type')) !== -1) { - const autoPlay = !isIOS() && autoPlayGif; - const label = attachment.get('type') === 'video' ? <Icon id='play' /> : 'GIF'; - - thumbnail = ( - <div className={classNames('media-gallery__gifv', { autoplay: autoPlay })}> + } else { + if (['audio', 'video'].includes(attachment.get('type'))) { + content = ( + <img + src={attachment.get('preview_url') || attachment.getIn(['account', 'avatar_static'])} + alt={attachment.get('description')} + onLoad={this.handleImageLoad} + /> + ); + + if (attachment.get('type') === 'audio') { + label = <Icon id='music' />; + } else { + label = <Icon id='play' />; + } + } else if (attachment.get('type') === '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; + + content = ( + <img + src={attachment.get('preview_url')} + alt={attachment.get('description')} + style={{ objectPosition: `${x}% ${y}%` }} + onLoad={this.handleImageLoad} + /> + ); + } else if (attachment.get('type') === 'gifv') { + content = ( <video className='media-gallery__item-gifv-thumbnail' aria-label={attachment.get('description')} - title={attachment.get('description')} role='application' src={attachment.get('url')} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} - autoPlay={autoPlay} + autoPlay={!isIOS() && autoPlayGif} loop muted /> + ); + + label = 'GIF'; + } + + thumbnail = ( + <div className='media-gallery__gifv'> + {content} <span className='media-gallery__gifv__label'>{label}</span> </div> ); } - if (!visible) { - icon = ( - <span className='account-gallery__item__icons'> - <Icon id='eye-slash' /> - </span> - ); - } - return ( <div className='account-gallery__item' style={{ width, height }}> <a className='media-gallery__item-thumbnail' href={status.get('url')} onClick={this.handleClick} title={title} target='_blank' rel='noopener noreferrer'> <Blurhash hash={attachment.get('blurhash')} - className={classNames('media-gallery__preview', { - 'media-gallery__preview--hidden': visible && loaded, - })} + className={classNames('media-gallery__preview', { 'media-gallery__preview--hidden': visible && loaded })} dummy={!useBlurhash} /> - {visible && thumbnail} - {!visible && icon} + + {visible ? thumbnail : icon} </a> </div> ); diff --git a/app/javascript/mastodon/features/account_gallery/index.js b/app/javascript/mastodon/features/account_gallery/index.js index de481075c..fc5aead48 100644 --- a/app/javascript/mastodon/features/account_gallery/index.js +++ b/app/javascript/mastodon/features/account_gallery/index.js @@ -101,9 +101,9 @@ class AccountGallery extends ImmutablePureComponent { handleOpenMedia = attachment => { if (attachment.get('type') === 'video') { - this.props.dispatch(openModal('VIDEO', { media: attachment, status: attachment.get('status') })); + this.props.dispatch(openModal('VIDEO', { media: attachment, status: attachment.get('status'), options: { autoPlay: true } })); } else if (attachment.get('type') === 'audio') { - this.props.dispatch(openModal('AUDIO', { media: attachment, status: attachment.get('status') })); + this.props.dispatch(openModal('AUDIO', { media: attachment, status: attachment.get('status'), options: { autoPlay: true } })); } else { const media = attachment.getIn(['status', 'media_attachments']); const index = media.findIndex(x => x.get('id') === attachment.get('id')); diff --git a/app/javascript/mastodon/features/audio/index.js b/app/javascript/mastodon/features/audio/index.js index a49489257..2f85ebb7e 100644 --- a/app/javascript/mastodon/features/audio/index.js +++ b/app/javascript/mastodon/features/audio/index.js @@ -37,6 +37,7 @@ class Audio extends React.PureComponent { backgroundColor: PropTypes.string, foregroundColor: PropTypes.string, accentColor: PropTypes.string, + autoPlay: PropTypes.bool, }; state = { @@ -259,6 +260,14 @@ class Audio extends React.PureComponent { this.setState({ hovered: false }); } + handleLoadedData = () => { + const { autoPlay } = this.props; + + if (autoPlay) { + this.audio.play(); + } + } + _initAudioContext () { const context = new AudioContext(); const source = context.createMediaElementSource(this.audio); @@ -336,7 +345,7 @@ class Audio extends React.PureComponent { } render () { - const { src, intl, alt, editable } = this.props; + const { src, intl, alt, editable, autoPlay } = this.props; const { paused, muted, volume, currentTime, duration, buffer, dragging } = this.state; const progress = (currentTime / duration) * 100; @@ -345,10 +354,11 @@ class Audio extends React.PureComponent { <audio src={src} ref={this.setAudioRef} - preload='none' + preload={autoPlay ? 'auto' : 'none'} onPlay={this.handlePlay} onPause={this.handlePause} onProgress={this.handleProgress} + onLoadedData={this.handleLoadedData} crossOrigin='anonymous' /> diff --git a/app/javascript/mastodon/features/ui/components/audio_modal.js b/app/javascript/mastodon/features/ui/components/audio_modal.js index dc033434e..a80776b22 100644 --- a/app/javascript/mastodon/features/ui/components/audio_modal.js +++ b/app/javascript/mastodon/features/ui/components/audio_modal.js @@ -2,17 +2,27 @@ import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import Audio from 'mastodon/features/audio'; +import { connect } from 'react-redux'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { FormattedMessage } from 'react-intl'; import { previewState } from './video_modal'; import classNames from 'classnames'; import Icon from 'mastodon/components/icon'; -export default class AudioModal extends ImmutablePureComponent { +const mapStateToProps = (state, { status }) => ({ + account: state.getIn(['accounts', status.get('account')]), +}); + +export default @connect(mapStateToProps) +class AudioModal extends ImmutablePureComponent { static propTypes = { media: ImmutablePropTypes.map.isRequired, status: ImmutablePropTypes.map, + options: PropTypes.shape({ + autoPlay: PropTypes.bool, + }), + account: ImmutablePropTypes.map, onClose: PropTypes.func.isRequired, }; @@ -50,7 +60,8 @@ export default class AudioModal extends ImmutablePureComponent { } render () { - const { media, status } = this.props; + const { media, status, account } = this.props; + const options = this.props.options || {}; return ( <div className='modal-root__modal audio-modal'> @@ -60,10 +71,11 @@ export default class AudioModal extends ImmutablePureComponent { alt={media.get('description')} duration={media.getIn(['meta', 'original', 'duration'], 0)} height={150} - poster={media.get('preview_url') || status.getIn(['account', 'avatar_static'])} + poster={media.get('preview_url') || account.get('avatar_static')} backgroundColor={media.getIn(['meta', 'colors', 'background'])} foregroundColor={media.getIn(['meta', 'colors', 'foreground'])} accentColor={media.getIn(['meta', 'colors', 'accent'])} + autoPlay={options.autoPlay} /> </div> diff --git a/app/javascript/mastodon/selectors/index.js b/app/javascript/mastodon/selectors/index.js index 673268c5a..fd3b72f96 100644 --- a/app/javascript/mastodon/selectors/index.js +++ b/app/javascript/mastodon/selectors/index.js @@ -154,12 +154,13 @@ export const makeGetNotification = () => { export const getAccountGallery = createSelector([ (state, id) => state.getIn(['timelines', `account:${id}:media`, 'items'], ImmutableList()), state => state.get('statuses'), -], (statusIds, statuses) => { + (state, id) => state.getIn(['accounts', id]), +], (statusIds, statuses, account) => { let medias = ImmutableList(); statusIds.forEach(statusId => { const status = statuses.get(statusId); - medias = medias.concat(status.get('media_attachments').map(media => media.set('status', status))); + medias = medias.concat(status.get('media_attachments').map(media => media.set('status', status).set('account', account))); }); return medias; |