mirror of
https://github.com/nextcloud/server.git
synced 2026-02-20 00:12:30 -05:00
feat: Use the blurhash in Files
Signed-off-by: Louis Chemineau <louis@chmn.me>
This commit is contained in:
parent
19dd32962d
commit
56e4859201
5 changed files with 76 additions and 11 deletions
|
|
@ -14,16 +14,22 @@
|
|||
</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"
|
||||
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 />
|
||||
|
||||
|
|
@ -58,6 +64,7 @@ import LinkIcon from 'vue-material-design-icons/Link.vue'
|
|||
import NetworkIcon from 'vue-material-design-icons/Network.vue'
|
||||
import TagIcon from 'vue-material-design-icons/Tag.vue'
|
||||
import PlayCircleIcon from 'vue-material-design-icons/PlayCircle.vue'
|
||||
import { decode } from 'blurhash'
|
||||
|
||||
import CollectivesIcon from './CollectivesIcon.vue'
|
||||
import FavoriteIcon from './FavoriteIcon.vue'
|
||||
|
|
@ -107,6 +114,7 @@ export default Vue.extend({
|
|||
data() {
|
||||
return {
|
||||
backgroundFailed: undefined as boolean | undefined,
|
||||
backgroundLoaded: false,
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -206,6 +214,16 @@ export default Vue.extend({
|
|||
|
||||
return null
|
||||
},
|
||||
|
||||
hasBlurhash() {
|
||||
return this.source.attributes['metadata-blurhash'] !== undefined
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (this.hasBlurhash && this.$refs.canvas) {
|
||||
this.drawBlurhash()
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
|
@ -213,17 +231,43 @@ export default Vue.extend({
|
|||
reset() {
|
||||
// Reset background state to cancel any ongoing requests
|
||||
this.backgroundFailed = undefined
|
||||
this.backgroundLoaded = false
|
||||
if (this.$refs.previewImg) {
|
||||
this.$refs.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,
|
||||
|
|
|
|||
|
|
@ -556,11 +556,24 @@ export default defineComponent({
|
|||
}
|
||||
}
|
||||
|
||||
&-preview {
|
||||
&-preview-container {
|
||||
position: relative; // Needed for the blurshash to be positioned correctly
|
||||
overflow: hidden;
|
||||
width: var(--icon-preview-size);
|
||||
height: var(--icon-preview-size);
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
&-blurhash {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
&-preview {
|
||||
// Center and contain the preview
|
||||
object-fit: contain;
|
||||
object-position: center;
|
||||
|
|
|
|||
|
|
@ -66,5 +66,6 @@ registerPreviewServiceWorker()
|
|||
|
||||
registerDavProperty('nc:hidden', { nc: 'http://nextcloud.org/ns' })
|
||||
registerDavProperty('nc:is-mount-root', { nc: 'http://nextcloud.org/ns' })
|
||||
registerDavProperty('nc:metadata-blurhash', { nc: 'http://nextcloud.org/ns' })
|
||||
|
||||
initLivePhotos()
|
||||
|
|
|
|||
6
package-lock.json
generated
6
package-lock.json
generated
|
|
@ -38,6 +38,7 @@
|
|||
"@vueuse/integrations": "^11.0.1",
|
||||
"backbone": "^1.4.1",
|
||||
"blueimp-md5": "^2.19.0",
|
||||
"blurhash": "^2.0.5",
|
||||
"browserslist-useragent-regexp": "^4.1.1",
|
||||
"camelcase": "^8.0.0",
|
||||
"cancelable-promise": "^4.3.1",
|
||||
|
|
@ -8082,6 +8083,11 @@
|
|||
"integrity": "sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==",
|
||||
"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/bmp-js": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz",
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@
|
|||
"@vueuse/integrations": "^11.0.1",
|
||||
"backbone": "^1.4.1",
|
||||
"blueimp-md5": "^2.19.0",
|
||||
"blurhash": "^2.0.5",
|
||||
"browserslist-useragent-regexp": "^4.1.1",
|
||||
"camelcase": "^8.0.0",
|
||||
"cancelable-promise": "^4.3.1",
|
||||
|
|
|
|||
Loading…
Reference in a new issue