diff options
author | Louis Chemineau <louis@chmn.me> | 2024-03-07 11:51:06 +0100 |
---|---|---|
committer | Louis Chemineau <louis@chmn.me> | 2024-03-07 11:57:49 +0100 |
commit | 12f6e25a7d1dad6cf3d84a373c8c393e85bf7c51 (patch) | |
tree | b23f4199368365adbd17a378e97cf37993833cd8 | |
parent | 3b436785333657c2c42e17fe0a21858d1d4a0b7e (diff) |
feat: Use blurhash as preview
Signed-off-by: Louis Chemineau <louis@chmn.me>
-rw-r--r-- | package-lock.json | 6 | ||||
-rw-r--r-- | package.json | 1 | ||||
-rw-r--r-- | src/components/File.vue | 69 | ||||
-rw-r--r-- | src/services/DavRequest.js | 1 |
4 files changed, 45 insertions, 32 deletions
diff --git a/package-lock.json b/package-lock.json index 7ad17b28..82bda293 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "@nextcloud/sharing": "^0.1.0", "@nextcloud/upload": "^1.0.4", "@nextcloud/vue": "^8.8.1", + "blurhash": "^2.0.5", "camelcase": "^8.0.0", "debounce": "^1.2.1", "he": "^1.2.0", @@ -6935,6 +6936,11 @@ "dev": true, "license": "MIT" }, + "node_modules/blurhash": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/blurhash/-/blurhash-2.0.5.tgz", + "integrity": "sha512-cRygWd7kGBQO3VEhPiTgq4Wc43ctsM+o46urrmPOiuAe+07fzlSB9OJVdpgDL0jPqXUVQ9ht7aq7kxOeJHRK+w==" + }, "node_modules/bn.js": { "version": "5.2.1", "license": "MIT" diff --git a/package.json b/package.json index 650c1208..ebe5c136 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "@nextcloud/sharing": "^0.1.0", "@nextcloud/upload": "^1.0.4", "@nextcloud/vue": "^8.8.1", + "blurhash": "^2.0.5", "camelcase": "^8.0.0", "debounce": "^1.2.1", "he": "^1.2.0", diff --git a/src/components/File.vue b/src/components/File.vue index 82dab56e..43ef9185 100644 --- a/src/components/File.vue +++ b/src/components/File.vue @@ -41,6 +41,8 @@ <!-- Preload large preview for near visible files --> <!-- Preload small preview for further away files --> <template v-if="initialized"> + <canvas v-if="hasBlurhash && !loadedSmall && !loadedLarge" ref="canvas" class="file__blurhash" /> + <img v-if="!loadedLarge && (loadedSmall || (distance < 5 && !errorSmall))" ref="imgSmall" :key="`${file.basename}-small`" @@ -81,11 +83,12 @@ <script> import VideoIcon from 'vue-material-design-icons/Video.vue' import PlayCircleIcon from 'vue-material-design-icons/PlayCircle.vue' -import FavoriteIcon from './FavoriteIcon.vue' +import { decode } from 'blurhash' import { generateUrl } from '@nextcloud/router' import { NcCheckboxRadioSwitch } from '@nextcloud/vue' +import FavoriteIcon from './FavoriteIcon.vue' import { isCachedPreview } from '../services/PreviewService.js' export default { @@ -155,6 +158,9 @@ export default { isVisible() { return this.distance === 0 }, + hasBlurhash() { + return this.file.metadataBlurhash !== undefined + }, }, async mounted() { @@ -164,6 +170,10 @@ export default { ]) this.initialized = true + + await this.$nextTick() // Wait for next tick to have the canvas in the DOM + + this.drawBlurhash() }, beforeDestroy() { @@ -211,6 +221,21 @@ export default { return generateUrl(`/apps/photos/api/v1/preview/${this.file.fileid}?etag=${this.decodedEtag}&x=${size}&y=${size}`) } }, + drawBlurhash() { + if (!this.hasBlurhash || !this.$refs.canvas) { + return + } + + const width = this.$refs.canvas.width + const height = this.$refs.canvas.height + + const pixels = decode(this.file.metadataBlurhash, width, height) + + const ctx = this.$refs.canvas.getContext('2d') + const imageData = ctx.createImageData(width, height) + imageData.data.set(pixels) + ctx.putImageData(imageData, 0, 0) + }, }, } @@ -242,6 +267,7 @@ export default { outline-offset: -4px; pointer-events: none; } + .selection-checkbox { opacity: 1; } @@ -254,8 +280,17 @@ export default { outline: none; // Override global focus state. display: flex; // Fill parent size + &__blurhash { + position: absolute; + top: 0; + height: 100%; + width: 100%; + object-fit: cover; + } + &__images { - display: contents; + width: 100%; + height: 100%; .icon-overlay { position: absolute; @@ -278,36 +313,6 @@ export default { position: absolute; color: transparent; /// Hide alt='' text when loading. } - - .loading-overlay { - position: absolute; - height: 100%; - width: 100%; - display: flex; - align-content: center; - align-items: center; - justify-content: center; - - svg { - width: 70%; - height: 70%; - } - } - } - - &__hidden-description { - position: absolute; - left: -10000px; - top: -10000px; - width: 1px; - height: 1px; - overflow: hidden; - - &.show { - position: initial; - width: fit-content; - height: fit-content; - } } } diff --git a/src/services/DavRequest.js b/src/services/DavRequest.js index f402ac6e..28428db2 100644 --- a/src/services/DavRequest.js +++ b/src/services/DavRequest.js @@ -30,6 +30,7 @@ const props = ` <nc:metadata-photos-size /> <nc:metadata-photos-original_date_time /> <nc:metadata-files-live-photo /> + <nc:metadata-blurhash/> <nc:has-preview /> <nc:realpath /> <nc:hidden /> |