about summary refs log tree commit diff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/javascript/mastodon/components/media_gallery.js44
-rw-r--r--app/javascript/mastodon/features/audio/index.js52
-rw-r--r--app/javascript/mastodon/features/status/components/card.js33
-rw-r--r--app/javascript/mastodon/features/video/index.js30
-rw-r--r--app/javascript/styles/mastodon/components.scss19
5 files changed, 140 insertions, 38 deletions
diff --git a/app/javascript/mastodon/components/media_gallery.js b/app/javascript/mastodon/components/media_gallery.js
index a31de206b..0ec866138 100644
--- a/app/javascript/mastodon/components/media_gallery.js
+++ b/app/javascript/mastodon/components/media_gallery.js
@@ -8,10 +8,10 @@ import { isIOS } from '../is_mobile';
 import classNames from 'classnames';
 import { autoPlayGif, cropImages, displayMedia, useBlurhash } from '../initial_state';
 import { decode } from 'blurhash';
+import { debounce } from 'lodash';
 
 const messages = defineMessages({
-  toggle_visible: { id: 'media_gallery.toggle_visible',
-    defaultMessage: 'Hide {number, plural, one {image} other {images}}' },
+  toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Hide {number, plural, one {image} other {images}}' },
 });
 
 class Item extends React.PureComponent {
@@ -267,6 +267,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' });
@@ -275,6 +283,14 @@ class MediaGallery extends React.PureComponent {
     }
   }
 
+  handleResize = debounce(() => {
+    if (this.node) {
+      this._setDimensions();
+    }
+  }, 250, {
+    trailing: true,
+  });
+
   handleOpen = () => {
     if (this.props.onToggleVisibility) {
       this.props.onToggleVisibility();
@@ -287,15 +303,25 @@ class MediaGallery extends React.PureComponent {
     this.props.onOpenMedia(this.props.media, index);
   }
 
-  handleRef = (node) => {
-    if (node) {
-      // offsetWidth triggers a layout, so only calculate when we need to
-      if (this.props.cacheWidth) this.props.cacheWidth(node.offsetWidth);
+  handleRef = c => {
+    this.node = c;
+
+    if (this.node) {
+      this._setDimensions();
+    }
+  }
+
+  _setDimensions () {
+    const width = this.node.offsetWidth;
 
-      this.setState({
-        width: 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() {
diff --git a/app/javascript/mastodon/features/audio/index.js b/app/javascript/mastodon/features/audio/index.js
index cde2357ac..9da143c96 100644
--- a/app/javascript/mastodon/features/audio/index.js
+++ b/app/javascript/mastodon/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 'mastodon/features/video';
+import { debounce } from 'lodash';
 
 const digitCharacters = [
   '0',
@@ -172,16 +173,20 @@ class Audio extends React.PureComponent {
   setPlayerRef = c => {
     this.player = c;
 
-    if (c) {
-      const width  = c.offsetWidth;
-      const height = width / (16/9);
+    if (this.player) {
+      this._setDimensions();
+    }
+  }
 
-      if (this.props.cacheWidth) {
-        this.props.cacheWidth(width);
-      }
+  _setDimensions () {
+    const width  = this.player.offsetWidth;
+    const height = width / (16/9);
 
-      this.setState({ width, height });
+    if (this.props.cacheWidth) {
+      this.props.cacheWidth(width);
     }
+
+    this.setState({ width, height });
   }
 
   setSeekRef = c => {
@@ -214,6 +219,7 @@ class Audio extends React.PureComponent {
 
   componentDidMount () {
     window.addEventListener('scroll', this.handleScroll);
+    window.addEventListener('resize', this.handleResize, { passive: true });
 
     const img = new Image();
     img.crossOrigin = 'anonymous';
@@ -243,6 +249,7 @@ class Audio extends React.PureComponent {
 
   componentWillUnmount () {
     window.removeEventListener('scroll', this.handleScroll);
+    window.removeEventListener('resize', this.handleResize);
   }
 
   togglePlay = () => {
@@ -253,6 +260,14 @@ class Audio extends React.PureComponent {
     }
   }
 
+  handleResize = debounce(() => {
+    if (this.player) {
+      this._setDimensions();
+    }
+  }, 250, {
+    trailing: true,
+  });
+
   handlePlay = () => {
     this.setState({ paused: false });
 
@@ -564,14 +579,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);
 
@@ -590,6 +604,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})`;
   }
@@ -638,7 +660,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' }}
         />
 
         <div className='video-player__seek' onMouseDown={this.handleMouseDown} ref={this.setSeekRef}>
diff --git a/app/javascript/mastodon/features/status/components/card.js b/app/javascript/mastodon/features/status/components/card.js
index 630e99f2c..4442ab495 100644
--- a/app/javascript/mastodon/features/status/components/card.js
+++ b/app/javascript/mastodon/features/status/components/card.js
@@ -9,6 +9,7 @@ import Icon from 'mastodon/components/icon';
 import classNames from 'classnames';
 import { useBlurhash } from 'mastodon/initial_state';
 import { decode } from 'blurhash';
+import { debounce } from 'lodash';
 
 const IDNA_PREFIX = 'xn--';
 
@@ -92,13 +93,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();
     }
@@ -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 = () => {
     const { card, onOpenMedia } = this.props;
 
@@ -150,9 +176,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/mastodon/features/video/index.js b/app/javascript/mastodon/features/video/index.js
index 72c23bc0c..1f85375ff 100644
--- a/app/javascript/mastodon/features/video/index.js
+++ b/app/javascript/mastodon/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 '../ui/util/fullscreen';
 import { displayMedia, useBlurhash } from '../../initial_state';
@@ -136,13 +136,21 @@ class Video extends React.PureComponent {
   setPlayerRef = c => {
     this.player = c;
 
-    if (c) {
-      if (this.props.cacheWidth) this.props.cacheWidth(this.player.offsetWidth);
+    if (this.player) {
+      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 => {
@@ -268,6 +276,7 @@ class Video extends React.PureComponent {
     document.addEventListener('MSFullscreenChange', this.handleFullscreenChange, true);
 
     window.addEventListener('scroll', this.handleScroll);
+    window.addEventListener('resize', this.handleResize, { passive: true });
 
     if (this.props.blurhash) {
       this._decode();
@@ -276,6 +285,7 @@ class Video extends React.PureComponent {
 
   componentWillUnmount () {
     window.removeEventListener('scroll', this.handleScroll);
+    window.removeEventListener('resize', this.handleResize);
 
     document.removeEventListener('fullscreenchange', 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(() => {
     if (!this.video) {
       return;
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 4e084b6c3..fb9dca41b 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -5562,7 +5562,7 @@ a.status-card.compact:hover {
     &.active {
       overflow: visible;
       width: 50px;
-      margin-right: 10px;
+      margin-right: 16px;
     }
 
     &::before {
@@ -5599,10 +5599,17 @@ a.status-card.compact:hover {
       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;
     }
   }
 
@@ -5662,10 +5669,12 @@ a.status-card.compact:hover {
       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;