mirror of https://github.com/nextcloud/photos
feat: Use blurhash as preview
Signed-off-by: Louis Chemineau <louis@chmn.me>
This commit is contained in:
parent
3b43678533
commit
12f6e25a7d
|
@ -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"
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 />
|
||||
|
|
Loading…
Reference in New Issue