aboutsummaryrefslogtreecommitdiffstats
path: root/apps/files/src/views/ReferenceFileWidget.vue
diff options
context:
space:
mode:
Diffstat (limited to 'apps/files/src/views/ReferenceFileWidget.vue')
-rw-r--r--apps/files/src/views/ReferenceFileWidget.vue254
1 files changed, 189 insertions, 65 deletions
diff --git a/apps/files/src/views/ReferenceFileWidget.vue b/apps/files/src/views/ReferenceFileWidget.vue
index 7634994a30d..9db346ea35d 100644
--- a/apps/files/src/views/ReferenceFileWidget.vue
+++ b/apps/files/src/views/ReferenceFileWidget.vue
@@ -1,70 +1,159 @@
<!--
- - @copyright Copyright (c) 2022 Julius Härtl <jus@bitgrid.net>
- -
- - @author Julius Härtl <jus@bitgrid.net>
- -
- - @license GNU AGPL version 3 or any later version
- -
- - This program is free software: you can redistribute it and/or modify
- - it under the terms of the GNU Affero General Public License as
- - published by the Free Software Foundation, either version 3 of the
- - License, or (at your option) any later version.
- -
- - This program is distributed in the hope that it will be useful,
- - but WITHOUT ANY WARRANTY; without even the implied warranty of
- - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- - GNU Affero General Public License for more details.
- -
- - You should have received a copy of the GNU Affero General Public License
- - along with this program. If not, see <http://www.gnu.org/licenses/>.
- -->
+ - SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
+ - SPDX-License-Identifier: AGPL-3.0-or-later
+-->
<template>
<div v-if="!accessible" class="widget-file widget-file--no-access">
- <div class="widget-file--image widget-file--image--icon icon-folder" />
- <div class="widget-file--details">
- <p class="widget-file--title">
+ <span class="widget-file__image widget-file__image--icon">
+ <FolderIcon v-if="isFolder" :size="88" />
+ <FileIcon v-else :size="88" />
+ </span>
+ <span class="widget-file__details">
+ <p class="widget-file__title">
{{ t('files', 'File cannot be accessed') }}
</p>
- <p class="widget-file--description">
- {{ t('files', 'You might not have have permissions to view it, ask the sender to share it') }}
+ <p class="widget-file__description">
+ {{ t('files', 'The file could not be found or you do not have permissions to view it. Ask the sender to share it.') }}
</p>
- </div>
+ </span>
</div>
+
+ <!-- Live preview if a handler is available -->
+ <component :is="viewerHandler.component"
+ v-else-if="interactive && viewerHandler && !failedViewer"
+ :active="false /* prevent video from autoplaying */"
+ :can-swipe="false"
+ :can-zoom="false"
+ :is-embedded="true"
+ v-bind="viewerFile"
+ :file-list="[viewerFile]"
+ :is-full-screen="false"
+ :is-sidebar-shown="false"
+ class="widget-file widget-file--interactive"
+ @error="failedViewer = true" />
+
+ <!-- The file is accessible -->
<a v-else
- class="widget-file"
+ class="widget-file widget-file--link"
:href="richObject.link"
- @click.prevent="navigate">
- <div class="widget-file--image" :class="filePreviewClass" :style="filePreview" />
- <div class="widget-file--details">
- <p class="widget-file--title">{{ richObject.name }}</p>
- <p class="widget-file--description">{{ fileSize }}<br>{{ fileMtime }}</p>
- <p class="widget-file--link">{{ filePath }}</p>
- </div>
+ target="_blank"
+ @click="navigate">
+ <span class="widget-file__image" :class="filePreviewClass" :style="filePreviewStyle">
+ <template v-if="!previewUrl">
+ <FolderIcon v-if="isFolder" :size="88" fill-color="var(--color-primary-element)" />
+ <FileIcon v-else :size="88" />
+ </template>
+ </span>
+ <span class="widget-file__details">
+ <p class="widget-file__title">{{ richObject.name }}</p>
+ <p class="widget-file__description">{{ fileSize }}<br>{{ fileMtime }}</p>
+ <p class="widget-file__link">{{ filePath }}</p>
+ </span>
</a>
</template>
-<script>
-import { generateUrl } from '@nextcloud/router'
+
+<script lang="ts">
+import { defineComponent, type Component, type PropType } from 'vue'
+import { generateRemoteUrl, generateUrl } from '@nextcloud/router'
+import { getCurrentUser } from '@nextcloud/auth'
+import { getFilePickerBuilder } from '@nextcloud/dialogs'
+import { Node } from '@nextcloud/files'
+import FileIcon from 'vue-material-design-icons/File.vue'
+import FolderIcon from 'vue-material-design-icons/Folder.vue'
import path from 'path'
-export default {
+// see lib/private/Collaboration/Reference/File/FileReferenceProvider.php
+type Ressource = {
+ id: number
+ name: string
+ size: number
+ path: string
+ link: string
+ mimetype: string
+ mtime: number // as unix timestamp
+ 'preview-available': boolean
+}
+
+type ViewerHandler = {
+ id: string
+ group: string
+ mimes: string[]
+ component: Component
+}
+
+/**
+ * Minimal mock of the legacy Viewer FileInfo
+ * TODO: replace by Node object
+ */
+type ViewerFile = {
+ filename: string // the path to the root folder
+ basename: string // the file name
+ lastmod: Date // the last modification date
+ size: number // the file size in bytes
+ type: string
+ mime: string
+ fileid: number
+ failed: boolean
+ loaded: boolean
+ davPath: string
+ source: string
+}
+
+export default defineComponent({
name: 'ReferenceFileWidget',
+ components: {
+ FolderIcon,
+ FileIcon,
+ },
props: {
richObject: {
- type: Object,
+ type: Object as PropType<Ressource>,
required: true,
},
accessible: {
type: Boolean,
default: true,
},
+ interactive: {
+ type: Boolean,
+ default: true,
+ },
},
+
data() {
return {
- previewUrl: window.OC.MimeType.getIconUrl(this.richObject.mimetype),
+ previewUrl: null as string | null,
+ failedViewer: false,
}
},
+
computed: {
+ availableViewerHandlers(): ViewerHandler[] {
+ return (window?.OCA?.Viewer?.availableHandlers || []) as ViewerHandler[]
+ },
+ viewerHandler(): ViewerHandler | undefined {
+ return this.availableViewerHandlers
+ .find(handler => handler.mimes.includes(this.richObject.mimetype))
+ },
+ viewerFile(): ViewerFile {
+ const davSource = generateRemoteUrl(`dav/files/${getCurrentUser()?.uid}/${this.richObject.path}`)
+ .replace(/\/\/$/, '/')
+ return {
+ filename: this.richObject.path,
+ basename: this.richObject.name,
+ lastmod: new Date(this.richObject.mtime * 1000),
+ size: this.richObject.size,
+ type: 'file',
+ mime: this.richObject.mimetype,
+ fileid: this.richObject.id,
+ failed: false,
+ loaded: true,
+ davPath: davSource,
+ source: davSource,
+ }
+ },
+
fileSize() {
return window.OC.Util.humanFileSize(this.richObject.size)
},
@@ -74,26 +163,26 @@ export default {
filePath() {
return path.dirname(this.richObject.path)
},
- filePreview() {
+ filePreviewStyle() {
if (this.previewUrl) {
return {
backgroundImage: 'url(' + this.previewUrl + ')',
}
}
-
- return {
- backgroundImage: 'url(' + window.OC.MimeType.getIconUrl(this.richObject.mimetype) + ')',
- }
-
+ return {}
},
filePreviewClass() {
if (this.previewUrl) {
- return 'widget-file--image--preview'
+ return 'widget-file__image--preview'
}
- return 'widget-file--image--icon'
+ return 'widget-file__image--icon'
},
+ isFolder() {
+ return this.richObject.mimetype === 'httpd/unix-directory'
+ },
},
+
mounted() {
if (this.richObject['preview-available']) {
const previewUrl = generateUrl('/core/preview?fileId={fileId}&x=250&y=250', {
@@ -110,43 +199,78 @@ export default {
}
},
methods: {
- navigate() {
- if (OCA.Viewer && OCA.Viewer.mimetypes.indexOf(this.richObject.mimetype) !== -1) {
- OCA.Viewer.open({ path: this.richObject.path })
- return
+ navigate(event) {
+ if (this.isFolder) {
+ event.stopPropagation()
+ event.preventDefault()
+ this.openFilePicker()
+ } else if (window?.OCA?.Viewer?.mimetypes.indexOf(this.richObject.mimetype) !== -1 && !window?.OCA?.Viewer?.file) {
+ event.stopPropagation()
+ event.preventDefault()
+ window?.OCA?.Viewer?.open({ path: this.richObject.path })
}
- window.location = this.richObject.link
+ },
+
+ openFilePicker() {
+ const picker = getFilePickerBuilder(t('settings', 'Your files'))
+ .allowDirectories(true)
+ .setMultiSelect(false)
+ .addButton({
+ id: 'open',
+ label: this.t('settings', 'Open in files'),
+ callback(nodes: Node[]) {
+ if (nodes[0]) {
+ window.open(generateUrl('/f/{fileid}', {
+ fileid: nodes[0].fileid,
+ }))
+ }
+ },
+ type: 'primary',
+ })
+ .disableNavigation()
+ .startAt(this.richObject.path)
+ .build()
+ picker.pick()
},
},
-}
+})
</script>
+
<style lang="scss" scoped>
.widget-file {
display: flex;
flex-grow: 1;
color: var(--color-main-text) !important;
text-decoration: none !important;
+ padding: 0 !important;
- &--image {
- min-width: 40%;
+ &__image {
+ width: 30%;
+ min-width: 160px;
+ max-width: 320px;
background-position: center;
background-size: cover;
background-repeat: no-repeat;
- &.widget-file--image--icon {
+ &--icon {
min-width: 88px;
- background-size: 44px;
+ max-width: 88px;
+ padding: 12px;
+ padding-inline-end: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
}
}
- &--title {
+ &__title {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-weight: bold;
}
- &--details {
+ &__details {
padding: 12px;
flex-grow: 1;
display: flex;
@@ -158,7 +282,7 @@ export default {
}
}
- &--description {
+ &__description {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
@@ -167,16 +291,16 @@ export default {
-webkit-box-orient: vertical;
}
+ // No preview, standard link to ressource
&--link {
color: var(--color-text-maxcontrast);
}
- &.widget-file--no-access {
- padding: 12px;
-
- .widget-file--details {
- padding: 0;
- }
+ &--interactive {
+ position: relative;
+ height: 400px;
+ max-height: 50vh;
+ margin: 0;
}
}
</style>