diff options
Diffstat (limited to 'apps/files/src/views/ReferenceFileWidget.vue')
-rw-r--r-- | apps/files/src/views/ReferenceFileWidget.vue | 254 |
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> |