Merge branch 'master' into glitch-soc/merge-upstream
Conflicts: - `config/routes.rb`: Upstream disabled E2EE routes, which we did earlier, but slightly differently. Took upstream's version.
This commit is contained in:
@ -61,57 +61,9 @@ 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;
|
||||
|
||||
if (attachment.get('type') === 'unknown') {
|
||||
// Skip
|
||||
} else if (attachment.get('type') === 'audio') {
|
||||
thumbnail = (
|
||||
<span className='account-gallery__item__icons'>
|
||||
<Icon id='music' />
|
||||
</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 })}>
|
||||
<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}
|
||||
loop
|
||||
muted
|
||||
/>
|
||||
|
||||
<span className='media-gallery__gifv__label'>{label}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
let thumbnail, label, icon, content;
|
||||
|
||||
if (!visible) {
|
||||
icon = (
|
||||
@ -119,6 +71,60 @@ export default class MediaItem extends ImmutablePureComponent {
|
||||
<Icon id='eye-slash' />
|
||||
</span>
|
||||
);
|
||||
} 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')}
|
||||
role='application'
|
||||
src={attachment.get('url')}
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
autoPlay={!isIOS() && autoPlayGif}
|
||||
loop
|
||||
muted
|
||||
/>
|
||||
);
|
||||
|
||||
label = 'GIF';
|
||||
}
|
||||
|
||||
thumbnail = (
|
||||
<div className='media-gallery__gifv'>
|
||||
{content}
|
||||
|
||||
<span className='media-gallery__gifv__label'>{label}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@ -126,13 +132,11 @@ export default class MediaItem extends ImmutablePureComponent {
|
||||
<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>
|
||||
);
|
||||
|
@ -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'));
|
||||
|
@ -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'
|
||||
/>
|
||||
|
||||
|
@ -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>
|
||||
|
||||
|
@ -10,10 +10,15 @@ import DisplayName from '../../../components/display_name';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
import AttachmentList from 'mastodon/components/attachment_list';
|
||||
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
|
||||
@ -55,14 +60,24 @@ class BoostModal extends ImmutablePureComponent {
|
||||
const { status, 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'>
|
||||
|
Reference in New Issue
Block a user