Fix audio/video/images/cards not reacting to window resizes in web UI (#14130)

* Fix audio/video/images/cards not reacting to window resizes in web UI

* Update app/javascript/mastodon/features/audio/index.js

Co-authored-by: Yamagishi Kazutoshi <ykzts@desire.sh>

Co-authored-by: Yamagishi Kazutoshi <ykzts@desire.sh>
This commit is contained in:
Eugen Rochko 2020-06-24 10:25:32 +02:00 committed by GitHub
parent d469247083
commit bb9ca8a587
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 143 additions and 41 deletions

View file

@ -8,10 +8,10 @@ import { isIOS } from '../is_mobile';
import classNames from 'classnames'; import classNames from 'classnames';
import { autoPlayGif, cropImages, displayMedia, useBlurhash } from '../initial_state'; import { autoPlayGif, cropImages, displayMedia, useBlurhash } from '../initial_state';
import { decode } from 'blurhash'; import { decode } from 'blurhash';
import { debounce } from 'lodash';
const messages = defineMessages({ const messages = defineMessages({
toggle_visible: { id: 'media_gallery.toggle_visible', toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Hide {number, plural, one {image} other {images}}' },
defaultMessage: 'Hide {number, plural, one {image} other {images}}' },
}); });
class Item extends React.PureComponent { class Item extends React.PureComponent {
@ -267,6 +267,14 @@ class MediaGallery extends React.PureComponent {
width: this.props.defaultWidth, width: this.props.defaultWidth,
}; };
componentDidMount () {
window.addEventListener('resize', this.handleResize, { passive: true });
}
componentWillUnmount () {
window.removeEventListener('resize', this.handleResize);
}
componentWillReceiveProps (nextProps) { componentWillReceiveProps (nextProps) {
if (!is(nextProps.media, this.props.media) && nextProps.visible === undefined) { if (!is(nextProps.media, this.props.media) && nextProps.visible === undefined) {
this.setState({ visible: displayMedia !== 'hide_all' && !nextProps.sensitive || displayMedia === 'show_all' }); this.setState({ visible: displayMedia !== 'hide_all' && !nextProps.sensitive || displayMedia === 'show_all' });
@ -275,6 +283,14 @@ class MediaGallery extends React.PureComponent {
} }
} }
handleResize = debounce(() => {
if (this.node) {
this._setDimensions();
}
}, 250, {
trailing: true,
});
handleOpen = () => { handleOpen = () => {
if (this.props.onToggleVisibility) { if (this.props.onToggleVisibility) {
this.props.onToggleVisibility(); this.props.onToggleVisibility();
@ -287,17 +303,27 @@ class MediaGallery extends React.PureComponent {
this.props.onOpenMedia(this.props.media, index); this.props.onOpenMedia(this.props.media, index);
} }
handleRef = (node) => { handleRef = c => {
if (node) { this.node = c;
// offsetWidth triggers a layout, so only calculate when we need to
if (this.props.cacheWidth) this.props.cacheWidth(node.offsetWidth);
this.setState({ if (this.node) {
width: node.offsetWidth, this._setDimensions();
});
} }
} }
_setDimensions () {
const width = this.node.offsetWidth;
// offsetWidth triggers a layout, so only calculate when we need to
if (this.props.cacheWidth) {
this.props.cacheWidth(width);
}
this.setState({
width: width,
});
}
isFullSizeEligible() { isFullSizeEligible() {
const { media } = this.props; const { media } = this.props;
return media.size === 1 && media.getIn([0, 'meta', 'small', 'aspect']); return media.size === 1 && media.getIn([0, 'meta', 'small', 'aspect']);

View file

@ -7,6 +7,7 @@ import classNames from 'classnames';
import { throttle } from 'lodash'; import { throttle } from 'lodash';
import { encode, decode } from 'blurhash'; import { encode, decode } from 'blurhash';
import { getPointerPosition, fileNameFromURL } from 'mastodon/features/video'; import { getPointerPosition, fileNameFromURL } from 'mastodon/features/video';
import { debounce } from 'lodash';
const digitCharacters = [ const digitCharacters = [
'0', '0',
@ -172,18 +173,22 @@ class Audio extends React.PureComponent {
setPlayerRef = c => { setPlayerRef = c => {
this.player = c; this.player = c;
if (c) { if (this.player) {
const width = c.offsetWidth; this._setDimensions();
const height = width / (16/9);
if (this.props.cacheWidth) {
this.props.cacheWidth(width);
}
this.setState({ width, height });
} }
} }
_setDimensions () {
const width = this.player.offsetWidth;
const height = width / (16/9);
if (this.props.cacheWidth) {
this.props.cacheWidth(width);
}
this.setState({ width, height });
}
setSeekRef = c => { setSeekRef = c => {
this.seek = c; this.seek = c;
} }
@ -214,6 +219,7 @@ class Audio extends React.PureComponent {
componentDidMount () { componentDidMount () {
window.addEventListener('scroll', this.handleScroll); window.addEventListener('scroll', this.handleScroll);
window.addEventListener('resize', this.handleResize, { passive: true });
const img = new Image(); const img = new Image();
img.crossOrigin = 'anonymous'; img.crossOrigin = 'anonymous';
@ -243,6 +249,7 @@ class Audio extends React.PureComponent {
componentWillUnmount () { componentWillUnmount () {
window.removeEventListener('scroll', this.handleScroll); window.removeEventListener('scroll', this.handleScroll);
window.removeEventListener('resize', this.handleResize);
} }
togglePlay = () => { togglePlay = () => {
@ -253,6 +260,14 @@ class Audio extends React.PureComponent {
} }
} }
handleResize = debounce(() => {
if (this.player) {
this._setDimensions();
}
}, 250, {
trailing: true,
});
handlePlay = () => { handlePlay = () => {
this.setState({ paused: false }); this.setState({ paused: false });
@ -564,14 +579,13 @@ class Audio extends React.PureComponent {
} }
_drawTick (x1, y1, x2, y2) { _drawTick (x1, y1, x2, y2) {
const radius = this._getRadius(); const cx = this._getCX();
const cx = parseInt(this.state.width / 2); const cy = this._getCY();
const cy = parseInt(radius + (PADDING * this._getScaleCoefficient()));
const dx1 = parseInt(cx + x1); const dx1 = Math.ceil(cx + x1);
const dy1 = parseInt(cy + y1); const dy1 = Math.ceil(cy + y1);
const dx2 = parseInt(cx + x2); const dx2 = Math.ceil(cx + x2);
const dy2 = parseInt(cy + y2); const dy2 = Math.ceil(cy + y2);
const gradient = this.canvasContext.createLinearGradient(dx1, dy1, dx2, dy2); const gradient = this.canvasContext.createLinearGradient(dx1, dy1, dx2, dy2);
@ -590,6 +604,14 @@ class Audio extends React.PureComponent {
this.canvasContext.stroke(); this.canvasContext.stroke();
} }
_getCX() {
return Math.floor(this.state.width / 2);
}
_getCY() {
return Math.floor(this._getRadius() + (PADDING * this._getScaleCoefficient()));
}
_getColor () { _getColor () {
return `rgb(${this.state.color.r}, ${this.state.color.g}, ${this.state.color.b})`; return `rgb(${this.state.color.r}, ${this.state.color.g}, ${this.state.color.b})`;
} }
@ -638,7 +660,7 @@ class Audio extends React.PureComponent {
alt='' alt=''
width={(this._getRadius() - TICK_SIZE) * 2} width={(this._getRadius() - TICK_SIZE) * 2}
height={(this._getRadius() - TICK_SIZE) * 2} height={(this._getRadius() - TICK_SIZE) * 2}
style={{ position: 'absolute', left: parseInt(this.state.width / 2), top: parseInt(this._getRadius() + (PADDING * this._getScaleCoefficient())), transform: 'translate(-50%, -50%)', borderRadius: '50%', pointerEvents: 'none' }} style={{ position: 'absolute', left: this._getCX(), top: this._getCY(), 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

@ -9,6 +9,7 @@ import Icon from 'mastodon/components/icon';
import classNames from 'classnames'; import classNames from 'classnames';
import { useBlurhash } from 'mastodon/initial_state'; import { useBlurhash } from 'mastodon/initial_state';
import { decode } from 'blurhash'; import { decode } from 'blurhash';
import { debounce } from 'lodash';
const IDNA_PREFIX = 'xn--'; const IDNA_PREFIX = 'xn--';
@ -92,13 +93,20 @@ export default class Card extends React.PureComponent {
} }
componentDidMount () { componentDidMount () {
window.addEventListener('resize', this.handleResize, { passive: true });
if (this.props.card && this.props.card.get('blurhash')) { if (this.props.card && this.props.card.get('blurhash')) {
this._decode(); this._decode();
} }
} }
componentWillUnmount () {
window.removeEventListener('resize', this.handleResize);
}
componentDidUpdate (prevProps) { componentDidUpdate (prevProps) {
const { card } = this.props; const { card } = this.props;
if (card.get('blurhash') && (!prevProps.card || prevProps.card.get('blurhash') !== card.get('blurhash'))) { if (card.get('blurhash') && (!prevProps.card || prevProps.card.get('blurhash') !== card.get('blurhash'))) {
this._decode(); this._decode();
} }
@ -118,6 +126,24 @@ export default class Card extends React.PureComponent {
} }
} }
_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;
@ -150,9 +176,10 @@ export default class Card extends React.PureComponent {
} }
setRef = c => { setRef = c => {
if (c) { this.node = c;
if (this.props.cacheWidth) this.props.cacheWidth(c.offsetWidth);
this.setState({ width: c.offsetWidth }); if (this.node) {
this._setDimensions();
} }
} }

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 { fromJS, is } from 'immutable'; import { fromJS, is } from 'immutable';
import { throttle } from 'lodash'; import { throttle, debounce } 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 '../../initial_state'; import { displayMedia, useBlurhash } from '../../initial_state';
@ -136,15 +136,23 @@ class Video extends React.PureComponent {
setPlayerRef = c => { setPlayerRef = c => {
this.player = c; this.player = c;
if (c) { if (this.player) {
if (this.props.cacheWidth) this.props.cacheWidth(this.player.offsetWidth); this._setDimensions();
this.setState({
containerWidth: c.offsetWidth,
});
} }
} }
_setDimensions () {
const width = this.player.offsetWidth;
if (this.props.cacheWidth) {
this.props.cacheWidth(width);
}
this.setState({
containerWidth: width,
});
}
setVideoRef = c => { setVideoRef = c => {
this.video = c; this.video = c;
@ -268,6 +276,7 @@ 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 });
if (this.props.blurhash) { if (this.props.blurhash) {
this._decode(); this._decode();
@ -276,6 +285,7 @@ class Video extends React.PureComponent {
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);
@ -313,6 +323,14 @@ class Video extends React.PureComponent {
} }
} }
handleResize = debounce(() => {
if (this.player) {
this._setDimensions();
}
}, 250, {
trailing: true,
});
handleScroll = throttle(() => { handleScroll = throttle(() => {
if (!this.video) { if (!this.video) {
return; return;

View file

@ -5562,7 +5562,7 @@ a.status-card.compact:hover {
&.active { &.active {
overflow: visible; overflow: visible;
width: 50px; width: 50px;
margin-right: 10px; margin-right: 16px;
} }
&::before { &::before {
@ -5599,10 +5599,17 @@ a.status-card.compact:hover {
left: 0; left: 0;
margin-left: -6px; margin-left: -6px;
transform: translate(0, -50%); transform: translate(0, -50%);
transition: opacity .1s ease;
background: lighten($ui-highlight-color, 8%); background: lighten($ui-highlight-color, 8%);
box-shadow: 1px 2px 6px rgba($base-shadow-color, 0.2); box-shadow: 1px 2px 6px rgba($base-shadow-color, 0.2);
pointer-events: none; opacity: 0;
.no-reduce-motion & {
transition: opacity 100ms linear;
}
}
&.active &__handle {
opacity: 1;
} }
} }
@ -5662,10 +5669,12 @@ a.status-card.compact:hover {
height: 12px; height: 12px;
top: 6px; top: 6px;
margin-left: -6px; margin-left: -6px;
transition: opacity .1s ease;
background: lighten($ui-highlight-color, 8%); background: lighten($ui-highlight-color, 8%);
box-shadow: 1px 2px 6px rgba($base-shadow-color, 0.2); box-shadow: 1px 2px 6px rgba($base-shadow-color, 0.2);
pointer-events: none;
.no-reduce-motion & {
transition: opacity .1s ease;
}
&.active { &.active {
opacity: 1; opacity: 1;