From 06309129be80c549aacf19a01e9b36bdcd47f9f2 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 24 Jun 2020 10:25:32 +0200 Subject: [PATCH] [Glitch] Fix audio/video/images/cards not reacting to window resizes in web UI Port bb9ca8a587ee5a3ec8778e72828aca0ba8871327 to glitch-soc Co-authored-by: Yamagishi Kazutoshi Co-authored-by: Yamagishi Kazutoshi Signed-off-by: Thibaut Girka --- .../glitch/components/media_gallery.js | 35 ++++++++++- .../flavours/glitch/features/audio/index.js | 60 +++++++++++++------ .../glitch/features/status/components/card.js | 33 +++++++++- .../flavours/glitch/features/video/index.js | 31 ++++++++-- .../glitch/styles/components/media.scss | 19 ++++-- 5 files changed, 146 insertions(+), 32 deletions(-) diff --git a/app/javascript/flavours/glitch/components/media_gallery.js b/app/javascript/flavours/glitch/components/media_gallery.js index 973406ad222..71240530ce5 100644 --- a/app/javascript/flavours/glitch/components/media_gallery.js +++ b/app/javascript/flavours/glitch/components/media_gallery.js @@ -8,6 +8,7 @@ import { isIOS } from 'flavours/glitch/util/is_mobile'; import classNames from 'classnames'; import { autoPlayGif, displayMedia, useBlurhash } from 'flavours/glitch/util/initial_state'; import { decode } from 'blurhash'; +import { debounce } from 'lodash'; const messages = defineMessages({ hidden: { @@ -289,6 +290,14 @@ class MediaGallery extends React.PureComponent { width: this.props.defaultWidth, }; + componentDidMount () { + window.addEventListener('resize', this.handleResize, { passive: true }); + } + + componentWillUnmount () { + window.removeEventListener('resize', this.handleResize); + } + componentWillReceiveProps (nextProps) { if (!is(nextProps.media, this.props.media) && nextProps.visible === undefined) { this.setState({ visible: displayMedia !== 'hide_all' && !nextProps.sensitive || displayMedia === 'show_all' }); @@ -305,6 +314,14 @@ class MediaGallery extends React.PureComponent { } } + handleResize = debounce(() => { + if (this.node) { + this._setDimensions(); + } + }, 250, { + trailing: true, + }); + handleOpen = () => { if (this.props.onToggleVisibility) { this.props.onToggleVisibility(); @@ -319,11 +336,23 @@ class MediaGallery extends React.PureComponent { handleRef = (node) => { this.node = node; - if (node && node.offsetWidth && node.offsetWidth != this.state.width) { - if (this.props.cacheWidth) this.props.cacheWidth(node.offsetWidth); + + if (this.node) { + this._setDimensions(); + } + } + + _setDimensions () { + const width = this.node.offsetWidth; + + if (width && width != this.state.width) { + // offsetWidth triggers a layout, so only calculate when we need to + if (this.props.cacheWidth) { + this.props.cacheWidth(width); + } this.setState({ - width: node.offsetWidth, + width: width, }); } } diff --git a/app/javascript/flavours/glitch/features/audio/index.js b/app/javascript/flavours/glitch/features/audio/index.js index 995586a24a3..9d81fbff75c 100644 --- a/app/javascript/flavours/glitch/features/audio/index.js +++ b/app/javascript/flavours/glitch/features/audio/index.js @@ -7,6 +7,7 @@ import classNames from 'classnames'; import { throttle } from 'lodash'; import { encode, decode } from 'blurhash'; import { getPointerPosition, fileNameFromURL } from 'flavours/glitch/features/video'; +import { debounce } from 'lodash'; const digitCharacters = [ '0', @@ -172,18 +173,22 @@ class Audio extends React.PureComponent { setPlayerRef = c => { this.player = c; - if (c) { - const width = c.offsetWidth; - const height = width / (16/9); - - if (this.props.cacheWidth) { - this.props.cacheWidth(width); - } - - this.setState({ width, height }); + if (this.player) { + this._setDimensions(); } } + _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 => { this.seek = c; } @@ -213,6 +218,8 @@ class Audio extends React.PureComponent { } componentDidMount () { + window.addEventListener('resize', this.handleResize, { passive: true }); + const img = new Image(); img.crossOrigin = 'anonymous'; img.onload = () => this.handlePosterLoad(img); @@ -239,6 +246,10 @@ class Audio extends React.PureComponent { this._draw(); } + componentWillUnmount () { + window.removeEventListener('resize', this.handleResize); + } + togglePlay = () => { if (this.state.paused) { this.setState({ paused: false }, () => this.audio.play()); @@ -247,6 +258,14 @@ class Audio extends React.PureComponent { } } + handleResize = debounce(() => { + if (this.player) { + this._setDimensions(); + } + }, 250, { + trailing: true, + }); + handlePlay = () => { this.setState({ paused: false }); @@ -545,14 +564,13 @@ class Audio extends React.PureComponent { } _drawTick (x1, y1, x2, y2) { - const radius = this._getRadius(); - const cx = parseInt(this.state.width / 2); - const cy = parseInt(radius + (PADDING * this._getScaleCoefficient())); + const cx = this._getCX(); + const cy = this._getCY(); - const dx1 = parseInt(cx + x1); - const dy1 = parseInt(cy + y1); - const dx2 = parseInt(cx + x2); - const dy2 = parseInt(cy + y2); + const dx1 = Math.ceil(cx + x1); + const dy1 = Math.ceil(cy + y1); + const dx2 = Math.ceil(cx + x2); + const dy2 = Math.ceil(cy + y2); const gradient = this.canvasContext.createLinearGradient(dx1, dy1, dx2, dy2); @@ -571,6 +589,14 @@ class Audio extends React.PureComponent { this.canvasContext.stroke(); } + _getCX() { + return Math.floor(this.state.width / 2); + } + + _getCY() { + return Math.floor(this._getRadius() + (PADDING * this._getScaleCoefficient())); + } + _getColor () { return `rgb(${this.state.color.r}, ${this.state.color.g}, ${this.state.color.b})`; } @@ -619,7 +645,7 @@ class Audio extends React.PureComponent { alt='' width={(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' }} />
diff --git a/app/javascript/flavours/glitch/features/status/components/card.js b/app/javascript/flavours/glitch/features/status/components/card.js index d2de225c0eb..4b66760628c 100644 --- a/app/javascript/flavours/glitch/features/status/components/card.js +++ b/app/javascript/flavours/glitch/features/status/components/card.js @@ -9,6 +9,7 @@ import { decode as decodeIDNA } from 'flavours/glitch/util/idna'; import Icon from 'flavours/glitch/components/icon'; import { useBlurhash } from 'flavours/glitch/util/initial_state'; import { decode } from 'blurhash'; +import { debounce } from 'lodash'; const getHostname = url => { const parser = document.createElement('a'); @@ -83,13 +84,20 @@ export default class Card extends React.PureComponent { } componentDidMount () { + window.addEventListener('resize', this.handleResize, { passive: true }); + if (this.props.card && this.props.card.get('blurhash')) { this._decode(); } } + componentWillUnmount () { + window.removeEventListener('resize', this.handleResize); + } + componentDidUpdate (prevProps) { const { card } = this.props; + if (card.get('blurhash') && (!prevProps.card || prevProps.card.get('blurhash') !== card.get('blurhash'))) { this._decode(); } @@ -109,6 +117,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 = () => { const { card, onOpenMedia } = this.props; @@ -141,9 +167,10 @@ export default class Card extends React.PureComponent { } setRef = c => { - if (c) { - if (this.props.cacheWidth) this.props.cacheWidth(c.offsetWidth); - this.setState({ width: c.offsetWidth }); + this.node = c; + + if (this.node) { + this._setDimensions(); } } diff --git a/app/javascript/flavours/glitch/features/video/index.js b/app/javascript/flavours/glitch/features/video/index.js index 747d03f8403..c7cb5bc4344 100644 --- a/app/javascript/flavours/glitch/features/video/index.js +++ b/app/javascript/flavours/glitch/features/video/index.js @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { fromJS, is } from 'immutable'; -import { throttle } from 'lodash'; +import { throttle, debounce } from 'lodash'; import classNames from 'classnames'; import { isFullscreen, requestFullscreen, exitFullscreen } from 'flavours/glitch/util/fullscreen'; import { displayMedia, useBlurhash } from 'flavours/glitch/util/initial_state'; @@ -144,10 +144,21 @@ class Video extends React.PureComponent { setPlayerRef = c => { this.player = c; - if (c && c.offsetWidth && c.offsetWidth != this.state.containerWidth) { - if (this.props.cacheWidth) this.props.cacheWidth(this.player.offsetWidth); + 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: c.offsetWidth, + containerWidth: width, }); } } @@ -293,12 +304,16 @@ class Video extends React.PureComponent { document.addEventListener('mozfullscreenchange', this.handleFullscreenChange, true); document.addEventListener('MSFullscreenChange', this.handleFullscreenChange, true); + window.addEventListener('resize', this.handleResize, { passive: true }); + if (this.props.blurhash) { this._decode(); } } componentWillUnmount () { + window.removeEventListener('resize', this.handleResize); + document.removeEventListener('fullscreenchange', this.handleFullscreenChange, true); document.removeEventListener('webkitfullscreenchange', this.handleFullscreenChange, true); document.removeEventListener('mozfullscreenchange', this.handleFullscreenChange, true); @@ -334,6 +349,14 @@ class Video extends React.PureComponent { } } + handleResize = debounce(() => { + if (this.player) { + this._setDimensions(); + } + }, 250, { + trailing: true, + }); + handleFullscreenChange = () => { this.setState({ fullscreen: isFullscreen() }); } diff --git a/app/javascript/flavours/glitch/styles/components/media.scss b/app/javascript/flavours/glitch/styles/components/media.scss index 88046a7a271..5f6eff80806 100644 --- a/app/javascript/flavours/glitch/styles/components/media.scss +++ b/app/javascript/flavours/glitch/styles/components/media.scss @@ -612,7 +612,7 @@ &.active { overflow: visible; width: 50px; - margin-right: 10px; + margin-right: 16px; } &::before { @@ -649,10 +649,17 @@ left: 0; margin-left: -6px; transform: translate(0, -50%); - transition: opacity .1s ease; background: lighten($ui-highlight-color, 8%); 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; } } @@ -712,10 +719,12 @@ height: 12px; top: 6px; margin-left: -6px; - transition: opacity .1s ease; background: lighten($ui-highlight-color, 8%); box-shadow: 1px 2px 6px rgba($base-shadow-color, 0.2); - pointer-events: none; + + .no-reduce-motion & { + transition: opacity .1s ease; + } &.active { opacity: 1;