Change media elements to use aspect-ratio rather than compute height themselves (#24686)
This commit is contained in:
		
					parent
					
						
							
								1eb51bd749
							
						
					
				
			
			
				commit
				
					
						598e63dad2
					
				
			
		
					 7 changed files with 40 additions and 136 deletions
				
			
		|  | @ -313,7 +313,7 @@ class MediaGallery extends React.PureComponent { | |||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { media, lang, intl, sensitive, height, defaultWidth, standalone, autoplay } = this.props; | ||||
|     const { media, lang, intl, sensitive, defaultWidth, standalone, autoplay } = this.props; | ||||
|     const { visible } = this.state; | ||||
|     const width = this.state.width || defaultWidth; | ||||
| 
 | ||||
|  | @ -322,13 +322,9 @@ class MediaGallery extends React.PureComponent { | |||
|     const style = {}; | ||||
| 
 | ||||
|     if (this.isFullSizeEligible() && (standalone || !cropImages)) { | ||||
|       if (width) { | ||||
|         style.height = width / this.props.media.getIn([0, 'meta', 'small', 'aspect']); | ||||
|       } | ||||
|     } else if (width) { | ||||
|       style.height = width / (16/9); | ||||
|       style.aspectRatio = `${this.props.media.getIn([0, 'meta', 'small', 'aspect'])}`; | ||||
|     } else { | ||||
|       style.height = height; | ||||
|       style.aspectRatio = '16 / 9'; | ||||
|     } | ||||
| 
 | ||||
|     const size     = media.take(4).size; | ||||
|  |  | |||
|  | @ -3,62 +3,22 @@ import PropTypes from 'prop-types'; | |||
| import Icon from 'mastodon/components/icon'; | ||||
| import { removePictureInPicture } from 'mastodon/actions/picture_in_picture'; | ||||
| import { connect } from 'react-redux'; | ||||
| import { debounce } from 'lodash'; | ||||
| import { FormattedMessage } from 'react-intl'; | ||||
| 
 | ||||
| class PictureInPicturePlaceholder extends React.PureComponent { | ||||
| 
 | ||||
|   static propTypes = { | ||||
|     width: PropTypes.number, | ||||
|     dispatch: PropTypes.func.isRequired, | ||||
|   }; | ||||
| 
 | ||||
|   state = { | ||||
|     width: this.props.width, | ||||
|     height: this.props.width && (this.props.width / (16/9)), | ||||
|   }; | ||||
| 
 | ||||
|   handleClick = () => { | ||||
|     const { dispatch } = this.props; | ||||
|     dispatch(removePictureInPicture()); | ||||
|   }; | ||||
| 
 | ||||
|   setRef = c => { | ||||
|     this.node = c; | ||||
| 
 | ||||
|     if (this.node) { | ||||
|       this._setDimensions(); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   _setDimensions () { | ||||
|     const width  = this.node.offsetWidth; | ||||
|     const height = width / (16/9); | ||||
| 
 | ||||
|     this.setState({ width, height }); | ||||
|   } | ||||
| 
 | ||||
|   componentDidMount () { | ||||
|     window.addEventListener('resize', this.handleResize, { passive: true }); | ||||
|   } | ||||
| 
 | ||||
|   componentWillUnmount () { | ||||
|     window.removeEventListener('resize', this.handleResize); | ||||
|   } | ||||
| 
 | ||||
|   handleResize = debounce(() => { | ||||
|     if (this.node) { | ||||
|       this._setDimensions(); | ||||
|     } | ||||
|   }, 250, { | ||||
|     trailing: true, | ||||
|   }); | ||||
| 
 | ||||
|   render () { | ||||
|     const { height } = this.state; | ||||
| 
 | ||||
|     return ( | ||||
|       <div ref={this.setRef} className='picture-in-picture-placeholder' style={{ height }} role='button' tabIndex={0} onClick={this.handleClick}> | ||||
|       <div className='picture-in-picture-placeholder' role='button' tabIndex={0} onClick={this.handleClick}> | ||||
|         <Icon id='window-restore' /> | ||||
|         <FormattedMessage id='picture_in_picture.restore' defaultMessage='Put it back' /> | ||||
|       </div> | ||||
|  |  | |||
|  | @ -411,7 +411,7 @@ class Status extends ImmutablePureComponent { | |||
|     } | ||||
| 
 | ||||
|     if (pictureInPicture.get('inUse')) { | ||||
|       media = <PictureInPicturePlaceholder width={this.props.cachedMediaWidth} />; | ||||
|       media = <PictureInPicturePlaceholder />; | ||||
|     } else if (status.get('media_attachments').size > 0) { | ||||
|       if (this.props.muted) { | ||||
|         media = ( | ||||
|  | @ -460,12 +460,9 @@ class Status extends ImmutablePureComponent { | |||
|                 src={attachment.get('url')} | ||||
|                 alt={attachment.get('description')} | ||||
|                 lang={status.get('language')} | ||||
|                 width={this.props.cachedMediaWidth} | ||||
|                 height={110} | ||||
|                 inline | ||||
|                 sensitive={status.get('sensitive')} | ||||
|                 onOpenVideo={this.handleOpenVideo} | ||||
|                 cacheWidth={this.props.cacheMediaWidth} | ||||
|                 deployPictureInPicture={pictureInPicture.get('available') ? this.handleDeployPictureInPicture : undefined} | ||||
|                 visible={this.state.showMedia} | ||||
|                 onToggleVisibility={this.handleToggleMediaVisibility} | ||||
|  | @ -498,8 +495,6 @@ class Status extends ImmutablePureComponent { | |||
|           onOpenMedia={this.handleOpenMedia} | ||||
|           card={status.get('card')} | ||||
|           compact | ||||
|           cacheWidth={this.props.cacheMediaWidth} | ||||
|           defaultWidth={this.props.cachedMediaWidth} | ||||
|           sensitive={status.get('sensitive')} | ||||
|         /> | ||||
|       ); | ||||
|  |  | |||
|  | @ -384,7 +384,7 @@ class Audio extends React.PureComponent { | |||
|   } | ||||
| 
 | ||||
|   _getRadius () { | ||||
|     return parseInt(((this.state.height || this.props.height) - (PADDING * this._getScaleCoefficient()) * 2) / 2); | ||||
|     return parseInt((this.state.height || this.props.height) / 2 - PADDING * this._getScaleCoefficient()); | ||||
|   } | ||||
| 
 | ||||
|   _getScaleCoefficient () { | ||||
|  | @ -396,7 +396,7 @@ class Audio extends React.PureComponent { | |||
|   } | ||||
| 
 | ||||
|   _getCY() { | ||||
|     return Math.floor(this._getRadius() + (PADDING * this._getScaleCoefficient())); | ||||
|     return Math.floor((this.state.height || this.props.height) / 2); | ||||
|   } | ||||
| 
 | ||||
|   _getAccentColor () { | ||||
|  | @ -470,7 +470,7 @@ class Audio extends React.PureComponent { | |||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|       <div className={classNames('audio-player', { editable, inactive: !revealed })} ref={this.setPlayerRef} style={{ backgroundColor: this._getBackgroundColor(), color: this._getForegroundColor(), height: this.props.fullscreen ? '100%' : (this.state.height || this.props.height) }} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} tabIndex={0} onKeyDown={this.handleKeyDown}> | ||||
|       <div className={classNames('audio-player', { editable, inactive: !revealed })} ref={this.setPlayerRef} style={{ backgroundColor: this._getBackgroundColor(), color: this._getForegroundColor(), aspectRatio: '16 / 9' }} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} tabIndex={0} onKeyDown={this.handleKeyDown}> | ||||
| 
 | ||||
|         <Blurhash | ||||
|           hash={blurhash} | ||||
|  | @ -515,9 +515,16 @@ class Audio extends React.PureComponent { | |||
|         {(revealed || editable) && <img | ||||
|           src={this.props.poster} | ||||
|           alt='' | ||||
|           width={(this._getRadius() - TICK_SIZE) * 2} | ||||
|           height={(this._getRadius() - TICK_SIZE) * 2} | ||||
|           style={{ position: 'absolute', left: this._getCX(), top: this._getCY(), transform: 'translate(-50%, -50%)', borderRadius: '50%', pointerEvents: 'none' }} | ||||
|           style={{ | ||||
|             position: 'absolute', | ||||
|             left: '50%', | ||||
|             top: '50%', | ||||
|             height: `calc(${(100 - 2 * 100 * PADDING / 982)}% - ${TICK_SIZE * 2}px)`, | ||||
|             aspectRatio: '1', | ||||
|             transform: 'translate(-50%, -50%)', | ||||
|             borderRadius: '50%', | ||||
|             pointerEvents: 'none', | ||||
|           }} | ||||
|         />} | ||||
| 
 | ||||
|         <div className='video-player__seek' onMouseDown={this.handleMouseDown} ref={this.setSeekRef}> | ||||
|  |  | |||
|  | @ -8,7 +8,6 @@ import classnames from 'classnames'; | |||
| import Icon from 'mastodon/components/icon'; | ||||
| import { useBlurhash } from 'mastodon/initial_state'; | ||||
| import Blurhash from 'mastodon/components/blurhash'; | ||||
| import { debounce } from 'lodash'; | ||||
| 
 | ||||
| const IDNA_PREFIX = 'xn--'; | ||||
| 
 | ||||
|  | @ -54,8 +53,6 @@ export default class Card extends React.PureComponent { | |||
|     card: ImmutablePropTypes.map, | ||||
|     onOpenMedia: PropTypes.func.isRequired, | ||||
|     compact: PropTypes.bool, | ||||
|     defaultWidth: PropTypes.number, | ||||
|     cacheWidth: PropTypes.func, | ||||
|     sensitive: PropTypes.bool, | ||||
|   }; | ||||
| 
 | ||||
|  | @ -64,7 +61,6 @@ export default class Card extends React.PureComponent { | |||
|   }; | ||||
| 
 | ||||
|   state = { | ||||
|     width: this.props.defaultWidth || 280, | ||||
|     previewLoaded: false, | ||||
|     embedded: false, | ||||
|     revealed: !this.props.sensitive, | ||||
|  | @ -87,24 +83,6 @@ export default class Card extends React.PureComponent { | |||
|     window.removeEventListener('resize', this.handleResize); | ||||
|   } | ||||
| 
 | ||||
|   _setDimensions () { | ||||
|     const width = this.node.offsetWidth; | ||||
| 
 | ||||
|     if (this.props.cacheWidth) { | ||||
|       this.props.cacheWidth(width); | ||||
|     } | ||||
| 
 | ||||
|     this.setState({ width }); | ||||
|   } | ||||
| 
 | ||||
|   handleResize = debounce(() => { | ||||
|     if (this.node) { | ||||
|       this._setDimensions(); | ||||
|     } | ||||
|   }, 250, { | ||||
|     trailing: true, | ||||
|   }); | ||||
| 
 | ||||
|   handlePhotoClick = () => { | ||||
|     const { card, onOpenMedia } = this.props; | ||||
| 
 | ||||
|  | @ -138,10 +116,6 @@ export default class Card extends React.PureComponent { | |||
| 
 | ||||
|   setRef = c => { | ||||
|     this.node = c; | ||||
| 
 | ||||
|     if (this.node) { | ||||
|       this._setDimensions(); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   handleImageLoad = () => { | ||||
|  | @ -157,36 +131,31 @@ export default class Card extends React.PureComponent { | |||
|   renderVideo () { | ||||
|     const { card }  = this.props; | ||||
|     const content   = { __html: addAutoPlay(card.get('html')) }; | ||||
|     const { width } = this.state; | ||||
|     const ratio     = card.get('width') / card.get('height'); | ||||
|     const height    = width / ratio; | ||||
| 
 | ||||
|     return ( | ||||
|       <div | ||||
|         ref={this.setRef} | ||||
|         className='status-card__image status-card-video' | ||||
|         dangerouslySetInnerHTML={content} | ||||
|         style={{ height }} | ||||
|         style={{ aspectRatio: `${card.get('width')} / ${card.get('height')}` }} | ||||
|       /> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { card, compact } = this.props; | ||||
|     const { width, embedded, revealed } = this.state; | ||||
|     const { embedded, revealed } = this.state; | ||||
| 
 | ||||
|     if (card === null) { | ||||
|       return null; | ||||
|     } | ||||
| 
 | ||||
|     const provider    = card.get('provider_name').length === 0 ? decodeIDNA(getHostname(card.get('url'))) : card.get('provider_name'); | ||||
|     const horizontal  = (!compact && card.get('width') > card.get('height') && (card.get('width') + 100 >= width)) || card.get('type') !== 'link' || embedded; | ||||
|     const horizontal  = (!compact && card.get('width') > card.get('height')) || card.get('type') !== 'link' || embedded; | ||||
|     const interactive = card.get('type') !== 'link'; | ||||
|     const className   = classnames('status-card', { horizontal, compact, interactive }); | ||||
|     const title       = interactive ? <a className='status-card__title' href={card.get('url')} title={card.get('title')} rel='noopener noreferrer' target='_blank'><strong>{card.get('title')}</strong></a> : <strong className='status-card__title' title={card.get('title')}>{card.get('title')}</strong>; | ||||
|     const language    = card.get('language') || ''; | ||||
|     const ratio       = card.get('width') / card.get('height'); | ||||
|     const height      = (compact && !embedded) ? (width / (16 / 9)) : (width / ratio); | ||||
| 
 | ||||
|     const description = ( | ||||
|       <div className='status-card__content' lang={language}> | ||||
|  | @ -196,6 +165,14 @@ export default class Card extends React.PureComponent { | |||
|       </div> | ||||
|     ); | ||||
| 
 | ||||
|     const thumbnailStyle = { | ||||
|       visibility: revealed? null : 'hidden', | ||||
|     }; | ||||
| 
 | ||||
|     if (horizontal) { | ||||
|       thumbnailStyle.aspectRatio = (compact && !embedded) ? '16 / 9' : `${card.get('width')} / ${card.get('height')}`; | ||||
|     } | ||||
| 
 | ||||
|     let embed     = ''; | ||||
|     let canvas = ( | ||||
|       <Blurhash | ||||
|  | @ -206,7 +183,7 @@ export default class Card extends React.PureComponent { | |||
|         dummy={!useBlurhash} | ||||
|       /> | ||||
|     ); | ||||
|     let thumbnail = <img src={card.get('image')} alt='' style={{ width: horizontal ? width : null, height: horizontal ? height : null, visibility: revealed ? null : 'hidden' }} onLoad={this.handleImageLoad} className='status-card__image-image' />; | ||||
|     let thumbnail = <img src={card.get('image')} alt='' style={thumbnailStyle} onLoad={this.handleImageLoad} className='status-card__image-image' />; | ||||
|     let spoilerButton = ( | ||||
|       <button type='button' onClick={this.handleReveal} className='spoiler-button__overlay'> | ||||
|         <span className='spoiler-button__overlay__label'><FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' /></span> | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ import React from 'react'; | |||
| import PropTypes from 'prop-types'; | ||||
| import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; | ||||
| import { is } from 'immutable'; | ||||
| import { throttle, debounce } from 'lodash'; | ||||
| import { throttle } from 'lodash'; | ||||
| import classNames from 'classnames'; | ||||
| import { isFullscreen, requestFullscreen, exitFullscreen } from '../ui/util/fullscreen'; | ||||
| import { displayMedia, useBlurhash } from '../../initial_state'; | ||||
|  | @ -102,8 +102,6 @@ class Video extends React.PureComponent { | |||
|     src: PropTypes.string.isRequired, | ||||
|     alt: PropTypes.string, | ||||
|     lang: PropTypes.string, | ||||
|     width: PropTypes.number, | ||||
|     height: PropTypes.number, | ||||
|     sensitive: PropTypes.bool, | ||||
|     currentTime: PropTypes.number, | ||||
|     onOpenVideo: PropTypes.func, | ||||
|  | @ -112,7 +110,6 @@ class Video extends React.PureComponent { | |||
|     inline: PropTypes.bool, | ||||
|     editable: PropTypes.bool, | ||||
|     alwaysVisible: PropTypes.bool, | ||||
|     cacheWidth: PropTypes.func, | ||||
|     visible: PropTypes.bool, | ||||
|     onToggleVisibility: PropTypes.func, | ||||
|     deployPictureInPicture: PropTypes.func, | ||||
|  | @ -135,7 +132,6 @@ class Video extends React.PureComponent { | |||
|     volume: 0.5, | ||||
|     paused: true, | ||||
|     dragging: false, | ||||
|     containerWidth: this.props.width, | ||||
|     fullscreen: false, | ||||
|     hovered: false, | ||||
|     muted: false, | ||||
|  | @ -144,24 +140,8 @@ class Video extends React.PureComponent { | |||
| 
 | ||||
|   setPlayerRef = c => { | ||||
|     this.player = c; | ||||
| 
 | ||||
|     if (this.player) { | ||||
|       this._setDimensions(); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   _setDimensions () { | ||||
|     const width = this.player.offsetWidth; | ||||
| 
 | ||||
|     if (this.props.cacheWidth) { | ||||
|       this.props.cacheWidth(width); | ||||
|     } | ||||
| 
 | ||||
|     this.setState({ | ||||
|       containerWidth: width, | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   setVideoRef = c => { | ||||
|     this.video = c; | ||||
| 
 | ||||
|  | @ -370,12 +350,10 @@ class Video extends React.PureComponent { | |||
|     document.addEventListener('MSFullscreenChange', this.handleFullscreenChange, true); | ||||
| 
 | ||||
|     window.addEventListener('scroll', this.handleScroll); | ||||
|     window.addEventListener('resize', this.handleResize, { passive: true }); | ||||
|   } | ||||
| 
 | ||||
|   componentWillUnmount () { | ||||
|     window.removeEventListener('scroll', this.handleScroll); | ||||
|     window.removeEventListener('resize', this.handleResize); | ||||
| 
 | ||||
|     document.removeEventListener('fullscreenchange', this.handleFullscreenChange, true); | ||||
|     document.removeEventListener('webkitfullscreenchange', this.handleFullscreenChange, true); | ||||
|  | @ -404,14 +382,6 @@ class Video extends React.PureComponent { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   handleResize = debounce(() => { | ||||
|     if (this.player) { | ||||
|       this._setDimensions(); | ||||
|     } | ||||
|   }, 250, { | ||||
|     trailing: true, | ||||
|   }); | ||||
| 
 | ||||
|   handleScroll = throttle(() => { | ||||
|     if (!this.video) { | ||||
|       return; | ||||
|  | @ -525,17 +495,12 @@ class Video extends React.PureComponent { | |||
| 
 | ||||
|   render () { | ||||
|     const { preview, src, inline, onOpenVideo, onCloseVideo, intl, alt, lang, detailed, sensitive, editable, blurhash, autoFocus } = this.props; | ||||
|     const { containerWidth, currentTime, duration, volume, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state; | ||||
|     const { currentTime, duration, volume, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state; | ||||
|     const progress = Math.min((currentTime / duration) * 100, 100); | ||||
|     const playerStyle = {}; | ||||
| 
 | ||||
|     let { width, height } = this.props; | ||||
| 
 | ||||
|     if (inline && containerWidth) { | ||||
|       width  = containerWidth; | ||||
|       height = containerWidth / (16/9); | ||||
| 
 | ||||
|       playerStyle.height = height; | ||||
|     if (inline) { | ||||
|       playerStyle.aspectRatio = '16 / 9'; | ||||
|     } | ||||
| 
 | ||||
|     let preload; | ||||
|  | @ -586,8 +551,6 @@ class Video extends React.PureComponent { | |||
|           aria-label={alt} | ||||
|           title={alt} | ||||
|           lang={lang} | ||||
|           width={width} | ||||
|           height={height} | ||||
|           volume={volume} | ||||
|           onClick={this.togglePlay} | ||||
|           onKeyDown={this.handleVideoKeyDown} | ||||
|  | @ -596,6 +559,7 @@ class Video extends React.PureComponent { | |||
|           onLoadedData={this.handleLoadedData} | ||||
|           onProgress={this.handleProgress} | ||||
|           onVolumeChange={this.handleVolumeChange} | ||||
|           style={{ ...playerStyle, width: '100%' }} | ||||
|         />} | ||||
| 
 | ||||
|         <div className={classNames('spoiler-button', { 'spoiler-button--hidden': revealed || editable })}> | ||||
|  |  | |||
|  | @ -3804,6 +3804,10 @@ a.status-card { | |||
| } | ||||
| 
 | ||||
| .status-card-video { | ||||
|   // Firefox has a bug where frameborder=0 iframes add some extra blank space | ||||
|   // see https://bugzilla.mozilla.org/show_bug.cgi?id=155174 | ||||
|   overflow: hidden; | ||||
| 
 | ||||
|   iframe { | ||||
|     width: 100%; | ||||
|     height: 100%; | ||||
|  | @ -8332,6 +8336,7 @@ noscript { | |||
|   font-weight: 500; | ||||
|   cursor: pointer; | ||||
|   color: $darker-text-color; | ||||
|   aspect-ratio: 16 / 9; | ||||
| 
 | ||||
|   i { | ||||
|     display: block; | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue