From f96e5882d16a8662ceb7b8cc6aa36fe131a2682f Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sun, 1 Aug 2021 13:39:56 -0400 Subject: [PATCH 01/29] Make media modal be aware of multi-touch actions Originally the media viewer would think every touch is a swipe (one-finger touch event), so we would encounter the case where a two-finger scale event would incorrectly change the current media. This is now fixed. --- src/components/media_modal/media_modal.js | 25 +++--- src/components/media_modal/media_modal.vue | 1 + .../gesture_service/gesture_service.js | 83 ++++++++++++++++++- 3 files changed, 93 insertions(+), 16 deletions(-) diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js index b8bce730..f3d381ee 100644 --- a/src/components/media_modal/media_modal.js +++ b/src/components/media_modal/media_modal.js @@ -53,28 +53,25 @@ const MediaModal = { } }, created () { - this.mediaSwipeGestureRight = GestureService.swipeGesture( - GestureService.DIRECTION_RIGHT, - this.goPrev, - 50 - ) - this.mediaSwipeGestureLeft = GestureService.swipeGesture( - GestureService.DIRECTION_LEFT, - this.goNext, - 50 - ) + this.mediaGesture = new GestureService.SwipeAndScaleGesture({ + direction: GestureService.DIRECTION_LEFT, + callbackPositive: this.goNext, + callbackNegative: this.goPrev, + threshold: 50 + }) }, methods: { getType (media) { return fileTypeService.fileType(media.mimetype) }, mediaTouchStart (e) { - GestureService.beginSwipe(e, this.mediaSwipeGestureRight) - GestureService.beginSwipe(e, this.mediaSwipeGestureLeft) + this.mediaGesture.start(e) }, mediaTouchMove (e) { - GestureService.updateSwipe(e, this.mediaSwipeGestureRight) - GestureService.updateSwipe(e, this.mediaSwipeGestureLeft) + this.mediaGesture.move(e) + }, + mediaTouchEnd (e) { + this.mediaGesture.end(e) }, hide () { this.$store.dispatch('closeMediaViewer') diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue index 8680267b..b5a65d50 100644 --- a/src/components/media_modal/media_modal.vue +++ b/src/components/media_modal/media_modal.vue @@ -13,6 +13,7 @@ :title="currentMedia.description" @touchstart.stop="mediaTouchStart" @touchmove.stop="mediaTouchMove" + @touchend.stop="mediaTouchEnd" @click="hide" @load="onImageLoaded" > diff --git a/src/services/gesture_service/gesture_service.js b/src/services/gesture_service/gesture_service.js index 88a328f3..8df4f03d 100644 --- a/src/services/gesture_service/gesture_service.js +++ b/src/services/gesture_service/gesture_service.js @@ -4,9 +4,17 @@ const DIRECTION_RIGHT = [1, 0] const DIRECTION_UP = [0, -1] const DIRECTION_DOWN = [0, 1] +const isSwipeEvent = e => (e.touches.length === 1) +const isSwipeEventEnd = e => (e.changedTouches.length === 1) + +const isScaleEvent = e => (e.targetTouches.length === 2) +// const isScaleEventEnd = e => (e.changedTouches.length === 2) + const deltaCoord = (oldCoord, newCoord) => [newCoord[0] - oldCoord[0], newCoord[1] - oldCoord[1]] -const touchEventCoord = e => ([e.touches[0].screenX, e.touches[0].screenY]) +const touchCoord = touch => [touch.screenX, touch.screenY] + +const touchEventCoord = e => touchCoord(e.touches[0]) const vectorLength = v => Math.sqrt(v[0] * v[0] + v[1] * v[1]) @@ -61,6 +69,76 @@ const updateSwipe = (event, gesture) => { gesture._swiping = false } +class SwipeAndScaleGesture { + constructor ({ + direction, callbackPositive, callbackNegative, + previewCallback, threshold = 30, perpendicularTolerance = 1.0 + }) { + this.direction = direction + this.previewCallback = previewCallback + this.callbackPositive = callbackPositive + this.callbackNegative = callbackNegative + this.threshold = threshold + this.perpendicularTolerance = perpendicularTolerance + this._startPos = [0, 0] + this._swiping = false + } + + start (event) { + console.log('start() called', event) + if (isSwipeEvent(event)) { + this._startPos = touchEventCoord(event) + console.log('start pos:', this._startPos) + this._swiping = true + } else if (isScaleEvent(event)) { + this._scalePoints = [...event.targetTouches] + this._swiping = false + } + } + + move (event) { + if (isScaleEvent(event)) { + } + } + + end (event) { + console.log('end() called', event) + if (!isSwipeEventEnd(event)) { + console.log('not swipe event') + return + } + if (!this._swiping) { + console.log('not swiping') + return + } + this.swiping = false + + console.log('is swipe event') + + // movement too small + const touch = event.changedTouches[0] + const delta = deltaCoord(this._startPos, touchCoord(touch)) + if (vectorLength(delta) < this.threshold) return + // movement is opposite from direction + const isPositive = dotProduct(delta, this.direction) > 0 + + // movement perpendicular to direction is too much + const towardsDir = project(delta, this.direction) + const perpendicularDir = perpendicular(this.direction) + const towardsPerpendicular = project(delta, perpendicularDir) + if ( + vectorLength(towardsDir) * this.perpendicularTolerance < + vectorLength(towardsPerpendicular) + ) return + + if (isPositive) { + this.callbackPositive() + } else { + this.callbackNegative() + } + } +} + const GestureService = { DIRECTION_LEFT, DIRECTION_RIGHT, @@ -68,7 +146,8 @@ const GestureService = { DIRECTION_DOWN, swipeGesture, beginSwipe, - updateSwipe + updateSwipe, + SwipeAndScaleGesture } export default GestureService From a7570f5eb2b8bc576edbcc8e212b2c873ac99e7e Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sun, 1 Aug 2021 19:46:27 -0400 Subject: [PATCH 02/29] Preview swipe action --- src/components/media_modal/media_modal.js | 26 +++++++ src/components/media_modal/media_modal.vue | 1 + src/modules/media_viewer.js | 64 ++++++++++++++++- .../gesture_service/gesture_service.js | 69 +++++++++++++------ 4 files changed, 136 insertions(+), 24 deletions(-) diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js index f3d381ee..8f67db2b 100644 --- a/src/components/media_modal/media_modal.js +++ b/src/components/media_modal/media_modal.js @@ -4,6 +4,7 @@ import Modal from '../modal/modal.vue' import fileTypeService from '../../services/file_type/file_type.service.js' import GestureService from '../../services/gesture_service/gesture_service' import Flash from 'src/components/flash/flash.vue' +import Vuex from 'vuex' import { library } from '@fortawesome/fontawesome-svg-core' import { faChevronLeft, @@ -17,6 +18,8 @@ library.add( faCircleNotch ) +const onlyXAxis = ([x, y]) => [x, 0] + const MediaModal = { components: { StillImage, @@ -50,6 +53,15 @@ const MediaModal = { }, type () { return this.currentMedia ? this.getType(this.currentMedia) : null + }, + scaling () { + return this.$store.state.mediaViewer.swipeScaler.scaling + }, + offsets () { + return this.$store.state.mediaViewer.swipeScaler.offsets + }, + transform () { + return `scale(${this.scaling}, ${this.scaling}) translate(${this.offsets[0]}px, ${this.offsets[1]}px)` } }, created () { @@ -57,6 +69,8 @@ const MediaModal = { direction: GestureService.DIRECTION_LEFT, callbackPositive: this.goNext, callbackNegative: this.goPrev, + swipePreviewCallback: this.handleSwipePreview, + swipeEndCallback: this.handleSwipeEnd, threshold: 50 }) }, @@ -99,6 +113,18 @@ const MediaModal = { onImageLoaded () { this.loading = false }, + handleSwipePreview (offsets) { + this.$store.dispatch('swipeScaler/apply', { offsets: onlyXAxis(offsets) }) + }, + handleSwipeEnd (sign) { + if (sign === 0) { + this.$store.dispatch('swipeScaler/revert') + } else if (sign > 0) { + this.goNext() + } else { + this.goPrev() + } + }, handleKeyupEvent (e) { if (this.showing && e.keyCode === 27) { // escape this.hide() diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue index b5a65d50..728c3035 100644 --- a/src/components/media_modal/media_modal.vue +++ b/src/components/media_modal/media_modal.vue @@ -11,6 +11,7 @@ :src="currentMedia.url" :alt="currentMedia.description" :title="currentMedia.description" + :style="{ transform }" @touchstart.stop="mediaTouchStart" @touchmove.stop="mediaTouchMove" @touchend.stop="mediaTouchEnd" diff --git a/src/modules/media_viewer.js b/src/modules/media_viewer.js index ebcba01d..38723763 100644 --- a/src/modules/media_viewer.js +++ b/src/modules/media_viewer.js @@ -20,19 +20,79 @@ const mediaViewer = { } }, actions: { - setMedia ({ commit }, attachments) { + setMedia ({ commit, dispatch }, attachments) { const media = attachments.filter(attachment => { const type = fileTypeService.fileType(attachment.mimetype) return supportedTypes.has(type) }) commit('setMedia', media) + dispatch('swipeScaler/reset') }, setCurrentMedia ({ commit, state }, current) { const index = state.media.indexOf(current) commit('setCurrentMedia', index || 0) + dispatch('swipeScaler/reset') }, - closeMediaViewer ({ commit }) { + closeMediaViewer ({ commit, dispatch }) { commit('close') + dispatch('swipeScaler/reset') + } + }, + modules: { + swipeScaler: { + namespaced: true, + + state: { + origOffsets: [0, 0], + offsets: [0, 0], + origScaling: 1, + scaling: 1 + }, + + mutations: { + reset (state) { + state.origOffsets = [0, 0] + state.offsets = [0, 0] + state.origScaling = 1 + state.scaling = 1 + }, + applyOffsets (state, { offsets }) { + state.offsets = state.origOffsets.map((k, n) => k + offsets[n]) + }, + applyScaling (state, { scaling }) { + state.scaling = state.origScaling * scaling + }, + finishOffsets (state) { + state.origOffsets = [...state.offsets] + }, + finishScaling (state) { + state.origScaling = state.scaling + }, + revertOffsets (state) { + state.offsets = [...state.origOffsets] + }, + revertScaling (state) { + state.scaling = state.origScaling + } + }, + + actions: { + reset ({ commit }) { + commit('reset') + }, + apply ({ commit }, { offsets, scaling = 1 }) { + commit('applyOffsets', { offsets }) + commit('applyScaling', { scaling }) + }, + finish ({ commit }) { + commit('finishOffsets') + commit('finishScaling') + }, + revert ({ commit }) { + commit('revertOffsets') + commit('revertScaling') + } + } } } } diff --git a/src/services/gesture_service/gesture_service.js b/src/services/gesture_service/gesture_service.js index 8df4f03d..8f406762 100644 --- a/src/services/gesture_service/gesture_service.js +++ b/src/services/gesture_service/gesture_service.js @@ -70,14 +70,28 @@ const updateSwipe = (event, gesture) => { } class SwipeAndScaleGesture { + // swipePreviewCallback(offsets: Array[Number]) + // offsets: the offset vector which the underlying component should move, from the starting position + // pinchPreviewCallback(offsets: Array[Number], scaling: Number) + // offsets: the offset vector which the underlying component should move, from the starting position + // scaling: the scaling factor we should apply to the underlying component, from the starting position + // swipeEndcallback(sign: 0|-1|1) + // sign: if the swipe does not meet the threshold, 0 + // if the swipe meets the threshold in the positive direction, 1 + // if the swipe meets the threshold in the negative direction, -1 constructor ({ - direction, callbackPositive, callbackNegative, - previewCallback, threshold = 30, perpendicularTolerance = 1.0 + direction, + // swipeStartCallback, pinchStartCallback, + swipePreviewCallback, pinchPreviewCallback, + swipeEndCallback, pinchEndCallback, + threshold = 30, perpendicularTolerance = 1.0 }) { + const nop = () => {} this.direction = direction - this.previewCallback = previewCallback - this.callbackPositive = callbackPositive - this.callbackNegative = callbackNegative + this.swipePreviewCallback = swipePreviewCallback || nop + this.pinchPreviewCallback = pinchPreviewCallback || nop + this.swipeEndCallback = swipeEndCallback || nop + this.pinchEndCallback = pinchEndCallback || nop this.threshold = threshold this.perpendicularTolerance = perpendicularTolerance this._startPos = [0, 0] @@ -97,7 +111,12 @@ class SwipeAndScaleGesture { } move (event) { - if (isScaleEvent(event)) { + if (isSwipeEvent(event)) { + const touch = event.changedTouches[0] + const delta = deltaCoord(this._startPos, touchCoord(touch)) + + this.swipePreviewCallback(delta) + } else if (isScaleEvent(event)) { } } @@ -118,24 +137,30 @@ class SwipeAndScaleGesture { // movement too small const touch = event.changedTouches[0] const delta = deltaCoord(this._startPos, touchCoord(touch)) - if (vectorLength(delta) < this.threshold) return - // movement is opposite from direction - const isPositive = dotProduct(delta, this.direction) > 0 + this.swipePreviewCallback(delta) - // movement perpendicular to direction is too much - const towardsDir = project(delta, this.direction) - const perpendicularDir = perpendicular(this.direction) - const towardsPerpendicular = project(delta, perpendicularDir) - if ( - vectorLength(towardsDir) * this.perpendicularTolerance < - vectorLength(towardsPerpendicular) - ) return + const sign = (() => { + if (vectorLength(delta) < this.threshold) { + return 0 + } + // movement is opposite from direction + const isPositive = dotProduct(delta, this.direction) > 0 - if (isPositive) { - this.callbackPositive() - } else { - this.callbackNegative() - } + // movement perpendicular to direction is too much + const towardsDir = project(delta, this.direction) + const perpendicularDir = perpendicular(this.direction) + const towardsPerpendicular = project(delta, perpendicularDir) + if ( + vectorLength(towardsDir) * this.perpendicularTolerance < + vectorLength(towardsPerpendicular) + ) { + return 0 + } + + return isPositive ? 1 : -1 + })() + + this.swipeEndCallback(sign) } } From d9030b4fddc9f47cb2f16a7a65f57a5703059e3f Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sun, 1 Aug 2021 21:39:07 -0400 Subject: [PATCH 03/29] Handle pinch action --- src/components/media_modal/media_modal.js | 11 ++++- src/components/media_modal/media_modal.vue | 6 +-- src/modules/media_viewer.js | 14 ++----- .../gesture_service/gesture_service.js | 42 ++++++++++++++++++- 4 files changed, 57 insertions(+), 16 deletions(-) diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js index 8f67db2b..04dd8658 100644 --- a/src/components/media_modal/media_modal.js +++ b/src/components/media_modal/media_modal.js @@ -61,7 +61,7 @@ const MediaModal = { return this.$store.state.mediaViewer.swipeScaler.offsets }, transform () { - return `scale(${this.scaling}, ${this.scaling}) translate(${this.offsets[0]}px, ${this.offsets[1]}px)` + return `translate(${this.offsets[0]}px, ${this.offsets[1]}px) scale(${this.scaling}, ${this.scaling})` } }, created () { @@ -71,6 +71,8 @@ const MediaModal = { callbackNegative: this.goPrev, swipePreviewCallback: this.handleSwipePreview, swipeEndCallback: this.handleSwipeEnd, + pinchPreviewCallback: this.handlePinchPreview, + pinchEndCallback: this.handlePinchEnd, threshold: 50 }) }, @@ -125,6 +127,13 @@ const MediaModal = { this.goPrev() } }, + handlePinchPreview (offsets, scaling) { + console.log('handle pinch preview:', offsets, scaling) + this.$store.dispatch('swipeScaler/apply', { offsets, scaling }) + }, + handlePinchEnd () { + this.$store.dispatch('swipeScaler/finish') + }, handleKeyupEvent (e) { if (this.showing && e.keyCode === 27) { // escape this.hide() diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue index 728c3035..853dec1d 100644 --- a/src/components/media_modal/media_modal.vue +++ b/src/components/media_modal/media_modal.vue @@ -12,9 +12,9 @@ :alt="currentMedia.description" :title="currentMedia.description" :style="{ transform }" - @touchstart.stop="mediaTouchStart" - @touchmove.stop="mediaTouchMove" - @touchend.stop="mediaTouchEnd" + @touchstart.stop.prevent="mediaTouchStart" + @touchmove.stop.prevent="mediaTouchMove" + @touchend.stop.prevent="mediaTouchEnd" @click="hide" @load="onImageLoaded" > diff --git a/src/modules/media_viewer.js b/src/modules/media_viewer.js index 38723763..d62aa7c1 100644 --- a/src/modules/media_viewer.js +++ b/src/modules/media_viewer.js @@ -62,16 +62,12 @@ const mediaViewer = { applyScaling (state, { scaling }) { state.scaling = state.origScaling * scaling }, - finishOffsets (state) { + finish (state) { state.origOffsets = [...state.offsets] - }, - finishScaling (state) { state.origScaling = state.scaling }, - revertOffsets (state) { + revert (state) { state.offsets = [...state.origOffsets] - }, - revertScaling (state) { state.scaling = state.origScaling } }, @@ -85,12 +81,10 @@ const mediaViewer = { commit('applyScaling', { scaling }) }, finish ({ commit }) { - commit('finishOffsets') - commit('finishScaling') + commit('finish') }, revert ({ commit }) { - commit('revertOffsets') - commit('revertScaling') + commit('revert') } } } diff --git a/src/services/gesture_service/gesture_service.js b/src/services/gesture_service/gesture_service.js index 8f406762..82337bc6 100644 --- a/src/services/gesture_service/gesture_service.js +++ b/src/services/gesture_service/gesture_service.js @@ -4,14 +4,21 @@ const DIRECTION_RIGHT = [1, 0] const DIRECTION_UP = [0, -1] const DIRECTION_DOWN = [0, 1] +const DISTANCE_MIN = 1 + const isSwipeEvent = e => (e.touches.length === 1) const isSwipeEventEnd = e => (e.changedTouches.length === 1) const isScaleEvent = e => (e.targetTouches.length === 2) -// const isScaleEventEnd = e => (e.changedTouches.length === 2) +const isScaleEventEnd = e => (e.targetTouches.length === 1) const deltaCoord = (oldCoord, newCoord) => [newCoord[0] - oldCoord[0], newCoord[1] - oldCoord[1]] +const vectorMinus = (a, b) => a.map((k, n) => k - b[n]) +const vectorAdd = (a, b) => a.map((k, n) => k + b[n]) + +const avgCoord = (coords) => [...coords].reduce(vectorAdd, [0, 0]).map(d => d / coords.length) + const touchCoord = touch => [touch.screenX, touch.screenY] const touchEventCoord = e => touchCoord(e.touches[0]) @@ -22,6 +29,8 @@ const perpendicular = v => [v[1], -v[0]] const dotProduct = (v1, v2) => v1[0] * v2[0] + v1[1] * v2[1] +// const numProduct = (num, v) => v.map(k => num * k) + const project = (v1, v2) => { const scalar = (dotProduct(v1, v2) / dotProduct(v2, v2)) return [scalar * v2[0], scalar * v2[1]] @@ -86,7 +95,7 @@ class SwipeAndScaleGesture { swipeEndCallback, pinchEndCallback, threshold = 30, perpendicularTolerance = 1.0 }) { - const nop = () => {} + const nop = () => { console.log('Warning: Not implemented') } this.direction = direction this.swipePreviewCallback = swipePreviewCallback || nop this.pinchPreviewCallback = pinchPreviewCallback || nop @@ -95,6 +104,7 @@ class SwipeAndScaleGesture { this.threshold = threshold this.perpendicularTolerance = perpendicularTolerance this._startPos = [0, 0] + this._startDistance = DISTANCE_MIN this._swiping = false } @@ -105,23 +115,51 @@ class SwipeAndScaleGesture { console.log('start pos:', this._startPos) this._swiping = true } else if (isScaleEvent(event)) { + const coords = [...event.targetTouches].map(touchCoord) + this._startPos = avgCoord(coords) + this._startDistance = vectorLength(deltaCoord(coords[0], coords[1])) + if (this._startDistance < DISTANCE_MIN) { + this._startDistance = DISTANCE_MIN + } this._scalePoints = [...event.targetTouches] this._swiping = false + console.log( + 'is scale event, start =', this._startPos, + 'dist =', this._startDistance) } } move (event) { + // console.log('move called', event) if (isSwipeEvent(event)) { const touch = event.changedTouches[0] const delta = deltaCoord(this._startPos, touchCoord(touch)) this.swipePreviewCallback(delta) } else if (isScaleEvent(event)) { + console.log('is scale event') + const coords = [...event.targetTouches].map(touchCoord) + const curPos = avgCoord(coords) + const curDistance = vectorLength(deltaCoord(coords[0], coords[1])) + const scaling = curDistance / this._startDistance + const posDiff = vectorMinus(curPos, this._startPos) + // const delta = vectorAdd(numProduct((1 - scaling), this._startPos), posDiff) + const delta = posDiff + // console.log( + // 'is scale event, cur =', curPos, + // 'dist =', curDistance, + // 'scale =', scaling, + // 'delta =', delta) + this.pinchPreviewCallback(delta, scaling) } } end (event) { console.log('end() called', event) + if (isScaleEventEnd(event)) { + this.pinchEndCallback() + } + if (!isSwipeEventEnd(event)) { console.log('not swipe event') return From 544db06294e3bb1948aec97d870d7506bf34252f Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sun, 1 Aug 2021 23:42:34 -0400 Subject: [PATCH 04/29] Add pan threshold --- src/components/media_modal/media_modal.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js index 04dd8658..222e3478 100644 --- a/src/components/media_modal/media_modal.js +++ b/src/components/media_modal/media_modal.js @@ -19,6 +19,8 @@ library.add( ) const onlyXAxis = ([x, y]) => [x, 0] +const SCALING_RESET_MIN = 1.1 +const SCALING_ENABLE_MOVE_THRESHOLD = 1 const MediaModal = { components: { @@ -116,11 +118,17 @@ const MediaModal = { this.loading = false }, handleSwipePreview (offsets) { - this.$store.dispatch('swipeScaler/apply', { offsets: onlyXAxis(offsets) }) + this.$store.dispatch('swipeScaler/apply', { + offsets: this.scaling > SCALING_ENABLE_MOVE_THRESHOLD ? offsets : onlyXAxis(offsets) + }) }, handleSwipeEnd (sign) { + if (this.scaling > SCALING_ENABLE_MOVE_THRESHOLD) { + this.$store.dispatch('swipeScaler/finish') + return + } if (sign === 0) { - this.$store.dispatch('swipeScaler/revert') + this.$store.dispatch('swipeScaler/reset') } else if (sign > 0) { this.goNext() } else { @@ -132,7 +140,11 @@ const MediaModal = { this.$store.dispatch('swipeScaler/apply', { offsets, scaling }) }, handlePinchEnd () { - this.$store.dispatch('swipeScaler/finish') + if (this.scaling > SCALING_RESET_MIN) { + this.$store.dispatch('swipeScaler/finish') + } else { + this.$store.dispatch('swipeScaler/reset') + } }, handleKeyupEvent (e) { if (this.showing && e.keyCode === 27) { // escape From a36673a6a8cace69979605f72bcc4d7a9d439814 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Mon, 2 Aug 2021 17:42:10 -0400 Subject: [PATCH 05/29] Use pinch-zoom-element for pinch zoom functionality --- package.json | 1 + src/components/media_modal/media_modal.js | 5 +- src/components/media_modal/media_modal.vue | 72 +++++++++++++++++----- src/components/pinch_zoom/pinch_zoom.js | 6 ++ src/components/pinch_zoom/pinch_zoom.vue | 11 ++++ src/main.js | 2 + src/modules/media_viewer.js | 2 +- yarn.lock | 11 ++++ 8 files changed, 94 insertions(+), 16 deletions(-) create mode 100644 src/components/pinch_zoom/pinch_zoom.js create mode 100644 src/components/pinch_zoom/pinch_zoom.vue diff --git a/package.json b/package.json index 5dd0e067..34029765 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@fortawesome/free-regular-svg-icons": "5.15.1", "@fortawesome/free-solid-svg-icons": "5.15.1", "@fortawesome/vue-fontawesome": "2.0.0", + "@kazvmoe-infra/pinch-zoom-element": "https://lily.kazv.moe/infra/pinch-zoom-element.git", "body-scroll-lock": "2.6.4", "chromatism": "3.0.0", "cropperjs": "1.4.3", diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js index 222e3478..684fbe45 100644 --- a/src/components/media_modal/media_modal.js +++ b/src/components/media_modal/media_modal.js @@ -1,6 +1,7 @@ import StillImage from '../still-image/still-image.vue' import VideoAttachment from '../video_attachment/video_attachment.vue' import Modal from '../modal/modal.vue' +import PinchZoom from '../pinch_zoom/pinch_zoom.vue' import fileTypeService from '../../services/file_type/file_type.service.js' import GestureService from '../../services/gesture_service/gesture_service' import Flash from 'src/components/flash/flash.vue' @@ -26,12 +27,14 @@ const MediaModal = { components: { StillImage, VideoAttachment, + PinchZoom, Modal, Flash }, data () { return { - loading: false + loading: false, + pinchZoomOptions: {} } }, computed: { diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue index 853dec1d..a3fad4c5 100644 --- a/src/components/media_modal/media_modal.vue +++ b/src/components/media_modal/media_modal.vue @@ -4,20 +4,29 @@ class="media-modal-view" @backdropClicked="hide" > - + + + + + + + diff --git a/src/main.js b/src/main.js index 3895da89..03493525 100644 --- a/src/main.js +++ b/src/main.js @@ -45,6 +45,8 @@ Vue.use(VueClickOutside) Vue.use(PortalVue) Vue.use(VBodyScrollLock) +Vue.config.ignoredElements = ['pinch-zoom'] + Vue.component('FAIcon', FontAwesomeIcon) Vue.component('FALayers', FontAwesomeLayers) diff --git a/src/modules/media_viewer.js b/src/modules/media_viewer.js index d62aa7c1..ddcccb79 100644 --- a/src/modules/media_viewer.js +++ b/src/modules/media_viewer.js @@ -28,7 +28,7 @@ const mediaViewer = { commit('setMedia', media) dispatch('swipeScaler/reset') }, - setCurrentMedia ({ commit, state }, current) { + setCurrentMedia ({ commit, state, dispatch }, current) { const index = state.media.indexOf(current) commit('setCurrentMedia', index || 0) dispatch('swipeScaler/reset') diff --git a/yarn.lock b/yarn.lock index 2d873c25..176b0c7d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -916,6 +916,12 @@ resolved "https://registry.yarnpkg.com/@fortawesome/vue-fontawesome/-/vue-fontawesome-2.0.0.tgz#63da3e459147cebb0a8d58eed81d6071db9f5973" integrity sha512-N3VKw7KzRfOm8hShUVldpinlm13HpvLBQgT63QS+aCrIRLwjoEUXY5Rcmttbfb6HkzZaeqjLqd/aZCQ53UjQpg== +"@kazvmoe-infra/pinch-zoom-element@https://lily.kazv.moe/infra/pinch-zoom-element.git": + version "1.1.1" + resolved "https://lily.kazv.moe/infra/pinch-zoom-element.git#de150c01057f0de0c3ef669917399fe370e36f40" + dependencies: + pointer-tracker "^2.0.3" + "@nodelib/fs.scandir@2.1.3": version "2.1.3" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" @@ -6995,6 +7001,11 @@ pngjs@^5.0.0: resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb" integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw== +pointer-tracker@^2.0.3: + version "2.4.0" + resolved "https://registry.yarnpkg.com/pointer-tracker/-/pointer-tracker-2.4.0.tgz#78721c2d2201486db11ec1094377f03023b621b3" + integrity sha512-pWI2tpaM/XNtc9mUTv42Rmjf6mkHvE8LT5DDEq0G7baPNhxNM9E3CepubPplSoSLk9E5bwQrAMyDcPVmJyTW4g== + portal-vue@2.1.7: version "2.1.7" resolved "https://registry.yarnpkg.com/portal-vue/-/portal-vue-2.1.7.tgz#ea08069b25b640ca08a5b86f67c612f15f4e4ad4" From 29cd8fbd3bd9f936d2639a2edd773e1245b0b5a5 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Mon, 2 Aug 2021 19:11:59 -0400 Subject: [PATCH 06/29] Add swipe-click handler to media modal Now swiping will correctly change the current media, and with a good preview. Clicking without swiping closes the overlay. --- src/components/media_modal/media_modal.js | 58 +++----- src/components/media_modal/media_modal.vue | 13 +- src/components/pinch_zoom/pinch_zoom.js | 5 + src/modules/media_viewer.js | 56 +------- .../gesture_service/gesture_service.js | 130 ++++++++---------- 5 files changed, 94 insertions(+), 168 deletions(-) diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js index 684fbe45..b4c0cfb4 100644 --- a/src/components/media_modal/media_modal.js +++ b/src/components/media_modal/media_modal.js @@ -2,10 +2,11 @@ import StillImage from '../still-image/still-image.vue' import VideoAttachment from '../video_attachment/video_attachment.vue' import Modal from '../modal/modal.vue' import PinchZoom from '../pinch_zoom/pinch_zoom.vue' -import fileTypeService from '../../services/file_type/file_type.service.js' +import SwipeClick from '../swipe_click/swipe_click.vue' import GestureService from '../../services/gesture_service/gesture_service' import Flash from 'src/components/flash/flash.vue' import Vuex from 'vuex' +import fileTypeService from '../../services/file_type/file_type.service.js' import { library } from '@fortawesome/fontawesome-svg-core' import { faChevronLeft, @@ -28,13 +29,15 @@ const MediaModal = { StillImage, VideoAttachment, PinchZoom, + SwipeClick, Modal, Flash }, data () { return { loading: false, - pinchZoomOptions: {} + swipeDirection: GestureService.DIRECTION_LEFT, + swipeThreshold: 50 } }, computed: { @@ -70,30 +73,19 @@ const MediaModal = { } }, created () { - this.mediaGesture = new GestureService.SwipeAndScaleGesture({ - direction: GestureService.DIRECTION_LEFT, - callbackPositive: this.goNext, - callbackNegative: this.goPrev, - swipePreviewCallback: this.handleSwipePreview, - swipeEndCallback: this.handleSwipeEnd, - pinchPreviewCallback: this.handlePinchPreview, - pinchEndCallback: this.handlePinchEnd, - threshold: 50 - }) + // this.mediaGesture = new GestureService.SwipeAndScaleGesture({ + // callbackPositive: this.goNext, + // callbackNegative: this.goPrev, + // swipePreviewCallback: this.handleSwipePreview, + // swipeEndCallback: this.handleSwipeEnd, + // pinchPreviewCallback: this.handlePinchPreview, + // pinchEndCallback: this.handlePinchEnd + // }) }, methods: { getType (media) { return fileTypeService.fileType(media.mimetype) }, - mediaTouchStart (e) { - this.mediaGesture.start(e) - }, - mediaTouchMove (e) { - this.mediaGesture.move(e) - }, - mediaTouchEnd (e) { - this.mediaGesture.end(e) - }, hide () { this.$store.dispatch('closeMediaViewer') }, @@ -105,6 +97,7 @@ const MediaModal = { this.loading = true } this.$store.dispatch('setCurrentMedia', newMedia) + this.$refs.pinchZoom.setTransform({ scale: 1, x: 0, y: 0 }) } }, goNext () { @@ -115,40 +108,25 @@ const MediaModal = { this.loading = true } this.$store.dispatch('setCurrentMedia', newMedia) + this.$refs.pinchZoom.setTransform({ scale: 1, x: 0, y: 0 }) } }, onImageLoaded () { this.loading = false }, handleSwipePreview (offsets) { - this.$store.dispatch('swipeScaler/apply', { - offsets: this.scaling > SCALING_ENABLE_MOVE_THRESHOLD ? offsets : onlyXAxis(offsets) - }) + this.$refs.pinchZoom.setTransform({ scale: 1, x: offsets[0], y: 0 }) }, handleSwipeEnd (sign) { - if (this.scaling > SCALING_ENABLE_MOVE_THRESHOLD) { - this.$store.dispatch('swipeScaler/finish') - return - } + console.log('handleSwipeEnd:', sign) if (sign === 0) { - this.$store.dispatch('swipeScaler/reset') + this.$refs.pinchZoom.setTransform({ scale: 1, x: 0, y: 0 }) } else if (sign > 0) { this.goNext() } else { this.goPrev() } }, - handlePinchPreview (offsets, scaling) { - console.log('handle pinch preview:', offsets, scaling) - this.$store.dispatch('swipeScaler/apply', { offsets, scaling }) - }, - handlePinchEnd () { - if (this.scaling > SCALING_RESET_MIN) { - this.$store.dispatch('swipeScaler/finish') - } else { - this.$store.dispatch('swipeScaler/reset') - } - }, handleKeyupEvent (e) { if (this.showing && e.keyCode === 27) { // escape this.hide() diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue index a3fad4c5..e385024e 100644 --- a/src/components/media_modal/media_modal.vue +++ b/src/components/media_modal/media_modal.vue @@ -4,9 +4,16 @@ class="media-modal-view" @backdropClicked="hide" > - + k + offsets[n]) - }, - applyScaling (state, { scaling }) { - state.scaling = state.origScaling * scaling - }, - finish (state) { - state.origOffsets = [...state.offsets] - state.origScaling = state.scaling - }, - revert (state) { - state.offsets = [...state.origOffsets] - state.scaling = state.origScaling - } - }, - - actions: { - reset ({ commit }) { - commit('reset') - }, - apply ({ commit }, { offsets, scaling = 1 }) { - commit('applyOffsets', { offsets }) - commit('applyScaling', { scaling }) - }, - finish ({ commit }) { - commit('finish') - }, - revert ({ commit }) { - commit('revert') - } - } } } } diff --git a/src/services/gesture_service/gesture_service.js b/src/services/gesture_service/gesture_service.js index 82337bc6..f10dec3a 100644 --- a/src/services/gesture_service/gesture_service.js +++ b/src/services/gesture_service/gesture_service.js @@ -4,25 +4,27 @@ const DIRECTION_RIGHT = [1, 0] const DIRECTION_UP = [0, -1] const DIRECTION_DOWN = [0, 1] -const DISTANCE_MIN = 1 +// const DISTANCE_MIN = 1 -const isSwipeEvent = e => (e.touches.length === 1) -const isSwipeEventEnd = e => (e.changedTouches.length === 1) +// const isSwipeEvent = e => (e.touches.length === 1) +// const isSwipeEventEnd = e => (e.changedTouches.length === 1) -const isScaleEvent = e => (e.targetTouches.length === 2) -const isScaleEventEnd = e => (e.targetTouches.length === 1) +// const isScaleEvent = e => (e.targetTouches.length === 2) +// const isScaleEventEnd = e => (e.targetTouches.length === 1) const deltaCoord = (oldCoord, newCoord) => [newCoord[0] - oldCoord[0], newCoord[1] - oldCoord[1]] -const vectorMinus = (a, b) => a.map((k, n) => k - b[n]) -const vectorAdd = (a, b) => a.map((k, n) => k + b[n]) +// const vectorMinus = (a, b) => a.map((k, n) => k - b[n]) +// const vectorAdd = (a, b) => a.map((k, n) => k + b[n]) -const avgCoord = (coords) => [...coords].reduce(vectorAdd, [0, 0]).map(d => d / coords.length) +// const avgCoord = (coords) => [...coords].reduce(vectorAdd, [0, 0]).map(d => d / coords.length) const touchCoord = touch => [touch.screenX, touch.screenY] const touchEventCoord = e => touchCoord(e.touches[0]) +const pointerEventCoord = e => [e.clientX, e.clientY] + const vectorLength = v => Math.sqrt(v[0] * v[0] + v[1] * v[1]) const perpendicular = v => [v[1], -v[0]] @@ -78,104 +80,87 @@ const updateSwipe = (event, gesture) => { gesture._swiping = false } -class SwipeAndScaleGesture { +class SwipeAndClickGesture { // swipePreviewCallback(offsets: Array[Number]) // offsets: the offset vector which the underlying component should move, from the starting position - // pinchPreviewCallback(offsets: Array[Number], scaling: Number) - // offsets: the offset vector which the underlying component should move, from the starting position - // scaling: the scaling factor we should apply to the underlying component, from the starting position - // swipeEndcallback(sign: 0|-1|1) + // swipeEndCallback(sign: 0|-1|1) // sign: if the swipe does not meet the threshold, 0 // if the swipe meets the threshold in the positive direction, 1 // if the swipe meets the threshold in the negative direction, -1 constructor ({ direction, - // swipeStartCallback, pinchStartCallback, - swipePreviewCallback, pinchPreviewCallback, - swipeEndCallback, pinchEndCallback, + // swipeStartCallback + swipePreviewCallback, + swipeEndCallback, + swipeCancelCallback, + swipelessClickCallback, threshold = 30, perpendicularTolerance = 1.0 }) { const nop = () => { console.log('Warning: Not implemented') } this.direction = direction this.swipePreviewCallback = swipePreviewCallback || nop - this.pinchPreviewCallback = pinchPreviewCallback || nop this.swipeEndCallback = swipeEndCallback || nop - this.pinchEndCallback = pinchEndCallback || nop + this.swipeCancelCallback = swipeCancelCallback || nop + this.swipelessClickCallback = swipelessClickCallback || nop this.threshold = threshold this.perpendicularTolerance = perpendicularTolerance + this._reset() + } + + _reset () { this._startPos = [0, 0] - this._startDistance = DISTANCE_MIN + this._pointerId = -1 this._swiping = false + this._swiped = false } start (event) { console.log('start() called', event) - if (isSwipeEvent(event)) { - this._startPos = touchEventCoord(event) - console.log('start pos:', this._startPos) - this._swiping = true - } else if (isScaleEvent(event)) { - const coords = [...event.targetTouches].map(touchCoord) - this._startPos = avgCoord(coords) - this._startDistance = vectorLength(deltaCoord(coords[0], coords[1])) - if (this._startDistance < DISTANCE_MIN) { - this._startDistance = DISTANCE_MIN - } - this._scalePoints = [...event.targetTouches] - this._swiping = false - console.log( - 'is scale event, start =', this._startPos, - 'dist =', this._startDistance) - } + + this._startPos = pointerEventCoord(event) + this._pointerId = event.pointerId + console.log('start pos:', this._startPos) + this._swiping = true + this._swiped = false } move (event) { - // console.log('move called', event) - if (isSwipeEvent(event)) { - const touch = event.changedTouches[0] - const delta = deltaCoord(this._startPos, touchCoord(touch)) + if (this._swiping && this._pointerId === event.pointerId) { + this._swiped = true + + const coord = pointerEventCoord(event) + const delta = deltaCoord(this._startPos, coord) this.swipePreviewCallback(delta) - } else if (isScaleEvent(event)) { - console.log('is scale event') - const coords = [...event.targetTouches].map(touchCoord) - const curPos = avgCoord(coords) - const curDistance = vectorLength(deltaCoord(coords[0], coords[1])) - const scaling = curDistance / this._startDistance - const posDiff = vectorMinus(curPos, this._startPos) - // const delta = vectorAdd(numProduct((1 - scaling), this._startPos), posDiff) - const delta = posDiff - // console.log( - // 'is scale event, cur =', curPos, - // 'dist =', curDistance, - // 'scale =', scaling, - // 'delta =', delta) - this.pinchPreviewCallback(delta, scaling) } } - end (event) { - console.log('end() called', event) - if (isScaleEventEnd(event)) { - this.pinchEndCallback() - } - - if (!isSwipeEventEnd(event)) { - console.log('not swipe event') + cancel (event) { + if (!this._swiping || this._pointerId !== event.pointerId) { return } + + this.swipeCancelCallback() + } + + end (event) { if (!this._swiping) { console.log('not swiping') return } - this.swiping = false - console.log('is swipe event') + if (this._pointerId !== event.pointerId) { + console.log('pointer id does not match') + return + } + + this._swiping = false + + console.log('end: is swipe event') // movement too small - const touch = event.changedTouches[0] - const delta = deltaCoord(this._startPos, touchCoord(touch)) - this.swipePreviewCallback(delta) + const coord = pointerEventCoord(event) + const delta = deltaCoord(this._startPos, coord) const sign = (() => { if (vectorLength(delta) < this.threshold) { @@ -198,7 +183,12 @@ class SwipeAndScaleGesture { return isPositive ? 1 : -1 })() - this.swipeEndCallback(sign) + if (this._swiped) { + this.swipeEndCallback(sign) + } else { + this.swipelessClickCallback() + } + this._reset() } } @@ -210,7 +200,7 @@ const GestureService = { swipeGesture, beginSwipe, updateSwipe, - SwipeAndScaleGesture + SwipeAndClickGesture } export default GestureService From 23a6b86ef3c976509bad4fb4f9a223a5724ec7e5 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Mon, 2 Aug 2021 19:21:18 -0400 Subject: [PATCH 07/29] Clean up --- src/components/media_modal/media_modal.js | 23 ++------------ src/components/media_modal/media_modal.vue | 7 ++--- .../gesture_service/gesture_service.js | 30 ++++++------------- 3 files changed, 15 insertions(+), 45 deletions(-) diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js index b4c0cfb4..2f00f5f9 100644 --- a/src/components/media_modal/media_modal.js +++ b/src/components/media_modal/media_modal.js @@ -37,7 +37,9 @@ const MediaModal = { return { loading: false, swipeDirection: GestureService.DIRECTION_LEFT, - swipeThreshold: 50 + swipeThreshold: 50, + pinchZoomMinScale: 1, + pinchZoomScaleResetLimit: 1.2 } }, computed: { @@ -62,25 +64,6 @@ const MediaModal = { type () { return this.currentMedia ? this.getType(this.currentMedia) : null }, - scaling () { - return this.$store.state.mediaViewer.swipeScaler.scaling - }, - offsets () { - return this.$store.state.mediaViewer.swipeScaler.offsets - }, - transform () { - return `translate(${this.offsets[0]}px, ${this.offsets[1]}px) scale(${this.scaling}, ${this.scaling})` - } - }, - created () { - // this.mediaGesture = new GestureService.SwipeAndScaleGesture({ - // callbackPositive: this.goNext, - // callbackNegative: this.goPrev, - // swipePreviewCallback: this.handleSwipePreview, - // swipeEndCallback: this.handleSwipeEnd, - // pinchPreviewCallback: this.handlePinchPreview, - // pinchEndCallback: this.handlePinchEnd - // }) }, methods: { getType (media) { diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue index e385024e..76cf4319 100644 --- a/src/components/media_modal/media_modal.vue +++ b/src/components/media_modal/media_modal.vue @@ -16,12 +16,11 @@ ref="pinchZoom" class="modal-image-container-inner" selector=".modal-image" - allow-pan-min-scale="1" - min-scale="1" - reset-to-min-scale-limit="1.2" reach-min-scale-strategy="reset" stop-propagate-handled="stop-propgate-handled" - :inner-class="'modal-image-container-inner'" + :allow-pan-min-scale="pinchZoomMinScale" + :min-scale="pinchZoomMinScale" + :reset-to-min-scale-limit="pinchZoomScaleResetLimit" > (e.touches.length === 1) -// const isSwipeEventEnd = e => (e.changedTouches.length === 1) - -// const isScaleEvent = e => (e.targetTouches.length === 2) -// const isScaleEventEnd = e => (e.targetTouches.length === 1) - const deltaCoord = (oldCoord, newCoord) => [newCoord[0] - oldCoord[0], newCoord[1] - oldCoord[1]] -// const vectorMinus = (a, b) => a.map((k, n) => k - b[n]) -// const vectorAdd = (a, b) => a.map((k, n) => k + b[n]) - -// const avgCoord = (coords) => [...coords].reduce(vectorAdd, [0, 0]).map(d => d / coords.length) - const touchCoord = touch => [touch.screenX, touch.screenY] const touchEventCoord = e => touchCoord(e.touches[0]) @@ -31,13 +18,14 @@ const perpendicular = v => [v[1], -v[0]] const dotProduct = (v1, v2) => v1[0] * v2[0] + v1[1] * v2[1] -// const numProduct = (num, v) => v.map(k => num * k) - const project = (v1, v2) => { const scalar = (dotProduct(v1, v2) / dotProduct(v2, v2)) return [scalar * v2[0], scalar * v2[1]] } +// const debug = console.log +const debug = () => {} + // direction: either use the constants above or an arbitrary 2d vector. // threshold: how many Px to move from touch origin before checking if the // callback should be called. @@ -96,7 +84,7 @@ class SwipeAndClickGesture { swipelessClickCallback, threshold = 30, perpendicularTolerance = 1.0 }) { - const nop = () => { console.log('Warning: Not implemented') } + const nop = () => { debug('Warning: Not implemented') } this.direction = direction this.swipePreviewCallback = swipePreviewCallback || nop this.swipeEndCallback = swipeEndCallback || nop @@ -115,11 +103,11 @@ class SwipeAndClickGesture { } start (event) { - console.log('start() called', event) + debug('start() called', event) this._startPos = pointerEventCoord(event) this._pointerId = event.pointerId - console.log('start pos:', this._startPos) + debug('start pos:', this._startPos) this._swiping = true this._swiped = false } @@ -145,18 +133,18 @@ class SwipeAndClickGesture { end (event) { if (!this._swiping) { - console.log('not swiping') + debug('not swiping') return } if (this._pointerId !== event.pointerId) { - console.log('pointer id does not match') + debug('pointer id does not match') return } this._swiping = false - console.log('end: is swipe event') + debug('end: is swipe event') // movement too small const coord = pointerEventCoord(event) From 0190a360709cde899387b311dcf4bbaf508b00ba Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Mon, 2 Aug 2021 19:33:33 -0400 Subject: [PATCH 08/29] Add missing swipe click component --- src/components/swipe_click/swipe_click.js | 85 ++++++++++++++++++++++ src/components/swipe_click/swipe_click.vue | 14 ++++ 2 files changed, 99 insertions(+) create mode 100644 src/components/swipe_click/swipe_click.js create mode 100644 src/components/swipe_click/swipe_click.vue diff --git a/src/components/swipe_click/swipe_click.js b/src/components/swipe_click/swipe_click.js new file mode 100644 index 00000000..49b097ce --- /dev/null +++ b/src/components/swipe_click/swipe_click.js @@ -0,0 +1,85 @@ +import GestureService from '../../services/gesture_service/gesture_service' + +/** + * props: + * direction: a vector that indicates the direction of the intended swipe + * threshold: the minimum distance in pixels the swipe has moved on `direction' + * for swipe-finished() to have a non-zero sign + * perpendicularTolerance: see gesture_service + * + * Events: + * preview-requested(offsets) + * Emitted when the pointer has moved. + * offsets: the offsets from the start of the swipe to the current cursor position + * + * swipe-canceled() + * Emitted when the swipe has been canceled due to a pointercancel event. + * + * swipe-finished(sign: 0|-1|1) + * Emitted when the swipe has finished. + * sign: if the swipe does not meet the threshold, 0 + * if the swipe meets the threshold in the positive direction, 1 + * if the swipe meets the threshold in the negative direction, -1 + * + * swipeless-clicked() + * Emitted when there is a click without swipe. + * This and swipe-finished() cannot be emitted for the same pointerup event. + */ +const SwipeClick = { + props: { + direction: { + type: Array + }, + threshold: { + type: Number, + default: 30 + }, + perpendicularTolerance: { + type: Number, + default: 1.0 + } + }, + methods: { + handlePointerDown (event) { + this.$gesture.start(event) + }, + handlePointerMove (event) { + this.$gesture.move(event) + }, + handlePointerUp (event) { + this.$gesture.end(event) + }, + handlePointerCancel (event) { + this.$gesture.cancel(event) + }, + handleNativeClick (event) { + event.stopPropagation() + event.preventDefault() + }, + preview (offsets) { + this.$emit('preview-requested', offsets) + }, + end (sign) { + this.$emit('swipe-finished', sign) + }, + click () { + this.$emit('swipeless-clicked') + }, + cancel () { + this.$emit('swipe-canceled') + } + }, + created () { + this.$gesture = new GestureService.SwipeAndClickGesture({ + direction: this.direction, + threshold: this.threshold, + perpendicularTolerance: this.perpendicularTolerance, + swipePreviewCallback: this.preview, + swipeEndCallback: this.end, + swipeCancelCallback: this.cancel, + swipelessClickCallback: this.click + }); + } +} + +export default SwipeClick diff --git a/src/components/swipe_click/swipe_click.vue b/src/components/swipe_click/swipe_click.vue new file mode 100644 index 00000000..5372071d --- /dev/null +++ b/src/components/swipe_click/swipe_click.vue @@ -0,0 +1,14 @@ + + + From 3502d374e3d8546699f644fad2a5e98598d7df3a Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Mon, 2 Aug 2021 19:34:18 -0400 Subject: [PATCH 09/29] Prevent the click event from firing on content below modal --- src/components/media_modal/media_modal.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js index 2f00f5f9..f4c99f6a 100644 --- a/src/components/media_modal/media_modal.js +++ b/src/components/media_modal/media_modal.js @@ -70,7 +70,12 @@ const MediaModal = { return fileTypeService.fileType(media.mimetype) }, hide () { - this.$store.dispatch('closeMediaViewer') + // HACK: Closing immediately via a touch will cause the click + // to be processed on the content below the overlay + const transitionTime = 100 // ms + setTimeout(() => { + this.$store.dispatch('closeMediaViewer') + }, transitionTime) }, goPrev () { if (this.canNavigate) { From f3269cdc106511bc589dc68e71619a0d9581cd67 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Mon, 2 Aug 2021 19:44:01 -0400 Subject: [PATCH 10/29] Make lint happy --- src/components/media_modal/media_modal.js | 4 ---- src/components/pinch_zoom/pinch_zoom.js | 6 ++++-- src/components/swipe_click/swipe_click.js | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js index f4c99f6a..3de9822b 100644 --- a/src/components/media_modal/media_modal.js +++ b/src/components/media_modal/media_modal.js @@ -20,10 +20,6 @@ library.add( faCircleNotch ) -const onlyXAxis = ([x, y]) => [x, 0] -const SCALING_RESET_MIN = 1.1 -const SCALING_ENABLE_MOVE_THRESHOLD = 1 - const MediaModal = { components: { StillImage, diff --git a/src/components/pinch_zoom/pinch_zoom.js b/src/components/pinch_zoom/pinch_zoom.js index 36bebbce..82670ddf 100644 --- a/src/components/pinch_zoom/pinch_zoom.js +++ b/src/components/pinch_zoom/pinch_zoom.js @@ -1,11 +1,13 @@ import PinchZoom from '@kazvmoe-infra/pinch-zoom-element' export default { - props: { - }, methods: { setTransform ({ scale, x, y }) { this.$el.setTransform({ scale, x, y }) } + }, + created () { + // Make lint happy + (() => PinchZoom)() } } diff --git a/src/components/swipe_click/swipe_click.js b/src/components/swipe_click/swipe_click.js index 49b097ce..ac77154a 100644 --- a/src/components/swipe_click/swipe_click.js +++ b/src/components/swipe_click/swipe_click.js @@ -78,7 +78,7 @@ const SwipeClick = { swipeEndCallback: this.end, swipeCancelCallback: this.cancel, swipelessClickCallback: this.click - }); + }) } } From 839627ffc42f7ac2515ca58207b52e24f76f3369 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Mon, 2 Aug 2021 20:03:01 -0400 Subject: [PATCH 11/29] Reset position on swipe end even if we cannot navigate --- src/components/media_modal/media_modal.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js index 3de9822b..91413563 100644 --- a/src/components/media_modal/media_modal.js +++ b/src/components/media_modal/media_modal.js @@ -81,7 +81,6 @@ const MediaModal = { this.loading = true } this.$store.dispatch('setCurrentMedia', newMedia) - this.$refs.pinchZoom.setTransform({ scale: 1, x: 0, y: 0 }) } }, goNext () { @@ -92,7 +91,6 @@ const MediaModal = { this.loading = true } this.$store.dispatch('setCurrentMedia', newMedia) - this.$refs.pinchZoom.setTransform({ scale: 1, x: 0, y: 0 }) } }, onImageLoaded () { @@ -103,9 +101,8 @@ const MediaModal = { }, handleSwipeEnd (sign) { console.log('handleSwipeEnd:', sign) - if (sign === 0) { - this.$refs.pinchZoom.setTransform({ scale: 1, x: 0, y: 0 }) - } else if (sign > 0) { + this.$refs.pinchZoom.setTransform({ scale: 1, x: 0, y: 0 }) + if (sign > 0) { this.goNext() } else { this.goPrev() From 9f3a983fef9dfedef8f44855e1f939ea944cd0ba Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Mon, 2 Aug 2021 20:32:02 -0400 Subject: [PATCH 12/29] Use native click for hiding overlay The pointerup strategy is unsuccessful, as some other overlays (Firefox's Inspect Element) will pass down pointerup events. --- src/components/swipe_click/swipe_click.js | 3 +-- .../gesture_service/gesture_service.js | 26 ++++++++++++++++--- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/components/swipe_click/swipe_click.js b/src/components/swipe_click/swipe_click.js index ac77154a..b979f42a 100644 --- a/src/components/swipe_click/swipe_click.js +++ b/src/components/swipe_click/swipe_click.js @@ -53,8 +53,7 @@ const SwipeClick = { this.$gesture.cancel(event) }, handleNativeClick (event) { - event.stopPropagation() - event.preventDefault() + this.$gesture.click(event) }, preview (offsets) { this.$emit('preview-requested', offsets) diff --git a/src/services/gesture_service/gesture_service.js b/src/services/gesture_service/gesture_service.js index 238b7875..cd9e3ba2 100644 --- a/src/services/gesture_service/gesture_service.js +++ b/src/services/gesture_service/gesture_service.js @@ -4,6 +4,8 @@ const DIRECTION_RIGHT = [1, 0] const DIRECTION_UP = [0, -1] const DIRECTION_DOWN = [0, 1] +const BUTTON_LEFT = 0 + const deltaCoord = (oldCoord, newCoord) => [newCoord[0] - oldCoord[0], newCoord[1] - oldCoord[1]] const touchCoord = touch => [touch.screenX, touch.screenY] @@ -23,8 +25,8 @@ const project = (v1, v2) => { return [scalar * v2[0], scalar * v2[1]] } -// const debug = console.log -const debug = () => {} +const debug = console.log +// const debug = () => {} // direction: either use the constants above or an arbitrary 2d vector. // threshold: how many Px to move from touch origin before checking if the @@ -100,11 +102,17 @@ class SwipeAndClickGesture { this._pointerId = -1 this._swiping = false this._swiped = false + this._preventNextClick = false } start (event) { debug('start() called', event) + // Only handle left click + if (event.button !== BUTTON_LEFT) { + return + } + this._startPos = pointerEventCoord(event) this._pointerId = event.pointerId debug('start pos:', this._startPos) @@ -124,6 +132,7 @@ class SwipeAndClickGesture { } cancel (event) { + debug('cancel called') if (!this._swiping || this._pointerId !== event.pointerId) { return } @@ -146,6 +155,8 @@ class SwipeAndClickGesture { debug('end: is swipe event') + debug('button = ', event.button) + // movement too small const coord = pointerEventCoord(event) const delta = deltaCoord(this._startPos, coord) @@ -171,9 +182,18 @@ class SwipeAndClickGesture { return isPositive ? 1 : -1 })() + const swiped = this._swiped if (this._swiped) { this.swipeEndCallback(sign) - } else { + } + this._reset() + if (swiped) { + this._preventNextClick = true + } + } + + click (event) { + if (!this._preventNextClick) { this.swipelessClickCallback() } this._reset() From ddf6c9cef085757ca008c9c7e67f998315948f45 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Mon, 2 Aug 2021 20:46:10 -0400 Subject: [PATCH 13/29] Allow pinch-zoom to fill the whole screen --- src/components/media_modal/media_modal.vue | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue index 76cf4319..80ce1dea 100644 --- a/src/components/media_modal/media_modal.vue +++ b/src/components/media_modal/media_modal.vue @@ -140,7 +140,10 @@ flex-direction: column; max-width: 90%; max-height: 95%; + width: 100%; + height: 100%; flex-grow: 1; + justify-content: center; &-inner { width: 100%; @@ -149,6 +152,7 @@ display: flex; flex-direction: column; align-items: center; + justify-content: center; } } From cb19db1006e84d9429d57d8a574dfaa9980d8ca7 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Mon, 2 Aug 2021 21:02:57 -0400 Subject: [PATCH 14/29] Update pinch-zoom-element --- yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 176b0c7d..73e8a2ed 100644 --- a/yarn.lock +++ b/yarn.lock @@ -918,7 +918,7 @@ "@kazvmoe-infra/pinch-zoom-element@https://lily.kazv.moe/infra/pinch-zoom-element.git": version "1.1.1" - resolved "https://lily.kazv.moe/infra/pinch-zoom-element.git#de150c01057f0de0c3ef669917399fe370e36f40" + resolved "https://lily.kazv.moe/infra/pinch-zoom-element.git#fbb4d71b1c09c2e24db054cf15d716e125a53bf9" dependencies: pointer-tracker "^2.0.3" From 6980e4ddf1aa8dfd8c3bba0ea6cc7de90f531ba9 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Mon, 2 Aug 2021 21:19:04 -0400 Subject: [PATCH 15/29] Scale swipe threshold with viewport width --- src/components/media_modal/media_modal.js | 7 +++++-- src/components/swipe_click/swipe_click.js | 4 ++-- src/services/gesture_service/gesture_service.js | 11 +++++++---- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js index 91413563..6a368508 100644 --- a/src/components/media_modal/media_modal.js +++ b/src/components/media_modal/media_modal.js @@ -33,7 +33,10 @@ const MediaModal = { return { loading: false, swipeDirection: GestureService.DIRECTION_LEFT, - swipeThreshold: 50, + swipeThreshold: () => { + const considerableMoveRatio = 1 / 4 + return window.innerWidth * considerableMoveRatio + }, pinchZoomMinScale: 1, pinchZoomScaleResetLimit: 1.2 } @@ -104,7 +107,7 @@ const MediaModal = { this.$refs.pinchZoom.setTransform({ scale: 1, x: 0, y: 0 }) if (sign > 0) { this.goNext() - } else { + } else if (sign < 0) { this.goPrev() } }, diff --git a/src/components/swipe_click/swipe_click.js b/src/components/swipe_click/swipe_click.js index b979f42a..238e6df8 100644 --- a/src/components/swipe_click/swipe_click.js +++ b/src/components/swipe_click/swipe_click.js @@ -31,8 +31,8 @@ const SwipeClick = { type: Array }, threshold: { - type: Number, - default: 30 + type: Function, + default: () => 30 }, perpendicularTolerance: { type: Number, diff --git a/src/services/gesture_service/gesture_service.js b/src/services/gesture_service/gesture_service.js index cd9e3ba2..97a26ba7 100644 --- a/src/services/gesture_service/gesture_service.js +++ b/src/services/gesture_service/gesture_service.js @@ -25,8 +25,8 @@ const project = (v1, v2) => { return [scalar * v2[0], scalar * v2[1]] } -const debug = console.log -// const debug = () => {} +// const debug = console.log +const debug = () => {} // direction: either use the constants above or an arbitrary 2d vector. // threshold: how many Px to move from touch origin before checking if the @@ -92,7 +92,7 @@ class SwipeAndClickGesture { this.swipeEndCallback = swipeEndCallback || nop this.swipeCancelCallback = swipeCancelCallback || nop this.swipelessClickCallback = swipelessClickCallback || nop - this.threshold = threshold + this.threshold = typeof threshold === 'function' ? threshold : () => threshold this.perpendicularTolerance = perpendicularTolerance this._reset() } @@ -162,7 +162,10 @@ class SwipeAndClickGesture { const delta = deltaCoord(this._startPos, coord) const sign = (() => { - if (vectorLength(delta) < this.threshold) { + debug( + 'threshold = ', this.threshold(), + 'vector len =', vectorLength(delta)) + if (vectorLength(delta) < this.threshold()) { return 0 } // movement is opposite from direction From a485ebc2bb120e7cc6eafb7d86a5f876608f7fe1 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Mon, 2 Aug 2021 22:08:04 -0400 Subject: [PATCH 16/29] Check whether we swiped only for mouse pointer --- src/services/gesture_service/gesture_service.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/services/gesture_service/gesture_service.js b/src/services/gesture_service/gesture_service.js index 97a26ba7..741d4a12 100644 --- a/src/services/gesture_service/gesture_service.js +++ b/src/services/gesture_service/gesture_service.js @@ -190,7 +190,11 @@ class SwipeAndClickGesture { this.swipeEndCallback(sign) } this._reset() - if (swiped) { + // Only a mouse will fire click event when + // the end point is far from the starting point + // so for other kinds of pointers do not check + // whether we have swiped + if (swiped && event.pointerType === 'mouse') { this._preventNextClick = true } } From 3b4afdf567a081338a9f74319c689c7b7fe55bb6 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Mon, 2 Aug 2021 22:45:19 -0400 Subject: [PATCH 17/29] Clean up --- src/modules/media_viewer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/media_viewer.js b/src/modules/media_viewer.js index 0299b04e..ebcba01d 100644 --- a/src/modules/media_viewer.js +++ b/src/modules/media_viewer.js @@ -20,7 +20,7 @@ const mediaViewer = { } }, actions: { - setMedia ({ commit, dispatch }, attachments) { + setMedia ({ commit }, attachments) { const media = attachments.filter(attachment => { const type = fileTypeService.fileType(attachment.mimetype) return supportedTypes.has(type) @@ -31,7 +31,7 @@ const mediaViewer = { const index = state.media.indexOf(current) commit('setCurrentMedia', index || 0) }, - closeMediaViewer ({ commit, dispatch }) { + closeMediaViewer ({ commit }) { commit('close') } } From 495960c83ae6db2146a34316655faefe3c0b87b5 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Mon, 2 Aug 2021 23:24:39 -0400 Subject: [PATCH 18/29] Bump pinch-zoom-element version --- yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 73e8a2ed..78ee50fb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -918,7 +918,7 @@ "@kazvmoe-infra/pinch-zoom-element@https://lily.kazv.moe/infra/pinch-zoom-element.git": version "1.1.1" - resolved "https://lily.kazv.moe/infra/pinch-zoom-element.git#fbb4d71b1c09c2e24db054cf15d716e125a53bf9" + resolved "https://lily.kazv.moe/infra/pinch-zoom-element.git#b5d2e9fb41231e1bff12058bbfc55df05cfdc1eb" dependencies: pointer-tracker "^2.0.3" From 49fa9c47e961c44acc75b30cf8be69df1fdd7d0a Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Wed, 8 Sep 2021 21:20:14 -0400 Subject: [PATCH 19/29] Bump @kazvmoe-infra/pinch-zoom-element to 1.2.0 on npm https://git.pleroma.social/pleroma/pleroma-fe/-/merge_requests/1403 --- package.json | 2 +- yarn.lock | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 34029765..2f3982e9 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "@fortawesome/free-regular-svg-icons": "5.15.1", "@fortawesome/free-solid-svg-icons": "5.15.1", "@fortawesome/vue-fontawesome": "2.0.0", - "@kazvmoe-infra/pinch-zoom-element": "https://lily.kazv.moe/infra/pinch-zoom-element.git", + "@kazvmoe-infra/pinch-zoom-element": "^1.2.0", "body-scroll-lock": "2.6.4", "chromatism": "3.0.0", "cropperjs": "1.4.3", diff --git a/yarn.lock b/yarn.lock index 78ee50fb..c704ecca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -916,9 +916,10 @@ resolved "https://registry.yarnpkg.com/@fortawesome/vue-fontawesome/-/vue-fontawesome-2.0.0.tgz#63da3e459147cebb0a8d58eed81d6071db9f5973" integrity sha512-N3VKw7KzRfOm8hShUVldpinlm13HpvLBQgT63QS+aCrIRLwjoEUXY5Rcmttbfb6HkzZaeqjLqd/aZCQ53UjQpg== -"@kazvmoe-infra/pinch-zoom-element@https://lily.kazv.moe/infra/pinch-zoom-element.git": - version "1.1.1" - resolved "https://lily.kazv.moe/infra/pinch-zoom-element.git#b5d2e9fb41231e1bff12058bbfc55df05cfdc1eb" +"@kazvmoe-infra/pinch-zoom-element@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@kazvmoe-infra/pinch-zoom-element/-/pinch-zoom-element-1.2.0.tgz#eb3ca34c53b4410c689d60aca02f4a497ce84aba" + integrity sha512-HBrhH5O/Fsp2bB7EGTXzCsBAVcMjknSagKC5pBdGpKsF8meHISR0kjDIdw4YoE0S+0oNMwJ6ZUZyIBrdywxPPw== dependencies: pointer-tracker "^2.0.3" From 5829cd98af60b758ee3a39c35f76f4da95a630c3 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Wed, 8 Sep 2021 21:26:59 -0400 Subject: [PATCH 20/29] Clean up debug code for image pinch zoom --- src/components/media_modal/media_modal.js | 1 - .../gesture_service/gesture_service.js | 18 +----------------- 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js index 6a368508..4ed0b66f 100644 --- a/src/components/media_modal/media_modal.js +++ b/src/components/media_modal/media_modal.js @@ -103,7 +103,6 @@ const MediaModal = { this.$refs.pinchZoom.setTransform({ scale: 1, x: offsets[0], y: 0 }) }, handleSwipeEnd (sign) { - console.log('handleSwipeEnd:', sign) this.$refs.pinchZoom.setTransform({ scale: 1, x: 0, y: 0 }) if (sign > 0) { this.goNext() diff --git a/src/services/gesture_service/gesture_service.js b/src/services/gesture_service/gesture_service.js index 741d4a12..94da3f43 100644 --- a/src/services/gesture_service/gesture_service.js +++ b/src/services/gesture_service/gesture_service.js @@ -25,9 +25,6 @@ const project = (v1, v2) => { return [scalar * v2[0], scalar * v2[1]] } -// const debug = console.log -const debug = () => {} - // direction: either use the constants above or an arbitrary 2d vector. // threshold: how many Px to move from touch origin before checking if the // callback should be called. @@ -86,7 +83,7 @@ class SwipeAndClickGesture { swipelessClickCallback, threshold = 30, perpendicularTolerance = 1.0 }) { - const nop = () => { debug('Warning: Not implemented') } + const nop = () => {} this.direction = direction this.swipePreviewCallback = swipePreviewCallback || nop this.swipeEndCallback = swipeEndCallback || nop @@ -106,8 +103,6 @@ class SwipeAndClickGesture { } start (event) { - debug('start() called', event) - // Only handle left click if (event.button !== BUTTON_LEFT) { return @@ -115,7 +110,6 @@ class SwipeAndClickGesture { this._startPos = pointerEventCoord(event) this._pointerId = event.pointerId - debug('start pos:', this._startPos) this._swiping = true this._swiped = false } @@ -132,7 +126,6 @@ class SwipeAndClickGesture { } cancel (event) { - debug('cancel called') if (!this._swiping || this._pointerId !== event.pointerId) { return } @@ -142,29 +135,20 @@ class SwipeAndClickGesture { end (event) { if (!this._swiping) { - debug('not swiping') return } if (this._pointerId !== event.pointerId) { - debug('pointer id does not match') return } this._swiping = false - debug('end: is swipe event') - - debug('button = ', event.button) - // movement too small const coord = pointerEventCoord(event) const delta = deltaCoord(this._startPos, coord) const sign = (() => { - debug( - 'threshold = ', this.threshold(), - 'vector len =', vectorLength(delta)) if (vectorLength(delta) < this.threshold()) { return 0 } From 76727cd39cf588ffc1c15c0a2fd8f1ae30ada7ea Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Wed, 8 Sep 2021 21:28:29 -0400 Subject: [PATCH 21/29] Remove image box-shadow in media modal The box-shadow causes an image to be very blurry on Webkit browsers (experienced: Konqueror, Safari; heard of: Chrome) when scaled up if the initial size of the image is much smaller than the actual size (e.g. when viewing a very long picture). The shadow is not really obvious anyway. --- src/components/media_modal/media_modal.vue | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue index 80ce1dea..0fbdb558 100644 --- a/src/components/media_modal/media_modal.vue +++ b/src/components/media_modal/media_modal.vue @@ -161,7 +161,6 @@ max-height: 100%; min-width: 0; min-height: 0; - box-shadow: 0px 5px 15px 0 rgba(0, 0, 0, 0.5); image-orientation: from-image; // NOTE: only FF supports this animation: 0.1s cubic-bezier(0.7, 0, 1, 0.6) media-fadein; } From 22d8961c5e1418d5037fbdd80a49ff434663d418 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Wed, 8 Sep 2021 21:46:19 -0400 Subject: [PATCH 22/29] Add changelog for https://git.pleroma.social/pleroma/pleroma-fe/-/merge_requests/1403 --- CHANGELOG.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae54025a..d7cc6994 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,19 +36,21 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Attachments are truncated just like post contents - Media modal now also displays description and counter position in gallery (i.e. 1/5) - Ability to rearrange order of attachments when uploading +- Enabled users to zoom and pan images in media viewer with mouse and touch + ## [2.4.2] - 2022-01-09 -### Added +### Added - Added Apply and Reset buttons to the bottom of theme tab to minimize UI travel - Implemented user option to always show floating New Post button (normally mobile-only) -- Display reasons for instance specific policies +- Display reasons for instance specific policies - Added functionality to cancel follow request ### Fixed - Fixed link to external profile not working on user profiles -- Fixed mobile shoutbox display +- Fixed mobile shoutbox display - Fixed favicon badge not working in Chrome -- Escape html more properly in subject/display name +- Escape html more properly in subject/display name ## [2.4.0] - 2021-08-08 From 1128cc463c4b16c41ab21c554646a8104ba58826 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Wed, 8 Sep 2021 21:50:28 -0400 Subject: [PATCH 23/29] Fix video in media modal not displaying properly --- src/components/media_modal/media_modal.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue index 0fbdb558..abc1d797 100644 --- a/src/components/media_modal/media_modal.vue +++ b/src/components/media_modal/media_modal.vue @@ -5,6 +5,7 @@ @backdropClicked="hide" > Date: Sun, 20 Feb 2022 22:02:31 -0500 Subject: [PATCH 24/29] Fix webkit image blurs --- src/components/media_modal/media_modal.vue | 53 ++++++++-------------- 1 file changed, 19 insertions(+), 34 deletions(-) diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue index abc1d797..ff9c4953 100644 --- a/src/components/media_modal/media_modal.vue +++ b/src/components/media_modal/media_modal.vue @@ -133,42 +133,28 @@ } } -.modal-image-container { - display: flex; - overflow: hidden; - align-items: center; - flex-direction: column; - max-width: 90%; - max-height: 95%; - width: 100%; - height: 100%; - flex-grow: 1; - justify-content: center; - - &-inner { + .modal-image-container { + display: flex; + overflow: hidden; + align-items: center; + flex-direction: column; + max-width: 90%; + max-height: 95%; width: 100%; height: 100%; flex-grow: 1; - display: flex; - flex-direction: column; - align-items: center; justify-content: center; + + &-inner { + width: 100%; + height: 100%; + flex-grow: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + } } -} - -.modal-image { - max-width: 100%; - max-height: 100%; - min-width: 0; - min-height: 0; - image-orientation: from-image; // NOTE: only FF supports this - animation: 0.1s cubic-bezier(0.7, 0, 1, 0.6) media-fadein; -} - -//.modal-image { -// height: 90vh; -// width: 100%; -//} .description, .counter { @@ -189,9 +175,8 @@ } .modal-image { - max-width: 90%; - max-height: 90%; - box-shadow: 0px 5px 15px 0 rgba(0, 0, 0, 0.5); + max-width: 100%; + max-height: 100%; image-orientation: from-image; // NOTE: only FF supports this animation: 0.1s cubic-bezier(0.7, 0, 1, 0.6) media-fadein; From 7dd1a0dd30773fd51508feaf93be53bdb744ec79 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sun, 20 Feb 2022 22:45:58 -0500 Subject: [PATCH 25/29] Prevent hiding media viewer if swiped over SwipeClick --- src/components/media_modal/media_modal.js | 9 +++++++++ src/components/media_modal/media_modal.vue | 3 ++- src/services/gesture_service/gesture_service.js | 8 +++++--- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js index 4ed0b66f..0c19ed50 100644 --- a/src/components/media_modal/media_modal.js +++ b/src/components/media_modal/media_modal.js @@ -76,6 +76,15 @@ const MediaModal = { this.$store.dispatch('closeMediaViewer') }, transitionTime) }, + hideIfNotSwiped (event) { + // If we have swiped over SwipeClick, do not trigger hide + const comp = this.$refs.swipeClick + if (!comp) { + this.hide() + } else { + comp.$gesture.click(event) + } + }, goPrev () { if (this.canNavigate) { const prevIndex = this.currentIndex === 0 ? this.media.length - 1 : (this.currentIndex - 1) diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue index ff9c4953..1aa66a55 100644 --- a/src/components/media_modal/media_modal.vue +++ b/src/components/media_modal/media_modal.vue @@ -2,10 +2,11 @@ {} this.direction = direction @@ -90,6 +92,7 @@ class SwipeAndClickGesture { this.swipeCancelCallback = swipeCancelCallback || nop this.swipelessClickCallback = swipelessClickCallback || nop this.threshold = typeof threshold === 'function' ? threshold : () => threshold + this.disableClickThreshold = typeof disableClickThreshold === 'function' ? disableClickThreshold : () => disableClickThreshold this.perpendicularTolerance = perpendicularTolerance this._reset() } @@ -169,7 +172,6 @@ class SwipeAndClickGesture { return isPositive ? 1 : -1 })() - const swiped = this._swiped if (this._swiped) { this.swipeEndCallback(sign) } @@ -178,7 +180,7 @@ class SwipeAndClickGesture { // the end point is far from the starting point // so for other kinds of pointers do not check // whether we have swiped - if (swiped && event.pointerType === 'mouse') { + if (vectorLength(delta) >= this.disableClickThreshold() && event.pointerType === 'mouse') { this._preventNextClick = true } } From 7e21853ccae5625ce0898a6408d6141331ed5471 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sun, 20 Feb 2022 22:49:58 -0500 Subject: [PATCH 26/29] Lint --- src/components/media_modal/media_modal.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js index 0c19ed50..e7efc018 100644 --- a/src/components/media_modal/media_modal.js +++ b/src/components/media_modal/media_modal.js @@ -5,7 +5,6 @@ import PinchZoom from '../pinch_zoom/pinch_zoom.vue' import SwipeClick from '../swipe_click/swipe_click.vue' import GestureService from '../../services/gesture_service/gesture_service' import Flash from 'src/components/flash/flash.vue' -import Vuex from 'vuex' import fileTypeService from '../../services/file_type/file_type.service.js' import { library } from '@fortawesome/fontawesome-svg-core' import { @@ -62,7 +61,7 @@ const MediaModal = { }, type () { return this.currentMedia ? this.getType(this.currentMedia) : null - }, + } }, methods: { getType (media) { From 5fb302d0f1f311985f7d26a8e8f11e15ec72d26f Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Thu, 3 Mar 2022 12:51:13 -0500 Subject: [PATCH 27/29] Add hide button to media modal --- src/components/media_modal/media_modal.js | 6 +- src/components/media_modal/media_modal.vue | 74 ++++++++++++++++------ 2 files changed, 60 insertions(+), 20 deletions(-) diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js index e7efc018..01a90377 100644 --- a/src/components/media_modal/media_modal.js +++ b/src/components/media_modal/media_modal.js @@ -10,13 +10,15 @@ import { library } from '@fortawesome/fontawesome-svg-core' import { faChevronLeft, faChevronRight, - faCircleNotch + faCircleNotch, + faTimes } from '@fortawesome/free-solid-svg-icons' library.add( faChevronLeft, faChevronRight, - faCircleNotch + faCircleNotch, + faTimes ) const MediaModal = { diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue index 1aa66a55..2d00d7fb 100644 --- a/src/components/media_modal/media_modal.vue +++ b/src/components/media_modal/media_modal.vue @@ -58,25 +58,36 @@ + + From b67a557a8c674ee5875784ca588e5cb2228f2a73 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Thu, 3 Mar 2022 12:52:24 -0500 Subject: [PATCH 28/29] Add English translation for hide tooltip --- src/i18n/en.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/en.json b/src/i18n/en.json index 61087ec2..bdf1f041 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -119,7 +119,8 @@ "media_modal": { "previous": "Previous", "next": "Next", - "counter": "{current} / {total}" + "counter": "{current} / {total}", + "hide": "Close media viewer" }, "nav": { "about": "About", From 1b204012eaf3fcc262689a070424c618d8d3a917 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sun, 6 Mar 2022 18:38:57 -0500 Subject: [PATCH 29/29] Make media modal buttons larger --- src/components/media_modal/media_modal.vue | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue index 2d00d7fb..708a43c6 100644 --- a/src/components/media_modal/media_modal.vue +++ b/src/components/media_modal/media_modal.vue @@ -115,9 +115,10 @@