reuse preview component, optimize logic

This commit is contained in:
Weidi Deng 2021-01-05 14:49:44 +08:00
parent 61a5d9a069
commit 04f847042a
5 changed files with 46 additions and 260 deletions

View File

@ -52,7 +52,7 @@ export default {
lastY: null, lastY: null,
inDrag: false, inDrag: false,
touches: 0, touches: 0,
navOffset: 50, navThreshold: 50,
lastTouchDistance: 0, lastTouchDistance: 0,
moveDisabled: false, moveDisabled: false,
disabledTimer: null, disabledTimer: null,
@ -92,7 +92,7 @@ export default {
const wScale = window.innerWidth / img.clientWidth const wScale = window.innerWidth / img.clientWidth
const hScale = window.innerHeight / img.clientHeight const hScale = window.innerHeight / img.clientHeight
this.scale = wScale < hScale? wScale: hScale this.scale = Math.min(wScale, hScale)
this.minScale = this.scale this.minScale = this.scale
this.setZoom() this.setZoom()
}, },
@ -117,32 +117,31 @@ export default {
let x = 0,y = 0 let x = 0,y = 0
// left out of viewport // left out of viewport
if (rect.left < 0 && rect.right < width) x = width - rect.right if (rect.left < 0 && rect.right < width) x = Math.min(-rect.left, width - rect.right)
// right out of viewport // right out of viewport
else if (rect.left > 0 && rect.right > width) x = -rect.left else if (rect.left > 0 && rect.right > width) x = Math.min(-rect.left, width - rect.right)
// top out of viewport // top out of viewport
if (rect.top < 0 && rect.bottom < height) y = height - rect.bottom if (rect.top < 0 && rect.bottom < height) y = Math.min(-rect.top, height - rect.bottom)
// bottom out of viewport // bottom out of viewport
else if (rect.top > 0 && rect.bottom > height) y = -rect.top else if (rect.top > 0 && rect.bottom > height) y = Math.min(-rect.top, height - rect.bottom)
return [x,y] return [x,y]
} }
}, },
checkNav(x) { checkNav(x) {
if (this.scale <= this.minScale) { if (this.scale <= this.minScale) {
if (x > this.navOffset) this.$root.$emit('gallery-nav', 0) if (x > this.navThreshold) this.$root.$emit('gallery-nav', 0)
else if (x < -this.navOffset) this.$root.$emit('gallery-nav', 1) else if (x < -this.navThreshold) this.$root.$emit('gallery-nav', 1)
} else { } else {
let img = this.$refs.imgex let img = this.$refs.imgex
const rect = img.getBoundingClientRect() const rect = img.getBoundingClientRect()
const width = window.innerWidth const width = window.innerWidth
if (rect.left > this.navOffset && rect.right > width + this.navOffset) this.$root.$emit('gallery-nav', 0) if (x > this.navThreshold && rect.left > this.navThreshold && rect.right > width + this.navThreshold) this.$root.$emit('gallery-nav', 0)
else if (rect.left < - this.navOffset && rect.right < width - this.navOffset) this.$root.$emit('gallery-nav', 1) else if (x < -this.navThreshold && rect.left < - this.navThreshold && rect.right < width - this.navThreshold) this.$root.$emit('gallery-nav', 1)
} }
}, },
onLoad() { onLoad() {
@ -214,18 +213,8 @@ export default {
event.preventDefault() event.preventDefault()
}, },
zoomAuto(event) { zoomAuto(event) {
switch (this.scale) { if (this.minScale <= this.scale && this.scale < 2 * this.minScale) this.scale *= 2
case 1: else this.scale /= 2
this.scale = 2
break
case 2:
this.scale = 4
break
default:
case 4:
this.scale = 1
break
}
this.setZoom() this.setZoom()
event.preventDefault() event.preventDefault()
}, },
@ -300,6 +289,7 @@ export default {
this.scale = this.scale < this.minScale ? this.minScale : this.scale this.scale = this.scale < this.minScale ? this.minScale : this.scale
this.scale = this.scale > this.maxScale ? this.maxScale : this.scale this.scale = this.scale > this.maxScale ? this.maxScale : this.scale
this.$refs.imgex.style.transform = `scale(${this.scale})` this.$refs.imgex.style.transform = `scale(${this.scale})`
this.refit()
}, },
pxStringToNumber(style) { pxStringToNumber(style) {
return +style.replace("px", "") return +style.replace("px", "")

View File

@ -1,218 +0,0 @@
<template>
<div id="previewer">
<div class="image-bar">
<button @click="back" class="action" :title="$t('files.closePreview')" :aria-label="$t('files.closePreview')" id="close">
<i class="material-icons">close</i>
</button>
<div class="title">{{ this.name }}</div>
<preview-size-button v-if="isResizeEnabled" @change-size="toggleSize" v-bind:size="fullSize" :disabled="loading"></preview-size-button>
<button @click="openMore" id="more" :aria-label="$t('buttons.more')" :title="$t('buttons.more')" class="action">
<i class="material-icons">more_vert</i>
</button>
<div id="dropdown" :class="{ active : showMore }">
<rename-button :disabled="loading" v-if="user.perm.rename"></rename-button>
<delete-button :disabled="loading" v-if="user.perm.delete"></delete-button>
<download-button :disabled="loading" v-if="user.perm.download"></download-button>
<info-button :disabled="loading"></info-button>
</div>
</div>
<div class="loading" v-if="loading">
<div class="spinner">
<div class="bounce1"></div>
<div class="bounce2"></div>
<div class="bounce3"></div>
</div>
</div>
<button class="action" @click="prev" v-show="hasPrevious" :aria-label="$t('buttons.previous')" :title="$t('buttons.previous')">
<i class="material-icons">chevron_left</i>
</button>
<button class="action" @click="next" v-show="hasNext" :aria-label="$t('buttons.next')" :title="$t('buttons.next')">
<i class="material-icons">chevron_right</i>
</button>
<template v-if="!loading">
<div class="preview">
<ExtendedImage :src="raw"></ExtendedImage>
</div>
</template>
<div v-show="showMore" @click="resetPrompts" class="overlay"></div>
</div>
</template>
<script>
import { mapState } from 'vuex'
import url from '@/utils/url'
import { baseURL, resizePreview } from '@/utils/constants'
import { files as api } from '@/api'
import PreviewSizeButton from '@/components/buttons/PreviewSize'
import InfoButton from '@/components/buttons/Info'
import DeleteButton from '@/components/buttons/Delete'
import RenameButton from '@/components/buttons/Rename'
import DownloadButton from '@/components/buttons/Download'
import ExtendedImage from './ExtendedImage'
export default {
name: 'image-preview',
components: {
PreviewSizeButton,
InfoButton,
DeleteButton,
RenameButton,
DownloadButton,
ExtendedImage
},
data: function () {
return {
previousLink: '',
nextLink: '',
listing: null,
name: '',
fullSize: false
}
},
computed: {
...mapState(['req', 'user', 'oldReq', 'jwt', 'loading', 'show']),
hasPrevious () {
return (this.previousLink !== '')
},
hasNext () {
return (this.nextLink !== '')
},
download () {
return `${baseURL}/api/raw${url.encodePath(this.req.path)}?auth=${this.jwt}`
},
previewUrl () {
if (!this.fullSize) {
return `${baseURL}/api/preview/big${url.encodePath(this.req.path)}?auth=${this.jwt}`
}
return `${baseURL}/api/raw${url.encodePath(this.req.path)}?auth=${this.jwt}`
},
raw () {
return `${this.previewUrl}&inline=true`
},
showMore () {
return this.$store.state.show === 'more'
},
isResizeEnabled () {
return resizePreview
}
},
watch: {
$route: function () {
this.updatePreview()
}
},
async mounted () {
window.addEventListener('keydown', this.key)
this.$store.commit('setPreviewMode', true)
this.listing = this.oldReq.items
this.$root.$on('preview-deleted', this.deleted)
this.$root.$on('gallery-nav', this.nav)
this.updatePreview()
},
beforeDestroy () {
window.removeEventListener('keydown', this.key)
this.$store.commit('setPreviewMode', false)
this.$root.$off('preview-deleted', this.deleted)
this.$root.$off('gallery-nav', this.nav)
},
methods: {
nav(e) {
if (e===0 && this.hasPrevious) this.prev()
else if (e===1 && this.hasNext) this.next()
},
deleted () {
this.listing = this.listing.filter(item => item.name !== this.name)
if (this.hasNext) {
this.next()
} else if (!this.hasPrevious && !this.hasNext) {
this.back()
} else {
this.prev()
}
},
back () {
this.$store.commit('setPreviewMode', false)
let uri = url.removeLastDir(this.$route.path) + '/'
this.$router.push({ path: uri })
},
prev () {
this.$router.push({ path: this.previousLink })
},
next () {
this.$router.push({ path: this.nextLink })
},
key (event) {
if (this.show !== null) {
return
}
if (event.which === 13 || event.which === 39) { // right arrow
if (this.hasNext) this.next()
} else if (event.which === 37) { // left arrow
if (this.hasPrevious) this.prev()
}
},
async updatePreview () {
if (this.req.subtitles) {
this.subtitles = this.req.subtitles.map(sub => `${baseURL}/api/raw${sub}?auth=${this.jwt}&inline=true`)
}
let dirs = this.$route.fullPath.split("/")
this.name = decodeURIComponent(dirs[dirs.length - 1])
if (!this.listing) {
try {
const path = url.removeLastDir(this.$route.path)
const res = await api.fetch(path)
this.listing = res.items
} catch (e) {
this.$showError(e)
}
}
this.previousLink = ''
this.nextLink = ''
for (let i = 0; i < this.listing.length; i++) {
if (this.listing[i].name !== this.name) {
continue
}
for (let j = i - 1; j >= 0; j--) {
if (this.listing[j].type === 'image') {
this.previousLink = this.listing[j].url
break
}
}
for (let j = i + 1; j < this.listing.length; j++) {
if (this.listing[j].type === 'image') {
this.nextLink = this.listing[j].url
break
}
}
return
}
},
openMore () {
this.$store.commit('showHover', 'more')
},
resetPrompts () {
this.$store.commit('closeHovers')
},
toggleSize () {
this.fullSize = !this.fullSize
}
}
}
</script>

View File

@ -1,13 +1,13 @@
<template> <template>
<div id="previewer"> <div id="previewer">
<div class="bar"> <div :class="isGallery ? 'gallery-bar' : 'bar'">
<button @click="back" class="action" :title="$t('files.closePreview')" :aria-label="$t('files.closePreview')" id="close"> <button @click="back" class="action" :title="$t('files.closePreview')" :aria-label="$t('files.closePreview')" id="close">
<i class="material-icons">close</i> <i class="material-icons">close</i>
</button> </button>
<div class="title">{{ this.name }}</div> <div class="title">{{ this.name }}</div>
<preview-size-button v-if="isResizeEnabled && this.req.type === 'image'" @change-size="toggleSize" v-bind:size="fullSize" :disabled="loading"></preview-size-button> <preview-size-button v-if="isResizeEnabled && req.type === 'image'" @change-size="toggleSize" v-bind:size="fullSize" :disabled="loading"></preview-size-button>
<button @click="openMore" id="more" :aria-label="$t('buttons.more')" :title="$t('buttons.more')" class="action"> <button @click="openMore" id="more" :aria-label="$t('buttons.more')" :title="$t('buttons.more')" class="action">
<i class="material-icons">more_vert</i> <i class="material-icons">more_vert</i>
</button> </button>
@ -37,7 +37,8 @@
<template v-if="!loading"> <template v-if="!loading">
<div class="preview"> <div class="preview">
<audio v-if="req.type == 'audio'" :src="raw" autoplay controls></audio> <ExtendedImage v-if="isGallery" :src="raw"></ExtendedImage>
<audio v-else-if="req.type == 'audio'" :src="raw" autoplay controls></audio>
<video v-else-if="req.type == 'video'" :src="raw" autoplay controls> <video v-else-if="req.type == 'video'" :src="raw" autoplay controls>
<track <track
kind="captions" kind="captions"
@ -70,6 +71,7 @@ import InfoButton from '@/components/buttons/Info'
import DeleteButton from '@/components/buttons/Delete' import DeleteButton from '@/components/buttons/Delete'
import RenameButton from '@/components/buttons/Rename' import RenameButton from '@/components/buttons/Rename'
import DownloadButton from '@/components/buttons/Download' import DownloadButton from '@/components/buttons/Download'
import ExtendedImage from './ExtendedImage'
const mediaTypes = [ const mediaTypes = [
"video", "video",
@ -84,7 +86,8 @@ export default {
InfoButton, InfoButton,
DeleteButton, DeleteButton,
RenameButton, RenameButton,
DownloadButton DownloadButton,
ExtendedImage
}, },
data: function () { data: function () {
return { return {
@ -93,7 +96,8 @@ export default {
listing: null, listing: null,
name: '', name: '',
subtitles: [], subtitles: [],
fullSize: false fullSize: false,
isGallery: false
} }
}, },
computed: { computed: {
@ -128,19 +132,28 @@ export default {
this.updatePreview() this.updatePreview()
} }
}, },
created() {
if (this.req.type === 'image') this.isGallery = true
},
async mounted () { async mounted () {
window.addEventListener('keydown', this.key) window.addEventListener('keydown', this.key)
this.$store.commit('setPreviewMode', true) this.$store.commit('setPreviewMode', true)
this.listing = this.oldReq.items this.listing = this.oldReq.items
this.$root.$on('preview-deleted', this.deleted) this.$root.$on('preview-deleted', this.deleted)
if (this.isGallery) this.$root.$on('gallery-nav', this.nav)
this.updatePreview() this.updatePreview()
}, },
beforeDestroy () { beforeDestroy () {
window.removeEventListener('keydown', this.key) window.removeEventListener('keydown', this.key)
this.$store.commit('setPreviewMode', false) this.$store.commit('setPreviewMode', false)
this.$root.$off('preview-deleted', this.deleted) this.$root.$off('preview-deleted', this.deleted)
if (this.isGallery) this.$root.$off('gallery-nav', this.nav)
}, },
methods: { methods: {
nav(e) {
if (e===0 && this.hasPrevious) this.prev()
else if (e===1 && this.hasNext) this.next()
},
deleted () { deleted () {
this.listing = this.listing.filter(item => item.name !== this.name) this.listing = this.listing.filter(item => item.name !== this.name)
@ -202,14 +215,20 @@ export default {
} }
for (let j = i - 1; j >= 0; j--) { for (let j = i - 1; j >= 0; j--) {
if (mediaTypes.includes(this.listing[j].type)) { if (this.isGallery && this.listing[j].type === 'image') {
this.previousLink = this.listing[j].url
break
} else if (mediaTypes.includes(this.listing[j].type)) {
this.previousLink = this.listing[j].url this.previousLink = this.listing[j].url
break break
} }
} }
for (let j = i + 1; j < this.listing.length; j++) { for (let j = i + 1; j < this.listing.length; j++) {
if (mediaTypes.includes(this.listing[j].type)) { if (this.isGallery && this.listing[j].type === 'image') {
this.nextLink = this.listing[j].url
break
} else if (mediaTypes.includes(this.listing[j].type)) {
this.nextLink = this.listing[j].url this.nextLink = this.listing[j].url
break break
} }

View File

@ -139,7 +139,7 @@
color: #fff; color: #fff;
} }
#previewer .image-bar { #previewer .gallery-bar {
width: 100%; width: 100%;
position: absolute; position: absolute;
z-index: 2; z-index: 2;
@ -151,15 +151,15 @@
transition: opacity 0.1s ease; transition: opacity 0.1s ease;
} }
#previewer .image-bar:hover { #previewer .gallery-bar:hover {
opacity: 1; opacity: 1;
} }
#previewer .image-bar > * { #previewer .gallery-bar > * {
flex: 0 0 auto; flex: 0 0 auto;
} }
#previewer .image-bar .title { #previewer .gallery-bar .title {
display: block; display: block;
flex: 1 1 auto; flex: 1 1 auto;
padding: 0 1em; padding: 0 1em;

View File

@ -16,7 +16,6 @@
<forbidden v-else-if="error.message === '403'"></forbidden> <forbidden v-else-if="error.message === '403'"></forbidden>
<internal-error v-else></internal-error> <internal-error v-else></internal-error>
</div> </div>
<image-preview v-else-if="isImagePreview"></image-preview>
<preview v-else-if="isPreview"></preview> <preview v-else-if="isPreview"></preview>
<editor v-else-if="isEditor"></editor> <editor v-else-if="isEditor"></editor>
<listing :class="{ multiple }" v-else-if="isListing"></listing> <listing :class="{ multiple }" v-else-if="isListing"></listing>
@ -33,8 +32,8 @@ import Forbidden from './errors/403'
import NotFound from './errors/404' import NotFound from './errors/404'
import InternalError from './errors/500' import InternalError from './errors/500'
import Preview from '@/components/files/Preview' import Preview from '@/components/files/Preview'
import ImagePreview from "@/components/files/ImagePreview"
import Listing from '@/components/files/Listing' import Listing from '@/components/files/Listing'
import Editor from '@/components/files/Editor'
import { files as api } from '@/api' import { files as api } from '@/api'
import { mapGetters, mapState, mapMutations } from 'vuex' import { mapGetters, mapState, mapMutations } from 'vuex'
@ -45,13 +44,12 @@ function clean (path) {
export default { export default {
name: 'files', name: 'files',
components: { components: {
ImagePreview,
Forbidden, Forbidden,
NotFound, NotFound,
InternalError, InternalError,
Preview, Preview,
Listing, Listing,
Editor: () => import('@/components/files/Editor') Editor
}, },
computed: { computed: {
...mapGetters([ ...mapGetters([
@ -68,11 +66,8 @@ export default {
'loading', 'loading',
'show' 'show'
]), ]),
isImagePreview () {
return (!this.loading && !this.isListing && !this.isEditor || this.loading && this.$store.state.previewMode) && this.req.type === 'image'
},
isPreview () { isPreview () {
return (!this.loading && !this.isListing && !this.isEditor || this.loading && this.$store.state.previewMode) && this.req.type !== 'image' return !this.loading && !this.isListing && !this.isEditor || this.loading && this.$store.state.previewMode
}, },
breadcrumbs () { breadcrumbs () {
let parts = this.$route.path.split('/') let parts = this.$route.path.split('/')