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,
inDrag: false,
touches: 0,
navOffset: 50,
navThreshold: 50,
lastTouchDistance: 0,
moveDisabled: false,
disabledTimer: null,
@ -92,7 +92,7 @@ export default {
const wScale = window.innerWidth / img.clientWidth
const hScale = window.innerHeight / img.clientHeight
this.scale = wScale < hScale? wScale: hScale
this.scale = Math.min(wScale, hScale)
this.minScale = this.scale
this.setZoom()
},
@ -117,32 +117,31 @@ export default {
let x = 0,y = 0
// 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
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
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
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]
}
},
checkNav(x) {
if (this.scale <= this.minScale) {
if (x > this.navOffset) this.$root.$emit('gallery-nav', 0)
else if (x < -this.navOffset) this.$root.$emit('gallery-nav', 1)
if (x > this.navThreshold) this.$root.$emit('gallery-nav', 0)
else if (x < -this.navThreshold) this.$root.$emit('gallery-nav', 1)
} else {
let img = this.$refs.imgex
const rect = img.getBoundingClientRect()
const width = window.innerWidth
if (rect.left > this.navOffset && rect.right > width + this.navOffset) this.$root.$emit('gallery-nav', 0)
else if (rect.left < - this.navOffset && rect.right < width - this.navOffset) this.$root.$emit('gallery-nav', 1)
if (x > this.navThreshold && rect.left > this.navThreshold && rect.right > width + this.navThreshold) this.$root.$emit('gallery-nav', 0)
else if (x < -this.navThreshold && rect.left < - this.navThreshold && rect.right < width - this.navThreshold) this.$root.$emit('gallery-nav', 1)
}
},
onLoad() {
@ -214,18 +213,8 @@ export default {
event.preventDefault()
},
zoomAuto(event) {
switch (this.scale) {
case 1:
this.scale = 2
break
case 2:
this.scale = 4
break
default:
case 4:
this.scale = 1
break
}
if (this.minScale <= this.scale && this.scale < 2 * this.minScale) this.scale *= 2
else this.scale /= 2
this.setZoom()
event.preventDefault()
},
@ -300,6 +289,7 @@ export default {
this.scale = this.scale < this.minScale ? this.minScale : this.scale
this.scale = this.scale > this.maxScale ? this.maxScale : this.scale
this.$refs.imgex.style.transform = `scale(${this.scale})`
this.refit()
},
pxStringToNumber(style) {
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>
<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">
<i class="material-icons">close</i>
</button>
<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">
<i class="material-icons">more_vert</i>
</button>
@ -37,7 +37,8 @@
<template v-if="!loading">
<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>
<track
kind="captions"
@ -70,6 +71,7 @@ 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'
const mediaTypes = [
"video",
@ -84,7 +86,8 @@ export default {
InfoButton,
DeleteButton,
RenameButton,
DownloadButton
DownloadButton,
ExtendedImage
},
data: function () {
return {
@ -93,7 +96,8 @@ export default {
listing: null,
name: '',
subtitles: [],
fullSize: false
fullSize: false,
isGallery: false
}
},
computed: {
@ -128,19 +132,28 @@ export default {
this.updatePreview()
}
},
created() {
if (this.req.type === 'image') this.isGallery = true
},
async mounted () {
window.addEventListener('keydown', this.key)
this.$store.commit('setPreviewMode', true)
this.listing = this.oldReq.items
this.$root.$on('preview-deleted', this.deleted)
if (this.isGallery) 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)
if (this.isGallery) 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)
@ -202,14 +215,20 @@ export default {
}
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
break
}
}
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
break
}

View File

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

View File

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