diff options
author | Starfall <us@starfall.systems> | 2020-07-17 14:00:08 -0500 |
---|---|---|
committer | Starfall <us@starfall.systems> | 2020-07-17 14:00:08 -0500 |
commit | c5b7bf491e9aaf0a92f7899d60d12705d98436dc (patch) | |
tree | 7506b93ec2c20bf068f2b57c31d0a05e0485aed5 /app/javascript/flavours | |
parent | 2f20bc2a8275875033c97249825a2a3305980c3b (diff) | |
parent | 801e546ecc1efb4103232d8265229eb455a8dd5b (diff) |
glitch 3.2.0 update / Merge branch 'glitch' into main
Diffstat (limited to 'app/javascript/flavours')
8 files changed, 107 insertions, 49 deletions
diff --git a/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js b/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js index b88f23aa4..781bd4e03 100644 --- a/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js +++ b/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js @@ -61,73 +61,81 @@ 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 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> ); } - const 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 : icon} </a> </div> diff --git a/app/javascript/flavours/glitch/features/account_gallery/index.js b/app/javascript/flavours/glitch/features/account_gallery/index.js index f5fe6c930..040741c2a 100644 --- a/app/javascript/flavours/glitch/features/account_gallery/index.js +++ b/app/javascript/flavours/glitch/features/account_gallery/index.js @@ -113,9 +113,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/flavours/glitch/features/audio/index.js b/app/javascript/flavours/glitch/features/audio/index.js index 120a5ce1a..4e85e3c58 100644 --- a/app/javascript/flavours/glitch/features/audio/index.js +++ b/app/javascript/flavours/glitch/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 = { @@ -244,6 +245,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); @@ -274,6 +283,8 @@ class Audio extends React.PureComponent { _renderCanvas () { requestAnimationFrame(() => { + if (!this.audio) return; + this.handleTimeUpdate(); this._clear(); this._draw(); @@ -321,7 +332,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; @@ -330,10 +341,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/flavours/glitch/features/ui/components/audio_modal.js b/app/javascript/flavours/glitch/features/ui/components/audio_modal.js index f0c3b3bcc..f9d4bb2f3 100644 --- a/app/javascript/flavours/glitch/features/ui/components/audio_modal.js +++ b/app/javascript/flavours/glitch/features/ui/components/audio_modal.js @@ -2,16 +2,26 @@ import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import Audio from 'flavours/glitch/features/audio'; +import { connect } from 'react-redux'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { FormattedMessage } from 'react-intl'; import classNames from 'classnames'; import Icon from 'flavours/glitch/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, }; @@ -27,7 +37,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'> @@ -37,10 +48,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/flavours/glitch/features/ui/components/boost_modal.js b/app/javascript/flavours/glitch/features/ui/components/boost_modal.js index cd2929fdb..8092e862f 100644 --- a/app/javascript/flavours/glitch/features/ui/components/boost_modal.js +++ b/app/javascript/flavours/glitch/features/ui/components/boost_modal.js @@ -10,10 +10,15 @@ import DisplayName from 'flavours/glitch/components/display_name'; import AttachmentList from 'flavours/glitch/components/attachment_list'; import Icon from 'flavours/glitch/components/icon'; import ImmutablePureComponent from 'react-immutable-pure-component'; +import classNames from 'classnames'; const messages = defineMessages({ cancel_reblog: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' }, reblog: { id: 'status.reblog', defaultMessage: 'Boost' }, + public_short: { id: 'privacy.public.short', defaultMessage: 'Public' }, + unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' }, + private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' }, + direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' }, }); export default @injectIntl @@ -58,14 +63,24 @@ class BoostModal extends ImmutablePureComponent { const { status, missingMediaDescription, intl } = this.props; const buttonText = status.get('reblogged') ? messages.cancel_reblog : messages.reblog; + const visibilityIconInfo = { + 'public': { icon: 'globe', text: intl.formatMessage(messages.public_short) }, + 'unlisted': { icon: 'unlock', text: intl.formatMessage(messages.unlisted_short) }, + 'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) }, + 'direct': { icon: 'envelope', text: intl.formatMessage(messages.direct_short) }, + }; + + const visibilityIcon = visibilityIconInfo[status.get('visibility')]; + return ( <div className='modal-root__modal boost-modal'> <div className='boost-modal__container'> - <div className='status light'> + <div className={classNames('status', `status-${status.get('visibility')}`, 'light')}> <div className='boost-modal__status-header'> <div className='boost-modal__status-time'> <a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener noreferrer'><RelativeTimestamp timestamp={status.get('created_at')} /></a> </div> + <span className='status__visibility-icon'><Icon id={visibilityIcon.icon} title={visibilityIcon.text} /></span> <a onClick={this.handleAccountClick} href={status.getIn(['account', 'url'])} className='status__display-name'> <div className='status__avatar'> diff --git a/app/javascript/flavours/glitch/features/video/index.js b/app/javascript/flavours/glitch/features/video/index.js index 976cdefc0..cc60a0d2e 100644 --- a/app/javascript/flavours/glitch/features/video/index.js +++ b/app/javascript/flavours/glitch/features/video/index.js @@ -195,6 +195,8 @@ class Video extends React.PureComponent { _updateTime () { requestAnimationFrame(() => { + if (!this.video) return; + this.handleTimeUpdate(); if (!this.state.paused) { diff --git a/app/javascript/flavours/glitch/selectors/index.js b/app/javascript/flavours/glitch/selectors/index.js index 4a3303c36..bb9180d12 100644 --- a/app/javascript/flavours/glitch/selectors/index.js +++ b/app/javascript/flavours/glitch/selectors/index.js @@ -183,12 +183,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; diff --git a/app/javascript/flavours/glitch/styles/components/modal.scss b/app/javascript/flavours/glitch/styles/components/modal.scss index 4310f620a..d0be730ac 100644 --- a/app/javascript/flavours/glitch/styles/components/modal.scss +++ b/app/javascript/flavours/glitch/styles/components/modal.scss @@ -425,6 +425,14 @@ padding: initial; } + .status__visibility-icon { + color: $dark-text-color; + float: right; + font-size: 14px; + margin-left: 4px; + margin-right: 4px; + } + .status__display-name { display: flex; } |