From 29cd8fbd3bd9f936d2639a2edd773e1245b0b5a5 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Mon, 2 Aug 2021 19:11:59 -0400 Subject: [PATCH] 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