file preview refactoring

This commit is contained in:
Peter Papp
2021-08-10 11:43:05 +02:00
parent 843a1bc690
commit 98688b81c0
9 changed files with 228 additions and 163 deletions

View File

@@ -0,0 +1,72 @@
<template>
<div
v-if="isFullPreview"
class="file-preview"
ref="filePreview"
tabindex="-1"
@keydown.esc="closeFilePreview"
@keydown.right="next"
@keydown.left="prev"
>
<FilePreviewToolbar />
<FilePreviewMedia />
</div>
</template>
<script>
import FilePreviewToolbar from '@/components/FilePreview/FilePreviewToolbar'
import FilePreviewMedia from '@/components/FilePreview/FilePreviewMedia'
import {events} from '@/bus'
export default {
name: 'FilePreview',
components: {
FilePreviewToolbar,
FilePreviewMedia,
},
data() {
return {
isFullPreview: false
}
},
methods: {
closeFilePreview() {
this.isFullPreview = false
this.$store.commit('FAST_PREVIEW_CLEAR')
},
next() {
events.$emit('file-preview:next')
},
prev() {
events.$emit('file-preview:prev')
}
},
updated() {
if (this.isFullPreview) {
this.$refs.filePreview.focus()
}
},
mounted() {
events.$on('file-preview:show', () => this.isFullPreview = true)
events.$on('file-preview:hide', () => this.closeFilePreview())
}
}
</script>
<style lang="scss" scoped>
@import '@assets/vuefilemanager/_variables';
.file-preview {
width: 100%;
height: 100%;
position: absolute;
z-index: 7;
background-color: white;
}
.dark-mode {
.file-preview {
background-color: $dark_mode_background;
}
}
</style>

View File

