pleroma-fe/src/components/media_modal/media_modal.js
2026-01-06 16:23:17 +02:00

162 lines
4.5 KiB
JavaScript

import { library } from '@fortawesome/fontawesome-svg-core'
import {
faChevronLeft,
faChevronRight,
faCircleNotch,
faTimes,
} from '@fortawesome/free-solid-svg-icons'
import Flash from 'src/components/flash/flash.vue'
import { useMediaViewerStore } from 'src/stores/media_viewer'
import fileTypeService from '../../services/file_type/file_type.service.js'
import GestureService from '../../services/gesture_service/gesture_service'
import Modal from '../modal/modal.vue'
import PinchZoom from '../pinch_zoom/pinch_zoom.vue'
import StillImage from '../still-image/still-image.vue'
import SwipeClick from '../swipe_click/swipe_click.vue'
import VideoAttachment from '../video_attachment/video_attachment.vue'
library.add(faChevronLeft, faChevronRight, faCircleNotch, faTimes)
const MediaModal = {
components: {
StillImage,
VideoAttachment,
PinchZoom,
SwipeClick,
Modal,
Flash,
},
data() {
return {
loading: false,
swipeDirection: GestureService.DIRECTION_LEFT,
swipeThreshold: () => {
const considerableMoveRatio = 1 / 4
return window.innerWidth * considerableMoveRatio
},
pinchZoomMinScale: 1,
pinchZoomScaleResetLimit: 1.2,
}
},
computed: {
showing() {
return useMediaViewerStore().activated
},
media() {
return useMediaViewerStore().media
},
description() {
return this.currentMedia.description
},
currentIndex() {
return useMediaViewerStore().currentIndex
},
currentMedia() {
return this.media[this.currentIndex]
},
canNavigate() {
return this.media.length > 1
},
type() {
return this.currentMedia ? this.getType(this.currentMedia) : null
},
swipeDisableClickThreshold() {
// If there is only one media, allow more mouse movements to close the modal
// because there is less chance that the user wants to switch to another image
return () => (this.canNavigate ? 1 : 30)
},
},
methods: {
getType(media) {
return fileTypeService.fileType(media.mimetype)
},
hide() {
// HACK: Closing immediately via a touch will cause the click
// to be processed on the content below the overlay
const transitionTime = 100 // ms
setTimeout(() => {
useMediaViewerStore().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
const newMedia = this.media[prevIndex]
if (this.getType(newMedia) === 'image') {
this.loading = true
}
useMediaViewerStore().setCurrentMedia(newMedia)
}
},
goNext() {
if (this.canNavigate) {
const nextIndex =
this.currentIndex === this.media.length - 1
? 0
: this.currentIndex + 1
const newMedia = this.media[nextIndex]
if (this.getType(newMedia) === 'image') {
this.loading = true
}
useMediaViewerStore().setCurrentMedia(newMedia)
}
},
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()
}
},
handleKeydownEvent(e) {
if (!this.showing) {
return
}
if (e.keyCode === 39) {
// arrow right
this.goNext()
} else if (e.keyCode === 37) {
// arrow left
this.goPrev()
}
},
},
mounted() {
window.addEventListener('popstate', this.hide)
document.addEventListener('keyup', this.handleKeyupEvent)
document.addEventListener('keydown', this.handleKeydownEvent)
},
unmounted() {
window.removeEventListener('popstate', this.hide)
document.removeEventListener('keyup', this.handleKeyupEvent)
document.removeEventListener('keydown', this.handleKeydownEvent)
},
}
export default MediaModal