From 078744f210add1edc988142fad3815d4fb92de01 Mon Sep 17 00:00:00 2001 From: Mélanie Chauvel Date: Tue, 27 Oct 2020 03:00:47 +0100 Subject: [Glitch] Make visibility icon clickable as part of the time of a toot Port 1d07f51039625c2eafa7eb0b1b6d5a7f8cf00e41 to glitch-soc Signed-off-by: Thibaut Girka --- app/javascript/flavours/glitch/features/ui/components/boost_modal.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'app/javascript/flavours/glitch/features/ui/components') diff --git a/app/javascript/flavours/glitch/features/ui/components/boost_modal.js b/app/javascript/flavours/glitch/features/ui/components/boost_modal.js index 8092e862f..12ad426c8 100644 --- a/app/javascript/flavours/glitch/features/ui/components/boost_modal.js +++ b/app/javascript/flavours/glitch/features/ui/components/boost_modal.js @@ -78,9 +78,10 @@ class BoostModal extends ImmutablePureComponent {
- + + +
-
-- cgit From 40af1cf65bdbe9e6ef5f480b6e03cb1be7db5774 Mon Sep 17 00:00:00 2001 From: Mashiro Date: Tue, 3 Nov 2020 04:16:38 +0800 Subject: [Glitch] Add expand/compress image button on image view box Port 6a2db10f767879b9072b6c020ebadac4f985ed34 to glitch-soc Signed-off-by: Thibaut Girka --- .../glitch/features/ui/components/image_loader.js | 4 + .../glitch/features/ui/components/media_modal.js | 24 +- .../features/ui/components/zoomable_image.js | 299 +++++++++++++++++++-- .../flavours/glitch/styles/components/index.scss | 14 + .../flavours/glitch/styles/components/modal.scss | 15 ++ 5 files changed, 335 insertions(+), 21 deletions(-) (limited to 'app/javascript/flavours/glitch/features/ui/components') diff --git a/app/javascript/flavours/glitch/features/ui/components/image_loader.js b/app/javascript/flavours/glitch/features/ui/components/image_loader.js index 5e1cf75af..c6f16a792 100644 --- a/app/javascript/flavours/glitch/features/ui/components/image_loader.js +++ b/app/javascript/flavours/glitch/features/ui/components/image_loader.js @@ -13,6 +13,7 @@ export default class ImageLoader extends React.PureComponent { width: PropTypes.number, height: PropTypes.number, onClick: PropTypes.func, + zoomButtonHidden: PropTypes.bool, } static defaultProps = { @@ -151,6 +152,9 @@ export default class ImageLoader extends React.PureComponent { alt={alt} src={src} onClick={onClick} + width={width} + height={height} + zoomButtonHidden={this.props.zoomButtonHidden} /> )}
diff --git a/app/javascript/flavours/glitch/features/ui/components/media_modal.js b/app/javascript/flavours/glitch/features/ui/components/media_modal.js index aa6554107..e37df7208 100644 --- a/app/javascript/flavours/glitch/features/ui/components/media_modal.js +++ b/app/javascript/flavours/glitch/features/ui/components/media_modal.js @@ -35,23 +35,39 @@ class MediaModal extends ImmutablePureComponent { state = { index: null, navigationHidden: false, + zoomButtonHidden: false, }; handleSwipe = (index) => { this.setState({ index: index % this.props.media.size }); } + handleTransitionEnd = () => { + this.setState({ + zoomButtonHidden: false, + }); + } + handleNextClick = () => { - this.setState({ index: (this.getIndex() + 1) % this.props.media.size }); + this.setState({ + index: (this.getIndex() + 1) % this.props.media.size, + zoomButtonHidden: true, + }); } handlePrevClick = () => { - this.setState({ index: (this.props.media.size + this.getIndex() - 1) % this.props.media.size }); + this.setState({ + index: (this.props.media.size + this.getIndex() - 1) % this.props.media.size, + zoomButtonHidden: true, + }); } handleChangeIndex = (e) => { const index = Number(e.currentTarget.getAttribute('data-index')); - this.setState({ index: index % this.props.media.size }); + this.setState({ + index: index % this.props.media.size, + zoomButtonHidden: true, + }); } handleKeyDown = (e) => { @@ -128,6 +144,7 @@ class MediaModal extends ImmutablePureComponent { alt={image.get('description')} key={image.get('url')} onClick={this.toggleNavigation} + zoomButtonHidden={this.state.zoomButtonHidden} /> ); } else if (image.get('type') === 'video') { @@ -191,6 +208,7 @@ class MediaModal extends ImmutablePureComponent { style={swipeableViewsStyle} containerStyle={containerStyle} onChangeIndex={this.handleSwipe} + onTransitionEnd={this.handleTransitionEnd} index={index} > {content} diff --git a/app/javascript/flavours/glitch/features/ui/components/zoomable_image.js b/app/javascript/flavours/glitch/features/ui/components/zoomable_image.js index 3f6562bc9..004913480 100644 --- a/app/javascript/flavours/glitch/features/ui/components/zoomable_image.js +++ b/app/javascript/flavours/glitch/features/ui/components/zoomable_image.js @@ -1,8 +1,16 @@ import React from 'react'; import PropTypes from 'prop-types'; +import IconButton from 'flavours/glitch/components/icon_button'; +import { defineMessages, injectIntl } from 'react-intl'; + +const messages = defineMessages({ + compress: { id: 'lightbox.compress', defaultMessage: 'Compress image view box' }, + expand: { id: 'lightbox.expand', defaultMessage: 'Expand image view box' }, +}); const MIN_SCALE = 1; const MAX_SCALE = 4; +const NAV_BAR_HEIGHT = 66; const getMidpoint = (p1, p2) => ({ x: (p1.clientX + p2.clientX) / 2, @@ -14,7 +22,77 @@ const getDistance = (p1, p2) => const clamp = (min, max, value) => Math.min(max, Math.max(min, value)); -export default class ZoomableImage extends React.PureComponent { +// Normalizing mousewheel speed across browsers +// copy from: https://github.com/facebookarchive/fixed-data-table/blob/master/src/vendor_upstream/dom/normalizeWheel.js +const normalizeWheel = event => { + // Reasonable defaults + const PIXEL_STEP = 10; + const LINE_HEIGHT = 40; + const PAGE_HEIGHT = 800; + + let sX = 0, + sY = 0, // spinX, spinY + pX = 0, + pY = 0; // pixelX, pixelY + + // Legacy + if ('detail' in event) { + sY = event.detail; + } + if ('wheelDelta' in event) { + sY = -event.wheelDelta / 120; + } + if ('wheelDeltaY' in event) { + sY = -event.wheelDeltaY / 120; + } + if ('wheelDeltaX' in event) { + sX = -event.wheelDeltaX / 120; + } + + // side scrolling on FF with DOMMouseScroll + if ('axis' in event && event.axis === event.HORIZONTAL_AXIS) { + sX = sY; + sY = 0; + } + + pX = sX * PIXEL_STEP; + pY = sY * PIXEL_STEP; + + if ('deltaY' in event) { + pY = event.deltaY; + } + if ('deltaX' in event) { + pX = event.deltaX; + } + + if ((pX || pY) && event.deltaMode) { + if (event.deltaMode === 1) { // delta in LINE units + pX *= LINE_HEIGHT; + pY *= LINE_HEIGHT; + } else { // delta in PAGE units + pX *= PAGE_HEIGHT; + pY *= PAGE_HEIGHT; + } + } + + // Fall-back if spin cannot be determined + if (pX && !sX) { + sX = (pX < 1) ? -1 : 1; + } + if (pY && !sY) { + sY = (pY < 1) ? -1 : 1; + } + + return { + spinX: sX, + spinY: sY, + pixelX: pX, + pixelY: pY, + }; +}; + +export default @injectIntl +class ZoomableImage extends React.PureComponent { static propTypes = { alt: PropTypes.string, @@ -22,6 +100,8 @@ export default class ZoomableImage extends React.PureComponent { width: PropTypes.number, height: PropTypes.number, onClick: PropTypes.func, + zoomButtonHidden: PropTypes.bool, + intl: PropTypes.object.isRequired, } static defaultProps = { @@ -32,6 +112,22 @@ export default class ZoomableImage extends React.PureComponent { state = { scale: MIN_SCALE, + zoomMatrix: { + type: null, // 'full-width' 'full-height' + rate: null, // full screen scale rate + clientWidth: null, + clientHeight: null, + offsetWidth: null, + offsetHeight: null, + clientHeightFixed: null, + scrollTop: null, + scrollLeft: null, + }, + zoomState: 'expand', // 'expand' 'compress' + navigationHidden: false, + dragPosition: { top: 0, left: 0, x: 0, y: 0 }, + dragged: false, + lockScroll: { x: 0, y: 0 }, } removers = []; @@ -49,17 +145,101 @@ export default class ZoomableImage extends React.PureComponent { // https://www.chromestatus.com/features/5093566007214080 this.container.addEventListener('touchmove', handler, { passive: false }); this.removers.push(() => this.container.removeEventListener('touchend', handler)); + + handler = this.mouseDownHandler; + this.container.addEventListener('mousedown', handler); + this.removers.push(() => this.container.removeEventListener('mousedown', handler)); + + handler = this.mouseWheelHandler; + this.container.addEventListener('wheel', handler); + this.removers.push(() => this.container.removeEventListener('wheel', handler)); + // Old Chrome + this.container.addEventListener('mousewheel', handler); + this.removers.push(() => this.container.removeEventListener('mousewheel', handler)); + // Old Firefox + this.container.addEventListener('DOMMouseScroll', handler); + this.removers.push(() => this.container.removeEventListener('DOMMouseScroll', handler)); + + this.initZoomMatrix(); } componentWillUnmount () { this.removeEventListeners(); } + componentDidUpdate () { + if (this.props.zoomButtonHidden) { + this.setState({ scale: MIN_SCALE }, () => { + this.container.scrollLeft = 0; + this.container.scrollTop = 0; + }); + } + + this.setState({ zoomState: this.state.scale >= this.state.zoomMatrix.rate ? 'compress' : 'expand' }); + + if (this.state.scale === 1) { + this.container.style.removeProperty('cursor'); + } + } + removeEventListeners () { this.removers.forEach(listeners => listeners()); this.removers = []; } + mouseWheelHandler = e => { + e.preventDefault(); + + const event = normalizeWheel(e); + + if (this.state.zoomMatrix.type === 'full-width') { + // full width, scroll vertical + this.container.scrollTop = this.container.scrollTop + event.pixelY; + } else { + // full height, scroll horizontal + this.container.scrollLeft = this.container.scrollLeft + event.pixelY; + } + } + + mouseDownHandler = e => { + this.container.style.cursor = 'grabbing'; + this.container.style.userSelect = 'none'; + + this.setState({ dragPosition: { + left: this.container.scrollLeft, + top: this.container.scrollTop, + // Get the current mouse position + x: e.clientX, + y: e.clientY, + } }); + + this.image.addEventListener('mousemove', this.mouseMoveHandler); + this.image.addEventListener('mouseup', this.mouseUpHandler); + } + + mouseMoveHandler = e => { + const dx = e.clientX - this.state.dragPosition.x; + const dy = e.clientY - this.state.dragPosition.y; + + if ((this.state.dragPosition.left - dx) >= this.state.lockScroll.x) { + this.container.scrollLeft = this.state.dragPosition.left - dx; + } + + if ((this.state.dragPosition.top - dy) >= this.state.lockScroll.y) { + this.container.scrollTop = this.state.dragPosition.top - dy; + } + + this.setState({ dragged: true }); + } + + mouseUpHandler = () => { + this.container.style.cursor = 'grab'; + this.container.style.removeProperty('user-select'); + + this.image.removeEventListener('mousemove', this.mouseMoveHandler); + this.image.removeEventListener('mouseup', this.mouseUpHandler); + } + handleTouchStart = e => { if (e.touches.length !== 2) return; @@ -80,7 +260,8 @@ export default class ZoomableImage extends React.PureComponent { const distance = getDistance(...e.touches); const midpoint = getMidpoint(...e.touches); - const scale = clamp(MIN_SCALE, MAX_SCALE, this.state.scale * distance / this.lastDistance); + const _MAX_SCALE = Math.max(MAX_SCALE, this.state.zoomMatrix.rate); + const scale = clamp(MIN_SCALE, _MAX_SCALE, this.state.scale * distance / this.lastDistance); this.zoom(scale, midpoint); @@ -110,8 +291,72 @@ export default class ZoomableImage extends React.PureComponent { handleClick = e => { // don't propagate event to MediaModal e.stopPropagation(); + const dragged = this.state.dragged; + this.setState({ dragged: false }); + if (dragged) return; const handler = this.props.onClick; if (handler) handler(); + this.setState({ navigationHidden: !this.state.navigationHidden }); + } + + handleMouseDown = e => { + e.preventDefault(); + } + + initZoomMatrix = () => { + const { width, height } = this.props; + const { clientWidth, clientHeight } = this.container; + const { offsetWidth, offsetHeight } = this.image; + const clientHeightFixed = clientHeight - NAV_BAR_HEIGHT; + + const type = width/height < clientWidth / clientHeightFixed ? 'full-width' : 'full-height'; + const rate = type === 'full-width' ? clientWidth / offsetWidth : clientHeightFixed / offsetHeight; + const scrollTop = type === 'full-width' ? (clientHeight - offsetHeight) / 2 - NAV_BAR_HEIGHT : (clientHeightFixed - offsetHeight) / 2; + const scrollLeft = (clientWidth - offsetWidth) / 2; + + this.setState({ + zoomMatrix: { + type: type, + rate: rate, + clientWidth: clientWidth, + clientHeight: clientHeight, + offsetWidth: offsetWidth, + offsetHeight: offsetHeight, + clientHeightFixed: clientHeightFixed, + scrollTop: scrollTop, + scrollLeft: scrollLeft, + }, + }); + } + + handleZoomClick = e => { + e.preventDefault(); + e.stopPropagation(); + + const { scale, zoomMatrix } = this.state; + + if ( scale >= zoomMatrix.rate ) { + this.setState({ scale: MIN_SCALE }, () => { + this.container.scrollLeft = 0; + this.container.scrollTop = 0; + this.setState({ lockScroll: { + x: 0, + y: 0, + } }); + }); + } else { + this.setState({ scale: zoomMatrix.rate }, () => { + this.container.scrollLeft = zoomMatrix.scrollLeft; + this.container.scrollTop = zoomMatrix.scrollTop; + this.setState({ lockScroll: { + x: zoomMatrix.scrollLeft, + y: zoomMatrix.scrollTop, + } }); + }); + } + + this.container.style.cursor = 'grab'; + this.container.style.removeProperty('user-select'); } setContainerRef = c => { @@ -123,29 +368,47 @@ export default class ZoomableImage extends React.PureComponent { } render () { - const { alt, src } = this.props; + const { alt, src, width, height, intl } = this.props; const { scale } = this.state; const overflow = scale === 1 ? 'hidden' : 'scroll'; + const zoomButtonSshouldHide = !this.state.navigationHidden && !this.props.zoomButtonHidden ? '' : 'media-modal__zoom-button--hidden'; + const zoomButtonTitle = this.state.zoomState === 'compress' ? intl.formatMessage(messages.compress) : intl.formatMessage(messages.expand); return ( -
- {alt} + -
+
+ {alt} +
+ ); } diff --git a/app/javascript/flavours/glitch/styles/components/index.scss b/app/javascript/flavours/glitch/styles/components/index.scss index 0614278e2..3e67754c5 100644 --- a/app/javascript/flavours/glitch/styles/components/index.scss +++ b/app/javascript/flavours/glitch/styles/components/index.scss @@ -425,6 +425,20 @@ align-items: center; justify-content: center; flex-direction: column; + scrollbar-width: none; /* Firefox */ + -ms-overflow-style: none; /* IE 10+ */ + + * { + scrollbar-width: none; /* Firefox */ + -ms-overflow-style: none; /* IE 10+ */ + } + + &::-webkit-scrollbar, + *::-webkit-scrollbar { + width: 0; + height: 0; + background: transparent; /* Chrome/Safari/Webkit */ + } .image-loader__preview-canvas { max-width: $media-modal-media-max-width; diff --git a/app/javascript/flavours/glitch/styles/components/modal.scss b/app/javascript/flavours/glitch/styles/components/modal.scss index 7abe8818b..bc0965864 100644 --- a/app/javascript/flavours/glitch/styles/components/modal.scss +++ b/app/javascript/flavours/glitch/styles/components/modal.scss @@ -40,6 +40,21 @@ z-index: 9999; } +.media-modal__zoom-button { + position: absolute; + right: 64px; + top: 8px; + z-index: 100; + pointer-events: auto; + transition: opacity 0.3s linear; + will-change: opacity; +} + +.media-modal__zoom-button--hidden { + pointer-events: none; + opacity: 0; +} + .onboarding-modal, .error-modal, .embed-modal { -- cgit From dc86d814d93b9bb564ab35728c6d9fb65d064ca3 Mon Sep 17 00:00:00 2001 From: Mashiro Date: Tue, 3 Nov 2020 13:06:45 +0800 Subject: [Glitch] add mouse scroll lock in image expand view Port f645dad661ba033efdcdd6ca7597e7b8bc7f4f51 to glitch-soc Signed-off-by: Thibaut Girka --- .../features/ui/components/zoomable_image.js | 40 ++++++++++++---------- 1 file changed, 21 insertions(+), 19 deletions(-) (limited to 'app/javascript/flavours/glitch/features/ui/components') diff --git a/app/javascript/flavours/glitch/features/ui/components/zoomable_image.js b/app/javascript/flavours/glitch/features/ui/components/zoomable_image.js index 004913480..2efc70890 100644 --- a/app/javascript/flavours/glitch/features/ui/components/zoomable_image.js +++ b/app/javascript/flavours/glitch/features/ui/components/zoomable_image.js @@ -194,11 +194,14 @@ class ZoomableImage extends React.PureComponent { if (this.state.zoomMatrix.type === 'full-width') { // full width, scroll vertical - this.container.scrollTop = this.container.scrollTop + event.pixelY; + this.container.scrollTop = Math.max(this.container.scrollTop + event.pixelY, this.state.lockScroll.y); } else { // full height, scroll horizontal - this.container.scrollLeft = this.container.scrollLeft + event.pixelY; + this.container.scrollLeft = Math.max(this.container.scrollLeft + event.pixelY, this.state.lockScroll.x); } + + // lock horizontal scroll + this.container.scrollLeft = Math.max(this.container.scrollLeft + event.pixelX, this.state.lockScroll.x); } mouseDownHandler = e => { @@ -221,13 +224,8 @@ class ZoomableImage extends React.PureComponent { const dx = e.clientX - this.state.dragPosition.x; const dy = e.clientY - this.state.dragPosition.y; - if ((this.state.dragPosition.left - dx) >= this.state.lockScroll.x) { - this.container.scrollLeft = this.state.dragPosition.left - dx; - } - - if ((this.state.dragPosition.top - dy) >= this.state.lockScroll.y) { - this.container.scrollTop = this.state.dragPosition.top - dy; - } + this.container.scrollLeft = Math.max(this.state.dragPosition.left - dx, this.state.lockScroll.x); + this.container.scrollTop = Math.max(this.state.dragPosition.top - dy, this.state.lockScroll.y); this.setState({ dragged: true }); } @@ -336,22 +334,26 @@ class ZoomableImage extends React.PureComponent { const { scale, zoomMatrix } = this.state; if ( scale >= zoomMatrix.rate ) { - this.setState({ scale: MIN_SCALE }, () => { - this.container.scrollLeft = 0; - this.container.scrollTop = 0; - this.setState({ lockScroll: { + this.setState({ + scale: MIN_SCALE, + lockScroll: { x: 0, y: 0, - } }); + }, + }, () => { + this.container.scrollLeft = 0; + this.container.scrollTop = 0; }); } else { - this.setState({ scale: zoomMatrix.rate }, () => { - this.container.scrollLeft = zoomMatrix.scrollLeft; - this.container.scrollTop = zoomMatrix.scrollTop; - this.setState({ lockScroll: { + this.setState({ + scale: zoomMatrix.rate, + lockScroll: { x: zoomMatrix.scrollLeft, y: zoomMatrix.scrollTop, - } }); + }, + }, () => { + this.container.scrollLeft = zoomMatrix.scrollLeft; + this.container.scrollTop = zoomMatrix.scrollTop; }); } -- cgit From 91c2f14fd2050855ef6c89cde14f41406a73b316 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Nov 2020 02:21:05 +0900 Subject: [Glitch] Bump detect-passive-events from 1.0.5 to 2.0.1 Port e16b0fb15a7ef2607f080e8c0b884c1cf37e5ebb to glitch-soc Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Yamagishi Kazutoshi Signed-off-by: Thibaut Girka --- app/javascript/flavours/glitch/components/column.js | 6 +++--- app/javascript/flavours/glitch/components/dropdown_menu.js | 4 ++-- app/javascript/flavours/glitch/features/emoji_picker/index.js | 4 ++-- .../flavours/glitch/features/ui/components/columns_area.js | 6 +++--- app/javascript/flavours/glitch/util/dom_helpers.js | 4 ++-- app/javascript/flavours/glitch/util/is_mobile.js | 4 ++-- 6 files changed, 14 insertions(+), 14 deletions(-) (limited to 'app/javascript/flavours/glitch/features/ui/components') diff --git a/app/javascript/flavours/glitch/components/column.js b/app/javascript/flavours/glitch/components/column.js index 5819d5362..c9da7d329 100644 --- a/app/javascript/flavours/glitch/components/column.js +++ b/app/javascript/flavours/glitch/components/column.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import detectPassiveEvents from 'detect-passive-events'; +import { supportsPassiveEvents } from 'detect-passive-events'; import { scrollTop } from 'flavours/glitch/util/scroll'; export default class Column extends React.PureComponent { @@ -37,9 +37,9 @@ export default class Column extends React.PureComponent { componentDidMount () { if (this.props.bindToDocument) { - document.addEventListener('wheel', this.handleWheel, detectPassiveEvents.hasSupport ? { passive: true } : false); + document.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false); } else { - this.node.addEventListener('wheel', this.handleWheel, detectPassiveEvents.hasSupport ? { passive: true } : false); + this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false); } } diff --git a/app/javascript/flavours/glitch/components/dropdown_menu.js b/app/javascript/flavours/glitch/components/dropdown_menu.js index e627ea51f..d1aba691c 100644 --- a/app/javascript/flavours/glitch/components/dropdown_menu.js +++ b/app/javascript/flavours/glitch/components/dropdown_menu.js @@ -5,9 +5,9 @@ import IconButton from './icon_button'; import Overlay from 'react-overlays/lib/Overlay'; import Motion from 'flavours/glitch/util/optional_motion'; import spring from 'react-motion/lib/spring'; -import detectPassiveEvents from 'detect-passive-events'; +import { supportsPassiveEvents } from 'detect-passive-events'; -const listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false; +const listenerOptions = supportsPassiveEvents ? { passive: true } : false; let id = 0; class DropdownMenu extends React.PureComponent { diff --git a/app/javascript/flavours/glitch/features/emoji_picker/index.js b/app/javascript/flavours/glitch/features/emoji_picker/index.js index 89219d739..5fd904593 100644 --- a/app/javascript/flavours/glitch/features/emoji_picker/index.js +++ b/app/javascript/flavours/glitch/features/emoji_picker/index.js @@ -10,7 +10,7 @@ import { EmojiPicker as EmojiPickerAsync } from 'flavours/glitch/util/async-comp import Overlay from 'react-overlays/lib/Overlay'; import classNames from 'classnames'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import detectPassiveEvents from 'detect-passive-events'; +import { supportsPassiveEvents } from 'detect-passive-events'; import { buildCustomEmojis, categoriesFromEmojis } from 'flavours/glitch/util/emoji'; import { useSystemEmojiFont } from 'flavours/glitch/util/initial_state'; import { assetHost } from 'flavours/glitch/util/config'; @@ -109,7 +109,7 @@ const mapDispatchToProps = (dispatch, { onPickEmoji }) => ({ let EmojiPicker, Emoji; // load asynchronously const backgroundImageFn = () => `${assetHost}/emoji/sheet_10.png`; -const listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false; +const listenerOptions = supportsPassiveEvents ? { passive: true } : false; class ModifierPickerMenu extends React.PureComponent { diff --git a/app/javascript/flavours/glitch/features/ui/components/columns_area.js b/app/javascript/flavours/glitch/features/ui/components/columns_area.js index 2de24bea5..729ade212 100644 --- a/app/javascript/flavours/glitch/features/ui/components/columns_area.js +++ b/app/javascript/flavours/glitch/features/ui/components/columns_area.js @@ -29,7 +29,7 @@ import Icon from 'flavours/glitch/components/icon'; import ComposePanel from './compose_panel'; import NavigationPanel from './navigation_panel'; -import detectPassiveEvents from 'detect-passive-events'; +import { supportsPassiveEvents } from 'detect-passive-events'; import { scrollRight } from 'flavours/glitch/util/scroll'; const componentMap = { @@ -80,7 +80,7 @@ class ColumnsArea extends ImmutablePureComponent { componentDidMount() { if (!this.props.singleColumn) { - this.node.addEventListener('wheel', this.handleWheel, detectPassiveEvents.hasSupport ? { passive: true } : false); + this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false); } this.lastIndex = getIndex(this.context.router.history.location.pathname); @@ -97,7 +97,7 @@ class ColumnsArea extends ImmutablePureComponent { componentDidUpdate(prevProps) { if (this.props.singleColumn !== prevProps.singleColumn && !this.props.singleColumn) { - this.node.addEventListener('wheel', this.handleWheel, detectPassiveEvents.hasSupport ? { passive: true } : false); + this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false); } this.lastIndex = getIndex(this.context.router.history.location.pathname); this.setState({ shouldAnimate: true }); diff --git a/app/javascript/flavours/glitch/util/dom_helpers.js b/app/javascript/flavours/glitch/util/dom_helpers.js index 3e1f4a26d..d94aeb9d4 100644 --- a/app/javascript/flavours/glitch/util/dom_helpers.js +++ b/app/javascript/flavours/glitch/util/dom_helpers.js @@ -1,9 +1,9 @@ // Package imports. -import detectPassiveEvents from 'detect-passive-events'; +import { supportsPassiveEvents } from 'detect-passive-events'; // This will either be a passive lister options object (if passive // events are supported), or `false`. -export const withPassive = detectPassiveEvents.hasSupport ? { passive: true } : false; +export const withPassive = supportsPassiveEvents ? { passive: true } : false; // Focuses the root element. export function focusRoot () { diff --git a/app/javascript/flavours/glitch/util/is_mobile.js b/app/javascript/flavours/glitch/util/is_mobile.js index db3c8bd80..7e584e8fa 100644 --- a/app/javascript/flavours/glitch/util/is_mobile.js +++ b/app/javascript/flavours/glitch/util/is_mobile.js @@ -1,4 +1,4 @@ -import detectPassiveEvents from 'detect-passive-events'; +import { supportsPassiveEvents } from 'detect-passive-events'; import { forceSingleColumn } from 'flavours/glitch/util/initial_state'; const LAYOUT_BREAKPOINT = 630; @@ -17,7 +17,7 @@ export function isMobile(width, columns) { const iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream; let userTouching = false; -let listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false; +let listenerOptions = supportsPassiveEvents ? { passive: true } : false; function touchListener() { userTouching = true; -- cgit From e4d62042bdb3b0d675c2367b4c48a2a48647af5e Mon Sep 17 00:00:00 2001 From: Mashiro Date: Thu, 5 Nov 2020 03:43:31 +0800 Subject: [Glitch] Add limitation of image's max zoom rate (max to the original size of image) Port f720af6b721b773e68c00a546256ff20eeb3226d to glitch-soc Signed-off-by: Thibaut Girka --- .../features/ui/components/zoomable_image.js | 69 ++++++++++++++++------ 1 file changed, 51 insertions(+), 18 deletions(-) (limited to 'app/javascript/flavours/glitch/features/ui/components') diff --git a/app/javascript/flavours/glitch/features/ui/components/zoomable_image.js b/app/javascript/flavours/glitch/features/ui/components/zoomable_image.js index 2efc70890..caeeced64 100644 --- a/app/javascript/flavours/glitch/features/ui/components/zoomable_image.js +++ b/app/javascript/flavours/glitch/features/ui/components/zoomable_image.js @@ -113,7 +113,8 @@ class ZoomableImage extends React.PureComponent { state = { scale: MIN_SCALE, zoomMatrix: { - type: null, // 'full-width' 'full-height' + type: null, // 'width' 'height' + fullScreen: null, // bool rate: null, // full screen scale rate clientWidth: null, clientHeight: null, @@ -122,12 +123,15 @@ class ZoomableImage extends React.PureComponent { clientHeightFixed: null, scrollTop: null, scrollLeft: null, + translateX: null, + translateY: null, }, zoomState: 'expand', // 'expand' 'compress' navigationHidden: false, dragPosition: { top: 0, left: 0, x: 0, y: 0 }, dragged: false, lockScroll: { x: 0, y: 0 }, + lockTranslate: { x: 0, y: 0 }, } removers = []; @@ -168,18 +172,24 @@ class ZoomableImage extends React.PureComponent { } componentDidUpdate () { + this.setState({ zoomState: this.state.scale >= this.state.zoomMatrix.rate ? 'compress' : 'expand' }); + + if (this.state.scale === MIN_SCALE) { + this.container.style.removeProperty('cursor'); + } + } + + UNSAFE_componentWillReceiveProps () { + // reset when slide to next image if (this.props.zoomButtonHidden) { - this.setState({ scale: MIN_SCALE }, () => { + this.setState({ + scale: MIN_SCALE, + lockTranslate: { x: 0, y: 0 }, + }, () => { this.container.scrollLeft = 0; this.container.scrollTop = 0; }); } - - this.setState({ zoomState: this.state.scale >= this.state.zoomMatrix.rate ? 'compress' : 'expand' }); - - if (this.state.scale === 1) { - this.container.style.removeProperty('cursor'); - } } removeEventListeners () { @@ -192,7 +202,7 @@ class ZoomableImage extends React.PureComponent { const event = normalizeWheel(e); - if (this.state.zoomMatrix.type === 'full-width') { + if (this.state.zoomMatrix.type === 'width') { // full width, scroll vertical this.container.scrollTop = Math.max(this.container.scrollTop + event.pixelY, this.state.lockScroll.y); } else { @@ -268,7 +278,7 @@ class ZoomableImage extends React.PureComponent { } zoom(nextScale, midpoint) { - const { scale } = this.state; + const { scale, zoomMatrix } = this.state; const { scrollLeft, scrollTop } = this.container; // math memo: @@ -283,6 +293,15 @@ class ZoomableImage extends React.PureComponent { this.setState({ scale: nextScale }, () => { this.container.scrollLeft = nextScrollLeft; this.container.scrollTop = nextScrollTop; + // reset the translateX/Y constantly + if (nextScale < zoomMatrix.rate) { + this.setState({ + lockTranslate: { + x: zoomMatrix.fullScreen ? 0 : zoomMatrix.translateX * ((nextScale - MIN_SCALE) / (zoomMatrix.rate - MIN_SCALE)), + y: zoomMatrix.fullScreen ? 0 : zoomMatrix.translateY * ((nextScale - MIN_SCALE) / (zoomMatrix.rate - MIN_SCALE)), + }, + }); + } }); } @@ -307,14 +326,18 @@ class ZoomableImage extends React.PureComponent { const { offsetWidth, offsetHeight } = this.image; const clientHeightFixed = clientHeight - NAV_BAR_HEIGHT; - const type = width/height < clientWidth / clientHeightFixed ? 'full-width' : 'full-height'; - const rate = type === 'full-width' ? clientWidth / offsetWidth : clientHeightFixed / offsetHeight; - const scrollTop = type === 'full-width' ? (clientHeight - offsetHeight) / 2 - NAV_BAR_HEIGHT : (clientHeightFixed - offsetHeight) / 2; + const type = width / height < clientWidth / clientHeightFixed ? 'width' : 'height'; + const fullScreen = type === 'width' ? width > clientWidth : height > clientHeightFixed; + const rate = type === 'width' ? Math.min(clientWidth, width) / offsetWidth : Math.min(clientHeightFixed, height) / offsetHeight; + const scrollTop = type === 'width' ? (clientHeight - offsetHeight) / 2 - NAV_BAR_HEIGHT : (clientHeightFixed - offsetHeight) / 2; const scrollLeft = (clientWidth - offsetWidth) / 2; + const translateX = type === 'width' ? (width - offsetWidth) / (2 * rate) : 0; + const translateY = type === 'height' ? (height - offsetHeight) / (2 * rate) : 0; this.setState({ zoomMatrix: { type: type, + fullScreen: fullScreen, rate: rate, clientWidth: clientWidth, clientHeight: clientHeight, @@ -323,6 +346,8 @@ class ZoomableImage extends React.PureComponent { clientHeightFixed: clientHeightFixed, scrollTop: scrollTop, scrollLeft: scrollLeft, + translateX: translateX, + translateY: translateY, }, }); } @@ -340,6 +365,10 @@ class ZoomableImage extends React.PureComponent { x: 0, y: 0, }, + lockTranslate: { + x: 0, + y: 0, + }, }, () => { this.container.scrollLeft = 0; this.container.scrollTop = 0; @@ -351,6 +380,10 @@ class ZoomableImage extends React.PureComponent { x: zoomMatrix.scrollLeft, y: zoomMatrix.scrollTop, }, + lockTranslate: { + x: zoomMatrix.fullScreen ? 0 : zoomMatrix.translateX, + y: zoomMatrix.fullScreen ? 0 : zoomMatrix.translateY, + }, }, () => { this.container.scrollLeft = zoomMatrix.scrollLeft; this.container.scrollTop = zoomMatrix.scrollTop; @@ -371,15 +404,15 @@ class ZoomableImage extends React.PureComponent { render () { const { alt, src, width, height, intl } = this.props; - const { scale } = this.state; - const overflow = scale === 1 ? 'hidden' : 'scroll'; - const zoomButtonSshouldHide = !this.state.navigationHidden && !this.props.zoomButtonHidden ? '' : 'media-modal__zoom-button--hidden'; + const { scale, lockTranslate } = this.state; + const overflow = scale === MIN_SCALE ? 'hidden' : 'scroll'; + const zoomButtonShouldHide = this.state.navigationHidden || this.props.zoomButtonHidden || this.state.zoomMatrix.rate <= MIN_SCALE ? 'media-modal__zoom-button--hidden' : ''; const zoomButtonTitle = this.state.zoomState === 'compress' ? intl.formatMessage(messages.compress) : intl.formatMessage(messages.expand); return ( Date: Thu, 12 Nov 2020 23:25:26 +0100 Subject: Fix minor design issues with fav confirmation modal --- .../features/ui/components/favourite_modal.js | 30 ++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) (limited to 'app/javascript/flavours/glitch/features/ui/components') diff --git a/app/javascript/flavours/glitch/features/ui/components/favourite_modal.js b/app/javascript/flavours/glitch/features/ui/components/favourite_modal.js index 176e7c487..ea1d7876e 100644 --- a/app/javascript/flavours/glitch/features/ui/components/favourite_modal.js +++ b/app/javascript/flavours/glitch/features/ui/components/favourite_modal.js @@ -7,11 +7,17 @@ import StatusContent from 'flavours/glitch/components/status_content'; import Avatar from 'flavours/glitch/components/avatar'; import RelativeTimestamp from 'flavours/glitch/components/relative_timestamp'; import DisplayName from 'flavours/glitch/components/display_name'; +import AttachmentList from 'flavours/glitch/components/attachment_list'; import Icon from 'flavours/glitch/components/icon'; import ImmutablePureComponent from 'react-immutable-pure-component'; +import classNames from 'classnames'; const messages = defineMessages({ favourite: { id: 'status.favourite', defaultMessage: 'Favourite' }, + public_short: { id: 'privacy.public.short', defaultMessage: 'Public' }, + unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' }, + private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' }, + direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' }, }); export default @injectIntl @@ -54,13 +60,25 @@ class FavouriteModal extends ImmutablePureComponent { render () { const { status, intl } = this.props; + const visibilityIconInfo = { + 'public': { icon: 'globe', text: intl.formatMessage(messages.public_short) }, + 'unlisted': { icon: 'unlock', text: intl.formatMessage(messages.unlisted_short) }, + 'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) }, + 'direct': { icon: 'envelope', text: intl.formatMessage(messages.direct_short) }, + }; + + const visibilityIcon = visibilityIconInfo[status.get('visibility')]; + return (
-- cgit