Preview swipe action

This commit is contained in:
Tusooa Zhu 2021-08-01 19:46:27 -04:00
parent 61509d1b1e
commit aa70c31950
No known key found for this signature in database
GPG key ID: 7B467EDE43A08224
4 changed files with 137 additions and 25 deletions

View file

@ -3,6 +3,7 @@ import VideoAttachment from '../video_attachment/video_attachment.vue'
import Modal from '../modal/modal.vue' import Modal from '../modal/modal.vue'
import fileTypeService from '../../services/file_type/file_type.service.js' import fileTypeService from '../../services/file_type/file_type.service.js'
import GestureService from '../../services/gesture_service/gesture_service' import GestureService from '../../services/gesture_service/gesture_service'
import Vuex from 'vuex'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
faChevronLeft, faChevronLeft,
@ -14,6 +15,8 @@ library.add(
faChevronRight faChevronRight
) )
const onlyXAxis = ([x, y]) => [x, 0]
const MediaModal = { const MediaModal = {
components: { components: {
StillImage, StillImage,
@ -38,6 +41,15 @@ const MediaModal = {
}, },
type () { type () {
return this.currentMedia ? fileTypeService.fileType(this.currentMedia.mimetype) : null return this.currentMedia ? fileTypeService.fileType(this.currentMedia.mimetype) : 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 () { created () {
@ -45,6 +57,8 @@ const MediaModal = {
direction: GestureService.DIRECTION_LEFT, direction: GestureService.DIRECTION_LEFT,
callbackPositive: this.goNext, callbackPositive: this.goNext,
callbackNegative: this.goPrev, callbackNegative: this.goPrev,
swipePreviewCallback: this.handleSwipePreview,
swipeEndCallback: this.handleSwipeEnd,
threshold: 50 threshold: 50
}) })
}, },
@ -73,6 +87,18 @@ const MediaModal = {
this.$store.dispatch('setCurrent', this.media[nextIndex]) this.$store.dispatch('setCurrent', this.media[nextIndex])
} }
}, },
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) { handleKeyupEvent (e) {
if (this.showing && e.keyCode === 27) { // escape if (this.showing && e.keyCode === 27) { // escape
this.hide() this.hide()

View file

@ -10,6 +10,7 @@
:src="currentMedia.url" :src="currentMedia.url"
:alt="currentMedia.description" :alt="currentMedia.description"
:title="currentMedia.description" :title="currentMedia.description"
:style="{ transform }"
@touchstart.stop="mediaTouchStart" @touchstart.stop="mediaTouchStart"
@touchmove.stop="mediaTouchMove" @touchmove.stop="mediaTouchMove"
@touchend.stop="mediaTouchEnd" @touchend.stop="mediaTouchEnd"

View file

@ -19,19 +19,79 @@ const mediaViewer = {
} }
}, },
actions: { actions: {
setMedia ({ commit }, attachments) { setMedia ({ commit, dispatch }, attachments) {
const media = attachments.filter(attachment => { const media = attachments.filter(attachment => {
const type = fileTypeService.fileType(attachment.mimetype) const type = fileTypeService.fileType(attachment.mimetype)
return type === 'image' || type === 'video' || type === 'audio' return type === 'image' || type === 'video' || type === 'audio'
}) })
commit('setMedia', media) commit('setMedia', media)
dispatch('swipeScaler/reset')
}, },
setCurrent ({ commit, state }, current) { setCurrent ({ commit, state, dispatch }, current) {
const index = state.media.indexOf(current) const index = state.media.indexOf(current)
commit('setCurrent', index || 0) commit('setCurrent', index || 0)
dispatch('swipeScaler/reset')
}, },
closeMediaViewer ({ commit }) { closeMediaViewer ({ commit, dispatch }) {
commit('close') 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')
}
}
} }
} }
} }

View file

@ -70,14 +70,28 @@ const updateSwipe = (event, gesture) => {
} }
class SwipeAndScaleGesture { 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 ({ constructor ({
direction, callbackPositive, callbackNegative, direction,
previewCallback, threshold = 30, perpendicularTolerance = 1.0 // swipeStartCallback, pinchStartCallback,
swipePreviewCallback, pinchPreviewCallback,
swipeEndCallback, pinchEndCallback,
threshold = 30, perpendicularTolerance = 1.0
}) { }) {
const nop = () => {}
this.direction = direction this.direction = direction
this.previewCallback = previewCallback this.swipePreviewCallback = swipePreviewCallback || nop
this.callbackPositive = callbackPositive this.pinchPreviewCallback = pinchPreviewCallback || nop
this.callbackNegative = callbackNegative this.swipeEndCallback = swipeEndCallback || nop
this.pinchEndCallback = pinchEndCallback || nop
this.threshold = threshold this.threshold = threshold
this.perpendicularTolerance = perpendicularTolerance this.perpendicularTolerance = perpendicularTolerance
this._startPos = [0, 0] this._startPos = [0, 0]
@ -97,7 +111,12 @@ class SwipeAndScaleGesture {
} }
move (event) { 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 // movement too small
const touch = event.changedTouches[0] const touch = event.changedTouches[0]
const delta = deltaCoord(this._startPos, touchCoord(touch)) const delta = deltaCoord(this._startPos, touchCoord(touch))
if (vectorLength(delta) < this.threshold) return this.swipePreviewCallback(delta)
// movement is opposite from direction
const isPositive = dotProduct(delta, this.direction) > 0
// movement perpendicular to direction is too much const sign = (() => {
const towardsDir = project(delta, this.direction) if (vectorLength(delta) < this.threshold) {
const perpendicularDir = perpendicular(this.direction) return 0
const towardsPerpendicular = project(delta, perpendicularDir) }
if ( // movement is opposite from direction
vectorLength(towardsDir) * this.perpendicularTolerance < const isPositive = dotProduct(delta, this.direction) > 0
vectorLength(towardsPerpendicular)
) return
if (isPositive) { // movement perpendicular to direction is too much
this.callbackPositive() const towardsDir = project(delta, this.direction)
} else { const perpendicularDir = perpendicular(this.direction)
this.callbackNegative() const towardsPerpendicular = project(delta, perpendicularDir)
} if (
vectorLength(towardsDir) * this.perpendicularTolerance <
vectorLength(towardsPerpendicular)
) {
return 0
}
return isPositive ? 1 : -1
})()
this.swipeEndCallback(sign)
} }
} }