@@ -0,0 +1,263 @@
<template>
<div v-if="currentFile" class="file-preview-wrapper">
<!--Arrow navigation-->
<div v-if="files.length > 1" class="navigation-arrows">
<div @click.prevent="prev" class="prev">
<chevron-left-icon size="17" />
</div>
<div @click.prevent="next" class="next">
<chevron-right-icon size="17" />
</div>
</div>
<!--File preview-->
<div class="file-wrapper-preview">
<!--Show PDF-->
<PdfFile v-if="isPDF" :file="currentFile"/>
<!--Show Audio, Video and Image-->
<div v-if="isAudio || isImage || isVideo" class="file-wrapper">
<Audio v-if="isAudio" :file="currentFile"/>
<Video v-if="isVideo" :file="currentFile"/>
<ImageFile v-if="isImage" :file="currentFile"/>
</div>
</div>
</div>
</template>
<script>
import {ChevronLeftIcon, ChevronRightIcon} from 'vue-feather-icons'
import ToolbarButton from '@/components/FilesView/ToolbarButton'
import ImageFile from '@/components/FilePreview/Media/ImageFile'
import PdfFile from '@/components/FilePreview/Media/PdfFile'
import Audio from '@/components/FilePreview/Media/Audio'
import Video from '@/components/FilePreview/Media/Video'
import Spinner from '@/components/FilesView/Spinner'
import {mapGetters} from 'vuex'
import {events} from '@/bus'
export default {
name: 'FilePreviewMedia',
components: {
ChevronRightIcon,
ChevronLeftIcon,
ToolbarButton,
ImageFile,
PdfFile,
Spinner,
Audio,
Video,
},
computed: {
...mapGetters([
'fastPreview',
'clipboard',
'entries',
]),
currentFile() {
return this.fastPreview
? this.fastPreview
: this.files[Math.abs(this.currentIndex) % this.files.length]
},
isPDF() {
return this.currentFile.mimetype === 'pdf'
},
isVideo() {
return this.currentFile.type === 'video'
},
isAudio() {
return this.currentFile.type === 'audio'
},
isImage() {
return this.currentFile.type === 'image'
}
},
data() {
return {
currentIndex: 0,
files: [],
}
},
watch: {
files() {
if (this.files.length === 0)
events.$emit('file-preview-wrapper:hide')
},
currentFile() {
if (this.clipboard[0]) {
this.$store.commit('CLIPBOARD_CLEAR')
this.$store.commit('ADD_ITEM_TO_CLIPBOARD', this.currentFile)
}
},
clipboard() {
if (!this.clipboard[0]) {
this.currentIndex -= 1
this.$store.commit('ADD_ITEM_TO_CLIPBOARD', this.currentFile)
this.files = []
}
},
data(newValue, oldValue) {
if (newValue !== oldValue) {
this.files = []
}
},
},
methods: {
getFilesForView() {
let requestedFile = this.clipboard[0]
this.entries.map(element => {
if (requestedFile.mimetype === 'pdf') {
if (element.mimetype === 'pdf')
this.files.push(element)
} else {
if (element.type === requestedFile.type)
this.files.push(element)
}
})
this.files.forEach((element, index) => {
if (element.id === this.clipboard[0].id) {
this.currentIndex = index
}
})
},
next() {
if (!this.files.length > 1) return
this.currentIndex += 1
if (this.currentIndex > this.files.length - 1) {
this.currentIndex = 0
}
},
prev() {
if (!this.files.length > 1) return
this.currentIndex -= 1
if (this.currentIndex < 0) {
this.currentIndex = this.files.length - 1
}
}
},
created() {
events.$on('file-preview:next', () => this.next())
events.$on('file-preview:prev', () => this.prev())
this.getFilesForView()
}
}
</script>
<style lang="scss">
@import '@assets/vuefilemanager/_variables';
@import '@assets/vuefilemanager/_mixins';
.navigation-arrows {
.prev, .next {
cursor: pointer;
position: absolute;
top: 45%;
display: flex;
justify-content: center;
color: $text;
border-radius: 50%;
text-decoration: none;
user-select: none;
filter: drop-shadow(0px 1px 0 rgba(255, 255, 255, 1));
padding: 10px;
z-index: 2;
}
.next {
right: 0;
}
.prev {
left: 0;
}
}
.file-preview-wrapper {
height: calc(100% - 72px);
top: 72px;
position: relative;
background-color: white;
}
.file-wrapper-preview {
width: 100%;
height: 100%;
padding: 30px 0px;
display: flex;
overflow: hidden;
justify-content: center;
align-items: center;
background-color: white;
.file-wrapper {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
.file-shadow {
box-shadow: 0 8px 40px rgba(17, 26, 52, 0.05);
}
.file {
max-width: 100%;
max-height: 100%;
align-self: center;
}
.audio {
border-radius: 28px;
}
img {
border-radius: 4px;
}
}
}
@media only screen and (max-width: 960px) {
.file-preview-wrapper {
top: 53px;
}
}
.dark-mode {
.navigation-arrows {
.prev, .next {
color: $light-text;
filter: drop-shadow(0px 1px 0 rgba(17, 19, 20, 1));
}
}
.file-wrapper-preview {
background-color: $dark_mode_background;
.file-wrapper {
.file-shadow {
box-shadow: 0 8px 40px rgba(0, 0, 0, 0.1);
}
}
}
}
</style>

View File

