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 { length } from 'stringz'; | ||||
| import { Tesseract as fetchTesseract } from 'mastodon/features/ui/util/async-components'; | ||||
| import GIFV from 'mastodon/components/gifv'; | ||||
| 
 | ||||
| const messages = defineMessages({ | ||||
|   close: { id: 'lightbox.close', defaultMessage: 'Close' }, | ||||
|  | @ -41,6 +42,36 @@ const removeExtraLineBreaks = str => str.replace(/\n\n/g, '******') | |||
| 
 | ||||
| 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) | ||||
| @injectIntl | ||||
| class FocalPointModal extends ImmutablePureComponent { | ||||
|  | @ -60,6 +91,7 @@ class FocalPointModal extends ImmutablePureComponent { | |||
|     description: '', | ||||
|     dirty: false, | ||||
|     progress: 0, | ||||
|     loading: true, | ||||
|   }; | ||||
| 
 | ||||
|   componentWillMount () { | ||||
|  | @ -242,8 +274,8 @@ class FocalPointModal extends ImmutablePureComponent { | |||
|           <div className='focal-point-modal__content'> | ||||
|             {focals && ( | ||||
|               <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') === 'gifv' && <video src={media.get('url')} width={width} height={height} loop muted autoPlay />} | ||||
|                 {media.get('type') === 'image' && <ImageLoader src={media.get('url')} width={width} height={height} alt='' />} | ||||
|                 {media.get('type') === 'gifv' && <GIFV src={media.get('url')} width={width} height={height} />} | ||||
| 
 | ||||
|                 <div className='focal-point__preview'> | ||||
|                   <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 PropTypes from 'prop-types'; | ||||
| import Video from 'mastodon/features/video'; | ||||
| import ExtendedVideoPlayer from 'mastodon/components/extended_video_player'; | ||||
| import classNames from 'classnames'; | ||||
| import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; | ||||
| import IconButton from 'mastodon/components/icon_button'; | ||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
| import ImageLoader from './image_loader'; | ||||
| import Icon from 'mastodon/components/icon'; | ||||
| import GIFV from 'mastodon/components/gifv'; | ||||
| 
 | ||||
| const messages = defineMessages({ | ||||
|   close: { id: 'lightbox.close', defaultMessage: 'Close' }, | ||||
|  | @ -169,10 +169,8 @@ class MediaModal extends ImmutablePureComponent { | |||
|         ); | ||||
|       } else if (image.get('type') === 'gifv') { | ||||
|         return ( | ||||
|           <ExtendedVideoPlayer | ||||
|           <GIFV | ||||
|             src={image.get('url')} | ||||
|             muted | ||||
|             controls={false} | ||||
|             width={width} | ||||
|             height={height} | ||||
|             key={image.get('preview_url')} | ||||
|  |  | |||
|  | @ -6092,7 +6092,8 @@ noscript { | |||
|   background: $base-shadow-color; | ||||
| 
 | ||||
|   img, | ||||
|   video { | ||||
|   video, | ||||
|   canvas { | ||||
|     display: block; | ||||
|     max-height: 80vh; | ||||
|     width: 100%; | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue