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

View file

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

View file

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

View file

@ -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)
}
}