@@ -0,0 +1,377 @@
<template>
<div class="navigation-panel" v-if="currentFile">
<div class="name-wrapper">
<x-icon @click="closeFullPreview" size="22" class="icon-close hover-text-theme" />
<div class="name-count-wrapper">
<p class="title">{{ currentFile.name }}</p>
<span v-if="! fastPreview" class="file-count"> ({{ showingImageIndex + ' ' + $t('pronouns.of') + ' ' + files.length }}) </span>
</div>
<PopoverWrapper>
<span @click.stop="showItemContextMenu" id="fast-preview-menu" class="fast-menu-icon group">
<more-horizontal-icon class="more-icon group-hover-text-theme" size="14" />
</span>
<PopoverItem name="file-preview-contextmenu" side="right">
<OptionGroup class="menu-option-group">
<Option @click.native="$renameFileOrFolder(currentFile)" :title="$t('context_menu.rename')" icon="rename" />
<Option @click.native="$moveFileOrFolder(currentFile)" :title="$t('context_menu.move')" icon="move-item" />
<Option @click.native="$shareFileOrFolder(currentFile)" :title="sharingTitle" icon="share" v-if="$checkPermission('master')" />
<Option @click.native="$deleteFileOrFolder(currentFile)" :title="$t('context_menu.delete')" icon="trash" class="menu-option" />
</OptionGroup>
<OptionGroup>
<Option @click.native="downloadItem" :title="$t('context_menu.download')" icon="download" />
</OptionGroup>
</PopoverItem>
</PopoverWrapper>
</div>
<div class="created-at-wrapper">
<p>{{ currentFile.filesize }}, {{ currentFile.created_at }}</p>
</div>
<div class="navigation-icons">
<div v-if="isPdf" class="navigation-tool-wrapper">
<ToolbarButton @click.native="decreaseSizeOfPDF" source="zoom-out" :action="$t('pdf_zoom_out')" />
<ToolbarButton @click.native="increaseSizeOfPDF" source="zoom-in" :action="$t('pdf_zoom_in')" />
</div>
<div class="navigation-tool-wrapper">
<ToolbarButton @click.native="downloadItem" class="mobile-hide" source="download" :action="$t('actions.download')" />
<ToolbarButton v-if="canShareItem" @click.native="$shareFileOrFolder(currentFile)" class="mobile-hide" :class="{ 'is-inactive': !canShareItem }" source="share" :action="$t('actions.share')" />
<ToolbarButton v-if="isImage" @click.native="printMethod()" source="print" :action="$t('actions.print')" />
</div>
</div>
</div>
</template>
<script>
import PopoverWrapper from '@/components/Desktop/PopoverWrapper'
import PopoverItem from '@/components/Desktop/PopoverItem'
import OptionGroup from '@/components/FilesView/OptionGroup'
import Option from '@/components/FilesView/Option'
import ToolbarButton from '@/components/FilesView/ToolbarButton'
import {XIcon, MoreHorizontalIcon} from 'vue-feather-icons'
import {mapGetters} from 'vuex'
import {events} from '@/bus'
export default {
name: 'FilePreviewToolbar',
components: {
MoreHorizontalIcon,
PopoverWrapper,
ToolbarButton,
PopoverItem,
OptionGroup,
Option,
XIcon,
},
computed: {
...mapGetters([
'fastPreview',
'clipboard',
'entries'
]),
currentFile() {
return this.fastPreview ? this.fastPreview : this.clipboard[0]
},
sharingTitle() {
return this.currentFile.shared
? this.$t('context_menu.share_edit')
: this.$t('context_menu.share')
},
isImage() {
return this.currentFile.type === 'image'
},
isPdf() {
return this.currentFile.mimetype === 'pdf'
},
files() {
let files = []
this.entries.map(element => {
if (this.currentFile.mimetype === 'pdf') {
if (element.mimetype === 'pdf')
files.push(element)
} else {
if (element.type === this.currentFile.type)
files.push(element)
}
})
return files
},
showingImageIndex() {
let activeIndex = undefined
this.files.forEach((element, index) => {
if (element.id === this.currentFile.id) {
activeIndex = index + 1
}
})
return activeIndex
},
canShareItem() {
return this.$isThisLocation([
'base', 'latest', 'shared'
])
},
},
methods: {
showItemContextMenu() {
if (this.$isMobile()) {
events.$emit('mobile-menu:show', 'file-menu')
} else {
events.$emit('popover:open', 'file-preview-contextmenu')
}
},
increaseSizeOfPDF() {
events.$emit('document-zoom:in')
},
decreaseSizeOfPDF() {
events.$emit('document-zoom:out')
},
printMethod() {
let tab = document.getElementById('printable-file')
let win = window.open('', '', 'height=700,width=700')
win.document.write(tab.outerHTML)
win.document.close()
win.print()
},
downloadItem() {
this.$downloadFile(
this.currentFile.file_url,
this.currentFile.name + '.' + this.currentFile.mimetype
)
},
closeFullPreview() {
events.$emit('file-preview:hide')
}
}
}
</script>
<style lang="scss" scoped>
@import '@assets/vuefilemanager/_variables';
@import '@assets/vuefilemanager/_mixins';
.name-wrapper {
width: 33%;
height: 22px;
display: flex;
position: relative;
align-items: center;
flex-grow: 1;
align-self: center;
white-space: nowrap;
.name-count-wrapper {
margin-left: 6px;
margin-right: 6px;
.file-count {
@include font-size(15);
line-height: 1;
font-weight: 700;
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
vertical-align: middle;
align-self: center;
color: $text;
}
.title {
@include font-size(15);
max-width: 250px;
line-height: 1;
font-weight: 700;
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
vertical-align: middle;
color: $text;
}
@media (max-width: 570px) {
.title {
max-width: 180px;
@include font-size(17);
}
.file-count {
@include font-size(17);
}
}
}
.icon-close {
min-width: 22px;
padding: 1px 4px;
border-radius: 6px;
vertical-align: middle;
cursor: pointer;
@include transition(150ms);
&:hover {
background: $light_background;
line {
color: inherit;
}
}
}
.fast-menu-icon {
height: 24px;
display: flex;
align-items: center;
vertical-align: middle;
padding: 1px 4px;
line-height: 0;
border-radius: 3px;
cursor: pointer;
@include transition(150ms);
svg circle {
@include transition(150ms);
}
&:hover {
background: $light_background;
svg circle {
color: inherit;
}
}
.more-icon {
vertical-align: middle;
cursor: pointer;
}
}
}
.context-menu {
min-width: 250px;
position: absolute;
z-index: 99;
box-shadow: $shadow;
background: white;
border-radius: 8px;
overflow: hidden;
top: 29px;
&.showed {
display: block;
}
}
.created-at-wrapper {
width: 33%;
display: flex;
text-align: center;
justify-content: center;
p {
display: flex;
align-items: center;
@include font-size(11);
}
}
.navigation-icons {
width: 33%;
text-align: right;
.navigation-tool-wrapper {
margin-left: 28px;
display: inline-block;
vertical-align: middle;
}
.button {
margin-left: 5px;
&:hover {
background: $light_background;
}
}
}
.navigation-panel {
height: 63px;
width: 100%;
padding: 10px 15px;
display: flex;
position: absolute;
z-index: 8;
align-items: center;
background-color: white;
color: $text;
}
@media (max-width: 960px) {
.context-menu {
.name-wrapper {
width: 67%;
}
}
.navigation-icons {
display: none;
}
.navigation-panel {
height: 53px;
padding: 15px;
}
.created-at-wrapper {
display: none;
}
.name-wrapper {
justify-content: space-between;
flex-direction: row-reverse;
width: 100%;
}
}
.dark-mode {
.navigation-panel {
background-color: $dark_mode_background;
color: $dark_mode_text_primary;
.icon-close {
color: $dark_mode_text_primary;
&:hover {
background-color: $dark_mode_background;
}
}
.fast-menu-icon:hover {
background: $dark_mode_background;
}
}
.name-wrapper {
.title,
.file-count {
color: $dark_mode_text_primary !important;
}
}
.navigation-icons {
.button:hover {
background: $dark_mode_background;
}
}
}
</style>

