Skip to content

Commit 56e4859

Browse files
artongesusnux
authored andcommitted
feat: Use the blurhash in Files
Signed-off-by: Louis Chemineau <louis@chmn.me>
1 parent 19dd329 commit 56e4859

File tree

5 files changed

+76
-11
lines changed

5 files changed

+76
-11
lines changed

apps/files/src/components/FileEntry/FileEntryPreview.vue

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,22 @@
1414
</template>
1515
</template>
1616

17-
<!-- Decorative image, should not be aria documented -->
18-
<img v-else-if="previewUrl && backgroundFailed !== true"
19-
ref="previewImg"
20-
alt=""
21-
class="files-list__row-icon-preview"
22-
:class="{'files-list__row-icon-preview--loaded': backgroundFailed === false}"
23-
loading="lazy"
24-
:src="previewUrl"
25-
@error="onBackgroundError"
26-
@load="backgroundFailed = false">
17+
<!-- Decorative images, should not be aria documented -->
18+
<span v-else-if="previewUrl" class="files-list__row-icon-preview-container">
19+
<canvas v-if="hasBlurhash && (backgroundFailed === true || !backgroundLoaded)"
20+
ref="canvas"
21+
class="files-list__row-icon-blurhash"
22+
aria-hidden="true" />
23+
<img v-if="backgroundFailed !== true"
24+
ref="previewImg"
25+
alt=""
26+
class="files-list__row-icon-preview"
27+
:class="{'files-list__row-icon-preview--loaded': backgroundFailed === false}"
28+
loading="lazy"
29+
:src="previewUrl"
30+
@error="onBackgroundError"
31+
@load="onBackgroundLoad">
32+
</span>
2733

2834
<FileIcon v-else v-once />
2935

@@ -58,6 +64,7 @@ import LinkIcon from 'vue-material-design-icons/Link.vue'
5864
import NetworkIcon from 'vue-material-design-icons/Network.vue'
5965
import TagIcon from 'vue-material-design-icons/Tag.vue'
6066
import PlayCircleIcon from 'vue-material-design-icons/PlayCircle.vue'
67+
import { decode } from 'blurhash'
6168
6269
import CollectivesIcon from './CollectivesIcon.vue'
6370
import FavoriteIcon from './FavoriteIcon.vue'
@@ -107,6 +114,7 @@ export default Vue.extend({
107114
data() {
108115
return {
109116
backgroundFailed: undefined as boolean | undefined,
117+
backgroundLoaded: false,
110118
}
111119
},
112120
@@ -206,24 +214,60 @@ export default Vue.extend({
206214
207215
return null
208216
},
217+
218+
hasBlurhash() {
219+
return this.source.attributes['metadata-blurhash'] !== undefined
220+
},
221+
},
222+
223+
mounted() {
224+
if (this.hasBlurhash && this.$refs.canvas) {
225+
this.drawBlurhash()
226+
}
209227
},
210228
211229
methods: {
212230
// Called from FileEntry
213231
reset() {
214232
// Reset background state to cancel any ongoing requests
215233
this.backgroundFailed = undefined
234+
this.backgroundLoaded = false
216235
if (this.$refs.previewImg) {
217236
this.$refs.previewImg.src = ''
218237
}
219238
},
220239
240+
onBackgroundLoad() {
241+
this.backgroundFailed = false
242+
this.backgroundLoaded = true
243+
},
244+
221245
onBackgroundError(event) {
222246
// Do not fail if we just reset the background
223247
if (event.target?.src === '') {
224248
return
225249
}
226250
this.backgroundFailed = true
251+
this.backgroundLoaded = false
252+
},
253+
254+
drawBlurhash() {
255+
const canvas = this.$refs.canvas as HTMLCanvasElement
256+
257+
const width = canvas.width
258+
const height = canvas.height
259+
260+
const pixels = decode(this.source.attributes['metadata-blurhash'], width, height)
261+
262+
const ctx = canvas.getContext('2d')
263+
if (ctx === null) {
264+
logger.error('Cannot create context for blurhash canvas')
265+
return
266+
}
267+
268+
const imageData = ctx.createImageData(width, height)
269+
imageData.data.set(pixels)
270+
ctx.putImageData(imageData, 0, 0)
227271
},
228272
229273
t,

apps/files/src/components/FilesListVirtual.vue

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -556,11 +556,24 @@ export default defineComponent({
556556
}
557557
}
558558
559-
&-preview {
559+
&-preview-container {
560+
position: relative; // Needed for the blurshash to be positioned correctly
560561
overflow: hidden;
561562
width: var(--icon-preview-size);
562563
height: var(--icon-preview-size);
563564
border-radius: var(--border-radius);
565+
}
566+
567+
&-blurhash {
568+
position: absolute;
569+
top: 0;
570+
left: 0;
571+
height: 100%;
572+
width: 100%;
573+
object-fit: cover;
574+
}
575+
576+
&-preview {
564577
// Center and contain the preview
565578
object-fit: contain;
566579
object-position: center;

apps/files/src/init.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,5 +66,6 @@ registerPreviewServiceWorker()
6666

6767
registerDavProperty('nc:hidden', { nc: 'http://nextcloud.org/ns' })
6868
registerDavProperty('nc:is-mount-root', { nc: 'http://nextcloud.org/ns' })
69+
registerDavProperty('nc:metadata-blurhash', { nc: 'http://nextcloud.org/ns' })
6970

7071
initLivePhotos()

package-lock.json

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
"@vueuse/integrations": "^11.0.1",
6969
"backbone": "^1.4.1",
7070
"blueimp-md5": "^2.19.0",
71+
"blurhash": "^2.0.5",
7172
"browserslist-useragent-regexp": "^4.1.1",
7273
"camelcase": "^8.0.0",
7374
"cancelable-promise": "^4.3.1",

0 commit comments

Comments
 (0)