Fix not being able to open audio modal in web UI (#15283)

Fix #15280

Also adds the new action bar and blurhash-based background
color to audio and video modals
main
Eugen Rochko 2020-12-07 04:29:37 +01:00 committed by GitHub
parent 59d943e152
commit a8c471fcc0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 157 additions and 157 deletions

View File

@ -0,0 +1,112 @@
const DIGIT_CHARACTERS = [
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
'K',
'L',
'M',
'N',
'O',
'P',
'Q',
'R',
'S',
'T',
'U',
'V',
'W',
'X',
'Y',
'Z',
'a',
'b',
'c',
'd',
'e',
'f',
'g',
'h',
'i',
'j',
'k',
'l',
'm',
'n',
'o',
'p',
'q',
'r',
's',
't',
'u',
'v',
'w',
'x',
'y',
'z',
'#',
'$',
'%',
'*',
'+',
',',
'-',
'.',
':',
';',
'=',
'?',
'@',
'[',
']',
'^',
'_',
'{',
'|',
'}',
'~',
];
export const decode83 = (str) => {
let value = 0;
let c, digit;
for (let i = 0; i < str.length; i++) {
c = str[i];
digit = DIGIT_CHARACTERS.indexOf(c);
value = value * 83 + digit;
}
return value;
};
export const intToRGB = int => ({
r: Math.max(0, (int >> 16)),
g: Math.max(0, (int >> 8) & 255),
b: Math.max(0, (int & 255)),
});
export const getAverageFromBlurhash = blurhash => {
if (!blurhash) {
return null;
}
return intToRGB(decode83(blurhash.slice(2, 6)));
};

View File

@ -192,8 +192,9 @@ class Status extends ImmutablePureComponent {
return <div className='audio-player' style={{ height: '110px' }} />; return <div className='audio-player' style={{ height: '110px' }} />;
} }
handleOpenVideo = (media, options) => { handleOpenVideo = (options) => {
this.props.onOpenVideo(this._properStatus().get('id'), media, options); const status = this._properStatus();
this.props.onOpenVideo(status.get('id'), status.getIn(['media_attachments', 0]), options);
} }
handleOpenMedia = (media, index) => { handleOpenMedia = (media, index) => {

View File

@ -58,8 +58,8 @@ class DetailedStatus extends ImmutablePureComponent {
e.stopPropagation(); e.stopPropagation();
} }
handleOpenVideo = (media, options) => { handleOpenVideo = (options) => {
this.props.onOpenVideo(media, options); this.props.onOpenVideo(this.props.status.getIn(['media_attachments', 0]), options);
} }
handleExpandedToggle = () => { handleExpandedToggle = () => {

View File

@ -4,13 +4,11 @@ import PropTypes from 'prop-types';
import Audio from 'mastodon/features/audio'; import Audio from 'mastodon/features/audio';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { FormattedMessage } from 'react-intl';
import { previewState } from './video_modal'; import { previewState } from './video_modal';
import classNames from 'classnames'; import Footer from 'mastodon/features/picture_in_picture/components/footer';
import Icon from 'mastodon/components/icon';
const mapStateToProps = (state, { status }) => ({ const mapStateToProps = (state, { statusId }) => ({
account: state.getIn(['accounts', status.get('account')]), accountStaticAvatar: state.getIn(['accounts', state.getIn(['statuses', statusId, 'account']), 'avatar_static']),
}); });
export default @connect(mapStateToProps) export default @connect(mapStateToProps)
@ -18,12 +16,13 @@ class AudioModal extends ImmutablePureComponent {
static propTypes = { static propTypes = {
media: ImmutablePropTypes.map.isRequired, media: ImmutablePropTypes.map.isRequired,
status: ImmutablePropTypes.map, statusId: PropTypes.string.isRequired,
accountStaticAvatar: PropTypes.string.isRequired,
options: PropTypes.shape({ options: PropTypes.shape({
autoPlay: PropTypes.bool, autoPlay: PropTypes.bool,
}), }),
account: ImmutablePropTypes.map,
onClose: PropTypes.func.isRequired, onClose: PropTypes.func.isRequired,
onChangeBackgroundColor: PropTypes.func.isRequired,
}; };
static contextTypes = { static contextTypes = {
@ -52,15 +51,8 @@ class AudioModal extends ImmutablePureComponent {
} }
} }
handleStatusClick = e => {
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
e.preventDefault();
this.context.router.history.push(`/statuses/${this.props.status.get('id')}`);
}
}
render () { render () {
const { media, status, account } = this.props; const { media, accountStaticAvatar, statusId, onClose } = this.props;
const options = this.props.options || {}; const options = this.props.options || {};
return ( return (
@ -71,7 +63,7 @@ class AudioModal extends ImmutablePureComponent {
alt={media.get('description')} alt={media.get('description')}
duration={media.getIn(['meta', 'original', 'duration'], 0)} duration={media.getIn(['meta', 'original', 'duration'], 0)}
height={150} height={150}
poster={media.get('preview_url') || account.get('avatar_static')} poster={media.get('preview_url') || accountStaticAvatar}
backgroundColor={media.getIn(['meta', 'colors', 'background'])} backgroundColor={media.getIn(['meta', 'colors', 'background'])}
foregroundColor={media.getIn(['meta', 'colors', 'foreground'])} foregroundColor={media.getIn(['meta', 'colors', 'foreground'])}
accentColor={media.getIn(['meta', 'colors', 'accent'])} accentColor={media.getIn(['meta', 'colors', 'accent'])}
@ -79,11 +71,9 @@ class AudioModal extends ImmutablePureComponent {
/> />
</div> </div>
{status && ( <div className='media-modal__overlay'>
<div className={classNames('media-modal__meta')}> {statusId && <Footer statusId={statusId} withOpenButton onClose={onClose} />}
<a href={status.get('url')} onClick={this.handleStatusClick}><Icon id='comments' /> <FormattedMessage id='lightbox.view_context' defaultMessage='View context' /></a>
</div> </div>
)}
</div> </div>
); );
} }

View File

@ -12,6 +12,7 @@ import Icon from 'mastodon/components/icon';
import GIFV from 'mastodon/components/gifv'; import GIFV from 'mastodon/components/gifv';
import { disableSwiping } from 'mastodon/initial_state'; import { disableSwiping } from 'mastodon/initial_state';
import Footer from 'mastodon/features/picture_in_picture/components/footer'; import Footer from 'mastodon/features/picture_in_picture/components/footer';
import { getAverageFromBlurhash } from 'mastodon/blurhash';
const messages = defineMessages({ const messages = defineMessages({
close: { id: 'lightbox.close', defaultMessage: 'Close' }, close: { id: 'lightbox.close', defaultMessage: 'Close' },
@ -21,111 +22,6 @@ const messages = defineMessages({
export const previewState = 'previewMediaModal'; export const previewState = 'previewMediaModal';
const digitCharacters = [
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
'K',
'L',
'M',
'N',
'O',
'P',
'Q',
'R',
'S',
'T',
'U',
'V',
'W',
'X',
'Y',
'Z',
'a',
'b',
'c',
'd',
'e',
'f',
'g',
'h',
'i',
'j',
'k',
'l',
'm',
'n',
'o',
'p',
'q',
'r',
's',
't',
'u',
'v',
'w',
'x',
'y',
'z',
'#',
'$',
'%',
'*',
'+',
',',
'-',
'.',
':',
';',
'=',
'?',
'@',
'[',
']',
'^',
'_',
'{',
'|',
'}',
'~',
];
const decode83 = (str) => {
let value = 0;
let c, digit;
for (let i = 0; i < str.length; i++) {
c = str[i];
digit = digitCharacters.indexOf(c);
value = value * 83 + digit;
}
return value;
};
const decodeRGB = int => ({
r: Math.max(0, (int >> 16)),
g: Math.max(0, (int >> 8) & 255),
b: Math.max(0, (int & 255)),
});
export default @injectIntl export default @injectIntl
class MediaModal extends ImmutablePureComponent { class MediaModal extends ImmutablePureComponent {
@ -224,7 +120,7 @@ class MediaModal extends ImmutablePureComponent {
const blurhash = media.getIn([index, 'blurhash']); const blurhash = media.getIn([index, 'blurhash']);
if (blurhash) { if (blurhash) {
const backgroundColor = decodeRGB(decode83(blurhash.slice(2, 6))); const backgroundColor = getAverageFromBlurhash(blurhash);
onChangeBackgroundColor(backgroundColor); onChangeBackgroundColor(backgroundColor);
} }
} }

View File

@ -3,6 +3,8 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Video from 'mastodon/features/video'; import Video from 'mastodon/features/video';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import Footer from 'mastodon/features/picture_in_picture/components/footer';
import { getAverageFromBlurhash } from 'mastodon/blurhash';
export const previewState = 'previewVideoModal'; export const previewState = 'previewVideoModal';
@ -17,6 +19,7 @@ export default class VideoModal extends ImmutablePureComponent {
defaultVolume: PropTypes.number, defaultVolume: PropTypes.number,
}), }),
onClose: PropTypes.func.isRequired, onClose: PropTypes.func.isRequired,
onChangeBackgroundColor: PropTypes.func.isRequired,
}; };
static contextTypes = { static contextTypes = {
@ -24,29 +27,35 @@ export default class VideoModal extends ImmutablePureComponent {
}; };
componentDidMount () { componentDidMount () {
if (this.context.router) { const { router } = this.context;
const history = this.context.router.history; const { media, onChangeBackgroundColor, onClose } = this.props;
history.push(history.location.pathname, previewState); if (router) {
router.history.push(router.history.location.pathname, previewState);
this.unlistenHistory = router.history.listen(() => onClose());
}
this.unlistenHistory = history.listen(() => { const backgroundColor = getAverageFromBlurhash(media.get('blurhash'));
this.props.onClose();
}); if (backgroundColor) {
onChangeBackgroundColor(backgroundColor);
} }
} }
componentWillUnmount () { componentWillUnmount () {
if (this.context.router) { const { router } = this.context;
if (router) {
this.unlistenHistory(); this.unlistenHistory();
if (this.context.router.history.location.state === previewState) { if (router.history.location.state === previewState) {
this.context.router.history.goBack(); router.history.goBack();
} }
} }
} }
render () { render () {
const { media, onClose } = this.props; const { media, statusId, onClose } = this.props;
const options = this.props.options || {}; const options = this.props.options || {};
return ( return (
@ -65,6 +74,10 @@ export default class VideoModal extends ImmutablePureComponent {
alt={media.get('description')} alt={media.get('description')}
/> />
</div> </div>
<div className='media-modal__overlay'>
{statusId && <Footer statusId={statusId} withOpenButton onClose={onClose} />}
</div>
</div> </div>
); );
} }

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { fromJS, is } from 'immutable'; import { is } from 'immutable';
import { throttle, debounce } from 'lodash'; import { throttle, debounce } from 'lodash';
import classNames from 'classnames'; import classNames from 'classnames';
import { isFullscreen, requestFullscreen, exitFullscreen } from '../ui/util/fullscreen'; import { isFullscreen, requestFullscreen, exitFullscreen } from '../ui/util/fullscreen';
@ -495,25 +495,13 @@ class Video extends React.PureComponent {
} }
handleOpenVideo = () => { handleOpenVideo = () => {
const { src, preview, width, height, alt } = this.props; this.video.pause();
const media = fromJS({ this.props.onOpenVideo({
type: 'video',
url: src,
preview_url: preview,
description: alt,
width,
height,
});
const options = {
startTime: this.video.currentTime, startTime: this.video.currentTime,
autoPlay: !this.state.paused, autoPlay: !this.state.paused,
defaultVolume: this.state.volume, defaultVolume: this.state.volume,
}; });
this.video.pause();
this.props.onOpenVideo(media, options);
} }
handleCloseVideo = () => { handleCloseVideo = () => {