diff options
Diffstat (limited to 'apps/files/src/components/FileEntry/FileEntryPreview.vue')
-rw-r--r-- | apps/files/src/components/FileEntry/FileEntryPreview.vue | 118 |
1 files changed, 94 insertions, 24 deletions
diff --git a/apps/files/src/components/FileEntry/FileEntryPreview.vue b/apps/files/src/components/FileEntry/FileEntryPreview.vue index 8022af9218e..3d0fffe7584 100644 --- a/apps/files/src/components/FileEntry/FileEntryPreview.vue +++ b/apps/files/src/components/FileEntry/FileEntryPreview.vue @@ -14,16 +14,23 @@ </template> </template> - <!-- Decorative image, should not be aria documented --> - <img v-else-if="previewUrl && backgroundFailed !== true" - ref="previewImg" - alt="" - class="files-list__row-icon-preview" - :class="{'files-list__row-icon-preview--loaded': backgroundFailed === false}" - loading="lazy" - :src="previewUrl" - @error="onBackgroundError" - @load="backgroundFailed = false"> + <!-- Decorative images, should not be aria documented --> + <span v-else-if="previewUrl" class="files-list__row-icon-preview-container"> + <canvas v-if="hasBlurhash && (backgroundFailed === true || !backgroundLoaded)" + ref="canvas" + class="files-list__row-icon-blurhash" + aria-hidden="true" /> + <img v-if="backgroundFailed !== true" + :key="source.fileid" + ref="previewImg" + alt="" + class="files-list__row-icon-preview" + :class="{'files-list__row-icon-preview--loaded': backgroundFailed === false}" + loading="lazy" + :src="previewUrl" + @error="onBackgroundError" + @load="onBackgroundLoad"> + </span> <FileIcon v-else v-once /> @@ -43,11 +50,13 @@ import type { PropType } from 'vue' import type { UserConfig } from '../../types.ts' import { Node, FileType } from '@nextcloud/files' -import { generateUrl } from '@nextcloud/router' import { translate as t } from '@nextcloud/l10n' -import { Type as ShareType } from '@nextcloud/sharing' +import { generateUrl } from '@nextcloud/router' +import { ShareType } from '@nextcloud/sharing' +import { getSharingToken, isPublicShare } from '@nextcloud/sharing/public' +import { decode } from 'blurhash' +import { defineComponent } from 'vue' -import Vue from 'vue' import AccountGroupIcon from 'vue-material-design-icons/AccountGroup.vue' import AccountPlusIcon from 'vue-material-design-icons/AccountPlus.vue' import FileIcon from 'vue-material-design-icons/File.vue' @@ -55,7 +64,7 @@ import FolderIcon from 'vue-material-design-icons/Folder.vue' import FolderOpenIcon from 'vue-material-design-icons/FolderOpen.vue' import KeyIcon from 'vue-material-design-icons/Key.vue' import LinkIcon from 'vue-material-design-icons/Link.vue' -import NetworkIcon from 'vue-material-design-icons/Network.vue' +import NetworkIcon from 'vue-material-design-icons/NetworkOutline.vue' import TagIcon from 'vue-material-design-icons/Tag.vue' import PlayCircleIcon from 'vue-material-design-icons/PlayCircle.vue' @@ -64,8 +73,9 @@ import FavoriteIcon from './FavoriteIcon.vue' import { isLivePhoto } from '../../services/LivePhotos' import { useUserConfigStore } from '../../store/userconfig.ts' +import logger from '../../logger.ts' -export default Vue.extend({ +export default defineComponent({ name: 'FileEntryPreview', components: { @@ -99,21 +109,25 @@ export default Vue.extend({ setup() { const userConfigStore = useUserConfigStore() + const isPublic = isPublicShare() + const publicSharingToken = getSharingToken() + return { userConfigStore, + + isPublic, + publicSharingToken, } }, data() { return { backgroundFailed: undefined as boolean | undefined, + backgroundLoaded: false, } }, computed: { - fileid() { - return this.source?.fileid?.toString?.() - }, isFavorite(): boolean { return this.source.attributes.favorite === 1 }, @@ -134,11 +148,28 @@ export default Vue.extend({ return null } + if (this.source.attributes['has-preview'] !== true + && this.source.mime !== undefined + && this.source.mime !== 'application/octet-stream' + ) { + const previewUrl = generateUrl('/core/mimeicon?mime={mime}', { + mime: this.source.mime, + }) + const url = new URL(window.location.origin + previewUrl) + return url.href + } + try { const previewUrl = this.source.attributes.previewUrl - || generateUrl('/core/preview?fileId={fileid}', { - fileid: this.fileid, - }) + || (this.isPublic + ? generateUrl('/apps/files_sharing/publicpreview/{token}?file={file}', { + token: this.publicSharingToken, + file: this.source.path, + }) + : generateUrl('/core/preview?fileId={fileid}', { + fileid: String(this.source.fileid), + }) + ) const url = new URL(window.location.origin + previewUrl) // Request tiny previews @@ -183,7 +214,7 @@ export default Vue.extend({ // Link and mail shared folders const shareTypes = Object.values(this.source?.attributes?.['share-types'] || {}).flat() as number[] - if (shareTypes.some(type => type === ShareType.SHARE_TYPE_LINK || type === ShareType.SHARE_TYPE_EMAIL)) { + if (shareTypes.some(type => type === ShareType.Link || type === ShareType.Email)) { return LinkIcon } @@ -200,10 +231,22 @@ export default Vue.extend({ return AccountGroupIcon case 'collective': return CollectivesIcon + case 'shared': + return AccountPlusIcon } return null }, + + hasBlurhash() { + return this.source.attributes['metadata-blurhash'] !== undefined + }, + }, + + mounted() { + if (this.hasBlurhash && this.$refs.canvas) { + this.drawBlurhash() + } }, methods: { @@ -211,17 +254,44 @@ export default Vue.extend({ reset() { // Reset background state to cancel any ongoing requests this.backgroundFailed = undefined - if (this.$refs.previewImg) { - this.$refs.previewImg.src = '' + this.backgroundLoaded = false + const previewImg = this.$refs.previewImg as HTMLImageElement | undefined + if (previewImg) { + previewImg.src = '' } }, + onBackgroundLoad() { + this.backgroundFailed = false + this.backgroundLoaded = true + }, + onBackgroundError(event) { // Do not fail if we just reset the background if (event.target?.src === '') { return } this.backgroundFailed = true + this.backgroundLoaded = false + }, + + drawBlurhash() { + const canvas = this.$refs.canvas as HTMLCanvasElement + + const width = canvas.width + const height = canvas.height + + const pixels = decode(this.source.attributes['metadata-blurhash'], width, height) + + const ctx = canvas.getContext('2d') + if (ctx === null) { + logger.error('Cannot create context for blurhash canvas') + return + } + + const imageData = ctx.createImageData(width, height) + imageData.data.set(pixels) + ctx.putImageData(imageData, 0, 0) }, t, |