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:
parent
d469247083
commit
bb9ca8a587
5 changed files with 143 additions and 41 deletions
|
@ -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']);
|
||||||
|
|
|
@ -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}>
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue