Fix media editing modal changing dimensions when image loads (#12131)

lolsob-rspec
Eugen Rochko 2019-10-10 05:21:38 +02:00 committed by GitHub
parent cfb06d0565
commit 53afb61c84
5 changed files with 113 additions and 70 deletions

View File

@ -1,63 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
export default class ExtendedVideoPlayer extends React.PureComponent {
static propTypes = {
src: PropTypes.string.isRequired,
alt: PropTypes.string,
width: PropTypes.number,
height: PropTypes.number,
time: PropTypes.number,
controls: PropTypes.bool.isRequired,
muted: PropTypes.bool.isRequired,
onClick: PropTypes.func,
};
handleLoadedData = () => {
if (this.props.time) {
this.video.currentTime = this.props.time;
}
}
componentDidMount () {
this.video.addEventListener('loadeddata', this.handleLoadedData);
}
componentWillUnmount () {
this.video.removeEventListener('loadeddata', this.handleLoadedData);
}
setRef = (c) => {
this.video = c;
}
handleClick = e => {
e.stopPropagation();
const handler = this.props.onClick;
if (handler) handler();
}
render () {
const { src, muted, controls, alt } = this.props;
return (
<div className='extended-video-player'>
<video
ref={this.setRef}
src={src}
autoPlay
role='button'
tabIndex='0'
aria-label={alt}
title={alt}
muted={muted}
controls={controls}
loop={!controls}
onClick={this.handleClick}
/>
</div>
);
}
}

View File

@ -0,0 +1,75 @@
import React from 'react';
import PropTypes from 'prop-types';
export default class GIFV extends React.PureComponent {
static propTypes = {
src: PropTypes.string.isRequired,
alt: PropTypes.string,
width: PropTypes.number,
height: PropTypes.number,
onClick: PropTypes.func,
};
state = {
loading: true,
};
handleLoadedData = () => {
this.setState({ loading: false });
}
componentWillReceiveProps (nextProps) {
if (nextProps.src !== this.props.src) {
this.setState({ loading: true });
}
}
handleClick = e => {
const { onClick } = this.props;
if (onClick) {
e.stopPropagation();
onClick();
}
}
render () {
const { src, width, height, alt } = this.props;
const { loading } = this.state;
return (
<div className='gifv' style={{ position: 'relative' }}>
{loading && (
<canvas
width={width}
height={height}
role='button'
tabIndex='0'
aria-label={alt}
title={alt}
onClick={this.handleClick}
/>
)}
<video
src={src}
width={width}
height={height}
role='button'
tabIndex='0'
aria-label={alt}
title={alt}
muted
loop
autoPlay
playsInline
onClick={this.handleClick}
onLoadedData={this.handleLoadedData}
style={{ position: loading ? 'absolute' : 'static', top: 0, left: 0 }}
/>
</div>
);
}
}

View File

@ -16,6 +16,7 @@ import UploadProgress from 'mastodon/features/compose/components/upload_progress
import CharacterCounter from 'mastodon/features/compose/components/character_counter'; import CharacterCounter from 'mastodon/features/compose/components/character_counter';
import { length } from 'stringz'; import { length } from 'stringz';
import { Tesseract as fetchTesseract } from 'mastodon/features/ui/util/async-components'; import { Tesseract as fetchTesseract } from 'mastodon/features/ui/util/async-components';
import GIFV from 'mastodon/components/gifv';
const messages = defineMessages({ const messages = defineMessages({
close: { id: 'lightbox.close', defaultMessage: 'Close' }, close: { id: 'lightbox.close', defaultMessage: 'Close' },
@ -41,6 +42,36 @@ const removeExtraLineBreaks = str => str.replace(/\n\n/g, '******')
const assetHost = process.env.CDN_HOST || ''; const assetHost = process.env.CDN_HOST || '';
class ImageLoader extends React.PureComponent {
static propTypes = {
src: PropTypes.string.isRequired,
width: PropTypes.number,
height: PropTypes.number,
};
state = {
loading: true,
};
componentDidMount() {
const image = new Image();
image.addEventListener('load', () => this.setState({ loading: false }));
image.src = this.props.src;
}
render () {
const { loading } = this.state;
if (loading) {
return <canvas width={this.props.width} height={this.props.height} />;
} else {
return <img {...this.props} alt='' />;
}
}
}
export default @connect(mapStateToProps, mapDispatchToProps) export default @connect(mapStateToProps, mapDispatchToProps)
@injectIntl @injectIntl
class FocalPointModal extends ImmutablePureComponent { class FocalPointModal extends ImmutablePureComponent {
@ -60,6 +91,7 @@ class FocalPointModal extends ImmutablePureComponent {
description: '', description: '',
dirty: false, dirty: false,
progress: 0, progress: 0,
loading: true,
}; };
componentWillMount () { componentWillMount () {
@ -242,8 +274,8 @@ class FocalPointModal extends ImmutablePureComponent {
<div className='focal-point-modal__content'> <div className='focal-point-modal__content'>
{focals && ( {focals && (
<div className={classNames('focal-point', { dragging })} ref={this.setRef} onMouseDown={this.handleMouseDown} onTouchStart={this.handleTouchStart}> <div className={classNames('focal-point', { dragging })} ref={this.setRef} onMouseDown={this.handleMouseDown} onTouchStart={this.handleTouchStart}>
{media.get('type') === 'image' && <img src={media.get('url')} width={width} height={height} alt='' />} {media.get('type') === 'image' && <ImageLoader src={media.get('url')} width={width} height={height} alt='' />}
{media.get('type') === 'gifv' && <video src={media.get('url')} width={width} height={height} loop muted autoPlay />} {media.get('type') === 'gifv' && <GIFV src={media.get('url')} width={width} height={height} />}
<div className='focal-point__preview'> <div className='focal-point__preview'>
<strong><FormattedMessage id='upload_modal.preview_label' defaultMessage='Preview ({ratio})' values={{ ratio: '16:9' }} /></strong> <strong><FormattedMessage id='upload_modal.preview_label' defaultMessage='Preview ({ratio})' values={{ ratio: '16:9' }} /></strong>

View File

@ -3,13 +3,13 @@ import ReactSwipeableViews from 'react-swipeable-views';
import ImmutablePropTypes from 'react-immutable-proptypes'; 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 ExtendedVideoPlayer from 'mastodon/components/extended_video_player';
import classNames from 'classnames'; import classNames from 'classnames';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import IconButton from 'mastodon/components/icon_button'; import IconButton from 'mastodon/components/icon_button';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import ImageLoader from './image_loader'; import ImageLoader from './image_loader';
import Icon from 'mastodon/components/icon'; import Icon from 'mastodon/components/icon';
import GIFV from 'mastodon/components/gifv';
const messages = defineMessages({ const messages = defineMessages({
close: { id: 'lightbox.close', defaultMessage: 'Close' }, close: { id: 'lightbox.close', defaultMessage: 'Close' },
@ -169,10 +169,8 @@ class MediaModal extends ImmutablePureComponent {
); );
} else if (image.get('type') === 'gifv') { } else if (image.get('type') === 'gifv') {
return ( return (
<ExtendedVideoPlayer <GIFV
src={image.get('url')} src={image.get('url')}
muted
controls={false}
width={width} width={width}
height={height} height={height}
key={image.get('preview_url')} key={image.get('preview_url')}

View File

@ -6092,7 +6092,8 @@ noscript {
background: $base-shadow-color; background: $base-shadow-color;
img, img,
video { video,
canvas {
display: block; display: block;
max-height: 80vh; max-height: 80vh;
width: 100%; width: 100%;