View File

@@ -0,0 +1,17 @@
<template>
<audio
:class="{'file-shadow': ! this.$isMobile() }"
class="file audio"
:src="file.file_url"
controls>
</audio>
</template>
<script>
export default {
name: 'Audio',
props: [
'file'
],
}
</script>

View File

@@ -0,0 +1,17 @@
<template>
<img
id="printable-file"
class="file"
:class="{'file-shadow': ! this.$isMobile() }"
:src="file.file_url"
/>
</template>
<script>
export default {
name: 'ImageFile',
props: [
'file'
],
}
</script>

View File

@@ -0,0 +1,114 @@
<template>
<div id="pdf-wrapper" :style="{width: documentSize + '%'}">
<pdf :src="pdfData" v-for="i in numPages" :key="i" :resize="true" :page="i" scale="page-width" style="width:100%; margin:0 auto 35px;" id="printable-file" class="pdf-file">
<template slot="loading">
<h1>loading content...</h1>
</template>
</pdf>
</div>
</template>
<script>
import {events} from '@/bus'
import pdf from 'pdfvuer'
export default {
name: 'PdfFile',
components: {
pdf,
},
props: [
'file'
],
watch: {
file() {
this.getPdf()
}
},
data() {
return {
pdfData: undefined,
numPages: 0,
documentSize: 50,
}
},
methods: {
getPdf() {
this.pdfData = undefined
this.numPages = 0
let self = this;
self.pdfData = pdf.createLoadingTask(this.file.file_url);
self.pdfData.then(pdf => self.numPages = pdf.numPages);
},
getDocumentSize() {
if (window.innerWidth < 960) {
this.documentSize = 100
}
if (window.innerWidth > 960){
this.documentSize = localStorage.getItem('documentSize')
? parseInt(localStorage.getItem('documentSize'))
: 50;
}
},
zoomIn() {
if (this.documentSize < 100) {
this.documentSize += 10
localStorage.setItem('documentSize', this.documentSize)
}
},
zoomOut() {
if (this.documentSize > 40) {
this.documentSize -= 10
localStorage.setItem('documentSize', this.documentSize)
}
}
},
created() {
this.getDocumentSize()
this.getPdf()
events.$on('document-zoom:in', () => this.zoomIn())
events.$on('document-zoom:out', () => this.zoomOut())
}
}
</script>
<style src="pdfvuer/dist/pdfvuer.css" lang="css"></style>
<style lang="scss" scoped>
@import '@assets/vuefilemanager/_variables';
#pdf-wrapper {
border-radius: 8px;
overflow-y: scroll;
margin: 0 auto;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1;
padding: 40px;
.pdf-file {
box-shadow: $light_mode_popup_shadow;
border-radius: 8px;
overflow: hidden;
}
}
@media only screen and (max-width: 960px) {
#pdf-wrapper {
border-radius: 0;
padding: 0;
.pdf-file {
box-shadow: none;
border-radius: 0;
}
}
}
</style>

View File

@@ -0,0 +1,60 @@
<template>
<div class="video-wrapper">
<video
:src="file.file_url"
class="video"
:class="{'file-shadow': !this.$isMobile() }"
controlsList="nodownload"
disablePictureInPicture
playsinline
controls
autoplay
/>
</div>
</template>
<script>
export default {
name: 'Video',
props: [
'file'
],
}
</script>
<style lang="scss" scoped>
@import '@assets/vuefilemanager/_variables';
.video-wrapper {
max-width: 1080px;
max-height: 100%;
@media (min-width: 1200px) {
& {
max-width: 800px;
}
}
@media (min-width: 1920px) and (max-width: 2560px) {
& {
max-width: 1080px;
}
}
@media (min-width: 2560px) and (max-width: 3840px) {
& {
max-width: 1440px;
}
}
@media (min-width: 3840px) {
& {
max-width: 2160px;
}
}
.video {
max-width: 100%;
max-height: 100%;
align-self: center;
}
}
</style>