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