[Glitch] Change media elements to use aspect-ratio rather than compute height themselves

Port 598e63dad2 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
main
Claire 2023-05-02 13:58:48 +02:00
parent 10f5329ddf
commit a8939e9098
8 changed files with 47 additions and 153 deletions

View File

@ -358,12 +358,10 @@ class MediaGallery extends React.PureComponent {
const computedClass = classNames('media-gallery', { 'full-width': fullwidth }); const computedClass = classNames('media-gallery', { 'full-width': fullwidth });
if (this.isStandaloneEligible() && width) { if (this.isStandaloneEligible()) { // TODO: cropImages setting
style.height = width / this.props.media.getIn([0, 'meta', 'small', 'aspect']); style.aspectRatio = `${this.props.media.getIn([0, 'meta', 'small', 'aspect'])}`;
} else if (width) {
style.height = width / (16/9);
} else { } else {
return (<div className={computedClass} ref={this.handleRef} />); style.aspectRatio = '16 / 9';
} }
if (this.isStandaloneEligible()) { if (this.isStandaloneEligible()) {

View File

@ -3,62 +3,22 @@ import PropTypes from 'prop-types';
import Icon from 'flavours/glitch/components/icon'; import Icon from 'flavours/glitch/components/icon';
import { removePictureInPicture } from 'flavours/glitch/actions/picture_in_picture'; import { removePictureInPicture } from 'flavours/glitch/actions/picture_in_picture';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { debounce } from 'lodash';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
class PictureInPicturePlaceholder extends React.PureComponent { class PictureInPicturePlaceholder extends React.PureComponent {
static propTypes = { static propTypes = {
width: PropTypes.number,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
}; };
state = {
width: this.props.width,
height: this.props.width && (this.props.width / (16/9)),
};
handleClick = () => { handleClick = () => {
const { dispatch } = this.props; const { dispatch } = this.props;
dispatch(removePictureInPicture()); 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 () { render () {
const { height } = this.state;
return ( 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' /> <Icon id='window-restore' />
<FormattedMessage id='picture_in_picture.restore' defaultMessage='Put it back' /> <FormattedMessage id='picture_in_picture.restore' defaultMessage='Put it back' />
</div> </div>

View File

@ -624,7 +624,7 @@ class Status extends ImmutablePureComponent {
attachments = status.get('media_attachments'); attachments = status.get('media_attachments');
if (pictureInPicture.get('inUse')) { if (pictureInPicture.get('inUse')) {
media.push(<PictureInPicturePlaceholder width={this.props.cachedMediaWidth} />); media.push(<PictureInPicturePlaceholder />);
mediaIcons.push('video-camera'); mediaIcons.push('video-camera');
} else if (attachments.size > 0) { } else if (attachments.size > 0) {
if (muted || attachments.some(item => item.get('type') === 'unknown')) { if (muted || attachments.some(item => item.get('type') === 'unknown')) {
@ -680,8 +680,6 @@ class Status extends ImmutablePureComponent {
fullwidth={!rootId && settings.getIn(['media', 'fullwidth'])} fullwidth={!rootId && settings.getIn(['media', 'fullwidth'])}
preventPlayback={isCollapsed || !isExpanded} preventPlayback={isCollapsed || !isExpanded}
onOpenVideo={this.handleOpenVideo} onOpenVideo={this.handleOpenVideo}
width={this.props.cachedMediaWidth}
cacheWidth={this.props.cacheMediaWidth}
deployPictureInPicture={pictureInPicture.get('available') ? this.handleDeployPictureInPicture : undefined} deployPictureInPicture={pictureInPicture.get('available') ? this.handleDeployPictureInPicture : undefined}
visible={this.state.showMedia} visible={this.state.showMedia}
onToggleVisibility={this.handleToggleMediaVisibility} onToggleVisibility={this.handleToggleMediaVisibility}
@ -721,8 +719,6 @@ class Status extends ImmutablePureComponent {
onOpenMedia={this.handleOpenMedia} onOpenMedia={this.handleOpenMedia}
card={status.get('card')} card={status.get('card')}
compact compact
cacheWidth={this.props.cacheMediaWidth}
defaultWidth={this.props.cachedMediaWidth}
sensitive={status.get('sensitive')} sensitive={status.get('sensitive')}
/>, />,
); );

View File

@ -390,7 +390,7 @@ class Audio extends React.PureComponent {
} }
_getRadius () { _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 () { _getScaleCoefficient () {
@ -402,7 +402,7 @@ class Audio extends React.PureComponent {
} }
_getCY() { _getCY() {
return Math.floor(this._getRadius() + (PADDING * this._getScaleCoefficient())); return Math.floor((this.state.height || this.props.height) / 2);
} }
_getAccentColor () { _getAccentColor () {
@ -476,7 +476,7 @@ class Audio extends React.PureComponent {
} }
return ( 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 <Blurhash
hash={blurhash} hash={blurhash}
@ -521,9 +521,16 @@ class Audio extends React.PureComponent {
{(revealed || editable) && <img {(revealed || editable) && <img
src={this.props.poster} src={this.props.poster}
alt='' alt=''
width={(this._getRadius() - TICK_SIZE) * 2} style={{
height={(this._getRadius() - TICK_SIZE) * 2} position: 'absolute',
style={{ position: 'absolute', left: this._getCX(), top: this._getCY(), transform: 'translate(-50%, -50%)', borderRadius: '50%', pointerEvents: 'none' }} 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}> <div className='video-player__seek' onMouseDown={this.handleMouseDown} ref={this.setSeekRef}>

View File

@ -8,7 +8,6 @@ import { decode as decodeIDNA } from 'flavours/glitch/utils/idna';
import Icon from 'flavours/glitch/components/icon'; import Icon from 'flavours/glitch/components/icon';
import { useBlurhash } from 'flavours/glitch/initial_state'; import { useBlurhash } from 'flavours/glitch/initial_state';
import Blurhash from 'flavours/glitch/components/blurhash'; import Blurhash from 'flavours/glitch/components/blurhash';
import { debounce } from 'lodash';
const getHostname = url => { const getHostname = url => {
const parser = document.createElement('a'); const parser = document.createElement('a');
@ -45,8 +44,6 @@ export default class Card extends React.PureComponent {
card: ImmutablePropTypes.map, card: ImmutablePropTypes.map,
onOpenMedia: PropTypes.func.isRequired, onOpenMedia: PropTypes.func.isRequired,
compact: PropTypes.bool, compact: PropTypes.bool,
defaultWidth: PropTypes.number,
cacheWidth: PropTypes.func,
sensitive: PropTypes.bool, sensitive: PropTypes.bool,
}; };
@ -55,7 +52,6 @@ export default class Card extends React.PureComponent {
}; };
state = { state = {
width: this.props.defaultWidth || 280,
previewLoaded: false, previewLoaded: false,
embedded: false, embedded: false,
revealed: !this.props.sensitive, revealed: !this.props.sensitive,
@ -78,24 +74,6 @@ export default class Card extends React.PureComponent {
window.removeEventListener('resize', this.handleResize); 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 = () => { handlePhotoClick = () => {
const { card, onOpenMedia } = this.props; const { card, onOpenMedia } = this.props;
@ -129,10 +107,6 @@ export default class Card extends React.PureComponent {
setRef = c => { setRef = c => {
this.node = c; this.node = c;
if (this.node) {
this._setDimensions();
}
}; };
handleImageLoad = () => { handleImageLoad = () => {
@ -148,36 +122,31 @@ export default class Card extends React.PureComponent {
renderVideo () { renderVideo () {
const { card } = this.props; const { card } = this.props;
const content = { __html: addAutoPlay(card.get('html')) }; const content = { __html: addAutoPlay(card.get('html')) };
const { width } = this.state;
const ratio = card.get('width') / card.get('height');
const height = width / ratio;
return ( return (
<div <div
ref={this.setRef} ref={this.setRef}
className='status-card__image status-card-video' className='status-card__image status-card-video'
dangerouslySetInnerHTML={content} dangerouslySetInnerHTML={content}
style={{ height }} style={{ aspectRatio: `${card.get('width')} / ${card.get('height')}` }}
/> />
); );
} }
render () { render () {
const { card, compact } = this.props; const { card, compact } = this.props;
const { width, embedded, revealed } = this.state; const { embedded, revealed } = this.state;
if (card === null) { if (card === null) {
return null; return null;
} }
const provider = card.get('provider_name').length === 0 ? decodeIDNA(getHostname(card.get('url'))) : card.get('provider_name'); 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 interactive = card.get('type') !== 'link';
const className = classnames('status-card', { horizontal, compact, interactive }); 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 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 language = card.get('language') || '';
const ratio = card.get('width') / card.get('height');
const height = (compact && !embedded) ? (width / (16 / 9)) : (width / ratio);
const description = ( const description = (
<div className='status-card__content' lang={language}> <div className='status-card__content' lang={language}>
@ -187,6 +156,14 @@ export default class Card extends React.PureComponent {
</div> </div>
); );
const thumbnailStyle = {
visibility: revealed? null : 'hidden',
};
if (horizontal) {
thumbnailStyle.aspectRatio = (compact && !embedded) ? '16 / 9' : `${card.get('width')} / ${card.get('height')}`;
}
let embed = ''; let embed = '';
let canvas = ( let canvas = (
<Blurhash <Blurhash
@ -197,7 +174,7 @@ export default class Card extends React.PureComponent {
dummy={!useBlurhash} 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 = ( let spoilerButton = (
<button type='button' onClick={this.handleReveal} className='spoiler-button__overlay'> <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> <span className='spoiler-button__overlay__label'><FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' /></span>

View File

@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { is } from 'immutable'; import { is } from 'immutable';
import { throttle, debounce } from 'lodash'; import { throttle } from 'lodash';
import classNames from 'classnames'; import classNames from 'classnames';
import { isFullscreen, requestFullscreen, exitFullscreen } from '../ui/util/fullscreen'; import { isFullscreen, requestFullscreen, exitFullscreen } from '../ui/util/fullscreen';
import { displayMedia, useBlurhash } from 'flavours/glitch/initial_state'; import { displayMedia, useBlurhash } from 'flavours/glitch/initial_state';
@ -102,8 +102,6 @@ class Video extends React.PureComponent {
src: PropTypes.string.isRequired, src: PropTypes.string.isRequired,
alt: PropTypes.string, alt: PropTypes.string,
lang: PropTypes.string, lang: PropTypes.string,
width: PropTypes.number,
height: PropTypes.number,
sensitive: PropTypes.bool, sensitive: PropTypes.bool,
currentTime: PropTypes.number, currentTime: PropTypes.number,
onOpenVideo: PropTypes.func, onOpenVideo: PropTypes.func,
@ -112,7 +110,6 @@ class Video extends React.PureComponent {
inline: PropTypes.bool, inline: PropTypes.bool,
editable: PropTypes.bool, editable: PropTypes.bool,
alwaysVisible: PropTypes.bool, alwaysVisible: PropTypes.bool,
cacheWidth: PropTypes.func,
visible: PropTypes.bool, visible: PropTypes.bool,
letterbox: PropTypes.bool, letterbox: PropTypes.bool,
fullwidth: PropTypes.bool, fullwidth: PropTypes.bool,
@ -138,41 +135,16 @@ class Video extends React.PureComponent {
volume: 0.5, volume: 0.5,
paused: true, paused: true,
dragging: false, dragging: false,
containerWidth: this.props.width,
fullscreen: false, fullscreen: false,
hovered: false, hovered: false,
muted: false, muted: false,
revealed: this.props.visible !== undefined ? this.props.visible : (displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all'), revealed: this.props.visible !== undefined ? this.props.visible : (displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all'),
}; };
componentWillReceiveProps (nextProps) {
if (!is(nextProps.visible, this.props.visible) && nextProps.visible !== undefined) {
this.setState({ revealed: nextProps.visible });
}
}
setPlayerRef = c => { setPlayerRef = c => {
this.player = c; this.player = c;
if (this.player) {
this._setDimensions();
}
}; };
_setDimensions () {
const width = this.player.offsetWidth;
if (width && width !== this.state.containerWidth) {
if (this.props.cacheWidth) {
this.props.cacheWidth(width);
}
this.setState({
containerWidth: width,
});
}
}
setVideoRef = c => { setVideoRef = c => {
this.video = c; this.video = c;
@ -381,12 +353,10 @@ class Video extends React.PureComponent {
document.addEventListener('MSFullscreenChange', this.handleFullscreenChange, true); document.addEventListener('MSFullscreenChange', this.handleFullscreenChange, true);
window.addEventListener('scroll', this.handleScroll); window.addEventListener('scroll', this.handleScroll);
window.addEventListener('resize', this.handleResize, { passive: true });
} }
componentWillUnmount () { componentWillUnmount () {
window.removeEventListener('scroll', this.handleScroll); window.removeEventListener('scroll', this.handleScroll);
window.removeEventListener('resize', this.handleResize);
document.removeEventListener('fullscreenchange', this.handleFullscreenChange, true); document.removeEventListener('fullscreenchange', this.handleFullscreenChange, true);
document.removeEventListener('webkitfullscreenchange', this.handleFullscreenChange, true); document.removeEventListener('webkitfullscreenchange', this.handleFullscreenChange, true);
@ -403,26 +373,18 @@ class Video extends React.PureComponent {
} }
} }
componentDidUpdate (prevProps) { componentWillReceiveProps (nextProps) {
if (this.player && this.player.offsetWidth && this.player.offsetWidth !== this.state.containerWidth && !this.state.fullscreen) { if (!is(nextProps.visible, this.props.visible) && nextProps.visible !== undefined) {
if (this.props.cacheWidth) this.props.cacheWidth(this.player.offsetWidth); this.setState({ revealed: nextProps.visible });
this.setState({
containerWidth: this.player.offsetWidth,
});
} }
}
componentDidUpdate (prevProps) {
if (this.video && this.state.revealed && this.props.preventPlayback && !prevProps.preventPlayback) { if (this.video && this.state.revealed && this.props.preventPlayback && !prevProps.preventPlayback) {
this.video.pause(); this.video.pause();
} }
} }
handleResize = debounce(() => {
if (this.player) {
this._setDimensions();
}
}, 250, {
trailing: true,
});
handleScroll = throttle(() => { handleScroll = throttle(() => {
if (!this.video) { if (!this.video) {
return; return;
@ -540,21 +502,12 @@ class Video extends React.PureComponent {
render () { render () {
const { preview, src, inline, onOpenVideo, onCloseVideo, intl, alt, lang, letterbox, fullwidth, detailed, sensitive, editable, blurhash, autoFocus } = this.props; const { preview, src, inline, onOpenVideo, onCloseVideo, intl, alt, lang, letterbox, fullwidth, 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 progress = Math.min((currentTime / duration) * 100, 100);
const playerStyle = {}; const playerStyle = {};
const computedClass = classNames('video-player', { inactive: !revealed, detailed, inline: inline && !fullscreen, fullscreen, editable, letterbox, 'full-width': fullwidth }); if (inline) {
playerStyle.aspectRatio = '16 / 9';
let { width, height } = this.props;
if (inline && containerWidth) {
width = containerWidth;
height = containerWidth / (16/9);
playerStyle.height = height;
} else if (inline) {
return (<div className={computedClass} ref={this.setPlayerRef} tabIndex={0} />);
} }
let preload; let preload;
@ -578,7 +531,7 @@ class Video extends React.PureComponent {
return ( return (
<div <div
role='menuitem' role='menuitem'
className={computedClass} className={classNames('video-player', { inactive: !revealed, detailed, inline: inline && !fullscreen, fullscreen, editable, letterbox, 'full-width': fullwidth })}
style={playerStyle} style={playerStyle}
ref={this.setPlayerRef} ref={this.setPlayerRef}
onMouseEnter={this.handleMouseEnter} onMouseEnter={this.handleMouseEnter}
@ -605,8 +558,6 @@ class Video extends React.PureComponent {
aria-label={alt} aria-label={alt}
title={alt} title={alt}
lang={lang} lang={lang}
width={width}
height={height}
volume={volume} volume={volume}
onClick={this.togglePlay} onClick={this.togglePlay}
onKeyDown={this.handleVideoKeyDown} onKeyDown={this.handleVideoKeyDown}
@ -615,6 +566,7 @@ class Video extends React.PureComponent {
onLoadedData={this.handleLoadedData} onLoadedData={this.handleLoadedData}
onProgress={this.handleProgress} onProgress={this.handleProgress}
onVolumeChange={this.handleVolumeChange} onVolumeChange={this.handleVolumeChange}
style={{ ...playerStyle, width: '100%' }}
/>} />}
<div className={classNames('spoiler-button', { 'spoiler-button--hidden': revealed || editable })}> <div className={classNames('spoiler-button', { 'spoiler-button--hidden': revealed || editable })}>

View File

@ -47,7 +47,6 @@
margin-right: -14px; margin-right: -14px;
width: inherit; width: inherit;
max-width: none; max-width: none;
height: 250px;
border-radius: 0; border-radius: 0;
} }
} }

View File

@ -808,6 +808,10 @@ a.status-card {
} }
.status-card-video { .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 { iframe {
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -1153,6 +1157,7 @@ a.status-card.compact:hover {
font-weight: 500; font-weight: 500;
cursor: pointer; cursor: pointer;
color: $darker-text-color; color: $darker-text-color;
aspect-ratio: 16 / 9;
i { i {
display: block; display: block;