Merge remote-tracking branch 'tusooa/from/develop/tusooa/media-touch-actions' into shigusegubu
* tusooa/from/develop/tusooa/media-touch-actions: (26 commits) Lint Prevent hiding media viewer if swiped over SwipeClick Fix webkit image blurs Fix video in media modal not displaying properly Add changelog for https://git.pleroma.social/pleroma/pleroma-fe/-/merge_requests/1403 Remove image box-shadow in media modal Clean up debug code for image pinch zoom Bump @kazvmoe-infra/pinch-zoom-element to 1.2.0 on npm Bump pinch-zoom-element version Clean up Check whether we swiped only for mouse pointer Scale swipe threshold with viewport width Update pinch-zoom-element Allow pinch-zoom to fill the whole screen Use native click for hiding overlay Reset position on swipe end even if we cannot navigate Make lint happy Prevent the click event from firing on content below modal Add missing swipe click component Clean up ...
This commit is contained in:
commit
5db8112cd5
11 changed files with 372 additions and 43 deletions
|
|
@ -1,9 +1,11 @@
|
|||
import StillImage from '../still-image/still-image.vue'
|
||||
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 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 fileTypeService from '../../services/file_type/file_type.service.js'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faChevronLeft,
|
||||
|
|
@ -21,12 +23,21 @@ const MediaModal = {
|
|||
components: {
|
||||
StillImage,
|
||||
VideoAttachment,
|
||||
PinchZoom,
|
||||
SwipeClick,
|
||||
Modal,
|
||||
Flash
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
loading: false
|
||||
loading: false,
|
||||
swipeDirection: GestureService.DIRECTION_LEFT,
|
||||
swipeThreshold: () => {
|
||||
const considerableMoveRatio = 1 / 4
|
||||
return window.innerWidth * considerableMoveRatio
|
||||
},
|
||||
pinchZoomMinScale: 1,
|
||||
pinchZoomScaleResetLimit: 1.2
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -52,32 +63,26 @@ const MediaModal = {
|
|||
return this.currentMedia ? this.getType(this.currentMedia) : null
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.mediaSwipeGestureRight = GestureService.swipeGesture(
|
||||
GestureService.DIRECTION_RIGHT,
|
||||
this.goPrev,
|
||||
50
|
||||
)
|
||||
this.mediaSwipeGestureLeft = GestureService.swipeGesture(
|
||||
GestureService.DIRECTION_LEFT,
|
||||
this.goNext,
|
||||
50
|
||||
)
|
||||
},
|
||||
methods: {
|
||||
getType (media) {
|
||||
return fileTypeService.fileType(media.mimetype)
|
||||
},
|
||||
mediaTouchStart (e) {
|
||||
GestureService.beginSwipe(e, this.mediaSwipeGestureRight)
|
||||
GestureService.beginSwipe(e, this.mediaSwipeGestureLeft)
|
||||
},
|
||||
mediaTouchMove (e) {
|
||||
GestureService.updateSwipe(e, this.mediaSwipeGestureRight)
|
||||
GestureService.updateSwipe(e, this.mediaSwipeGestureLeft)
|
||||
},
|
||||
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)
|
||||
},
|
||||
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) {
|
||||
|
|
@ -102,6 +107,17 @@ const MediaModal = {
|
|||
onImageLoaded () {
|
||||
this.loading = false
|
||||
},
|
||||
handleSwipePreview (offsets) {
|
||||
this.$refs.pinchZoom.setTransform({ scale: 1, x: offsets[0], y: 0 })
|
||||
},
|
||||
handleSwipeEnd (sign) {
|
||||
this.$refs.pinchZoom.setTransform({ scale: 1, x: 0, y: 0 })
|
||||
if (sign > 0) {
|
||||
this.goNext()
|
||||
} else if (sign < 0) {
|
||||
this.goPrev()
|
||||
}
|
||||
},
|
||||
handleKeyupEvent (e) {
|
||||
if (this.showing && e.keyCode === 27) { // escape
|
||||
this.hide()
|
||||
|
|
|
|||
|
|
@ -2,20 +2,38 @@
|
|||
<Modal
|
||||
v-if="showing"
|
||||
class="media-modal-view"
|
||||
@backdropClicked="hide"
|
||||
@backdropClicked="hideIfNotSwiped"
|
||||
>
|
||||
<img
|
||||
<SwipeClick
|
||||
v-if="type === 'image'"
|
||||
:class="{ loading }"
|
||||
class="modal-image"
|
||||
:src="currentMedia.url"
|
||||
:alt="currentMedia.description"
|
||||
:title="currentMedia.description"
|
||||
@touchstart.stop="mediaTouchStart"
|
||||
@touchmove.stop="mediaTouchMove"
|
||||
@click="hide"
|
||||
@load="onImageLoaded"
|
||||
ref="swipeClick"
|
||||
class="modal-image-container"
|
||||
:direction="swipeDirection"
|
||||
:threshold="swipeThreshold"
|
||||
@preview-requested="handleSwipePreview"
|
||||
@swipe-finished="handleSwipeEnd"
|
||||
@swipeless-clicked="hide"
|
||||
>
|
||||
<PinchZoom
|
||||
ref="pinchZoom"
|
||||
class="modal-image-container-inner"
|
||||
selector=".modal-image"
|
||||
reach-min-scale-strategy="reset"
|
||||
stop-propagate-handled="stop-propgate-handled"
|
||||
:allow-pan-min-scale="pinchZoomMinScale"
|
||||
:min-scale="pinchZoomMinScale"
|
||||
:reset-to-min-scale-limit="pinchZoomScaleResetLimit"
|
||||
>
|
||||
<img
|
||||
:class="{ loading }"
|
||||
class="modal-image"
|
||||
:src="currentMedia.url"
|
||||
:alt="currentMedia.description"
|
||||
:title="currentMedia.description"
|
||||
@load="onImageLoaded"
|
||||
>
|
||||
</PinchZoom>
|
||||
</SwipeClick>
|
||||
<VideoAttachment
|
||||
v-if="type === 'video'"
|
||||
class="modal-image"
|
||||
|
|
@ -103,6 +121,7 @@
|
|||
opacity: 1;
|
||||
}
|
||||
}
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.media-modal-view {
|
||||
|
|
@ -115,6 +134,29 @@
|
|||
}
|
||||
}
|
||||
|
||||
.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 {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.description,
|
||||
.counter {
|
||||
/* Hardcoded since background is also hardcoded */
|
||||
|
|
@ -134,9 +176,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;
|
||||
|
||||
|
|
|
|||
13
src/components/pinch_zoom/pinch_zoom.js
Normal file
13
src/components/pinch_zoom/pinch_zoom.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import PinchZoom from '@kazvmoe-infra/pinch-zoom-element'
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
setTransform ({ scale, x, y }) {
|
||||
this.$el.setTransform({ scale, x, y })
|
||||
}
|
||||
},
|
||||
created () {
|
||||
// Make lint happy
|
||||
(() => PinchZoom)()
|
||||
}
|
||||
}
|
||||
11
src/components/pinch_zoom/pinch_zoom.vue
Normal file
11
src/components/pinch_zoom/pinch_zoom.vue
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<template>
|
||||
<pinch-zoom
|
||||
class="pinch-zoom-parent"
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners"
|
||||
>
|
||||
<slot />
|
||||
</pinch-zoom>
|
||||
</template>
|
||||
|
||||
<script src="./pinch_zoom.js"></script>
|
||||
84
src/components/swipe_click/swipe_click.js
Normal file
84
src/components/swipe_click/swipe_click.js
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
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: Function,
|
||||
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) {
|
||||
this.$gesture.click(event)
|
||||
},
|
||||
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
|
||||
14
src/components/swipe_click/swipe_click.vue
Normal file
14
src/components/swipe_click/swipe_click.vue
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<template>
|
||||
<div
|
||||
v-bind="$attrs"
|
||||
@pointerdown="handlePointerDown"
|
||||
@pointermove="handlePointerMove"
|
||||
@pointerup="handlePointerUp"
|
||||
@pointercancel="handlePointerCancel"
|
||||
@click="handleNativeClick"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./swipe_click.js"></script>
|
||||
Loading…
Add table
Add a link
Reference in a new issue