Fix media editing modal changing dimensions when image loads (#12131)
This commit is contained in:
parent
915f3712ae
commit
6ebd74f4fa
5 changed files with 113 additions and 70 deletions
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
75
app/javascript/mastodon/components/gifv.js
Normal file
75
app/javascript/mastodon/components/gifv.js
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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>
|
||||||
|
|
|
@ -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')}
|
||||||
|
|
|
@ -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%;
|
||||||
|
|
Loading…
Reference in a new issue