Skip to content

Commit 7019db8

Browse files
committed
feat: Use the blurhash in Files
Signed-off-by: Louis Chemineau <louis@chmn.me>
1 parent 02548bd commit 7019db8

File tree

5 files changed

+66
-11
lines changed

5 files changed

+66
-11
lines changed

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

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,19 @@
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)" ref="canvas" class="files-list__row-icon-blurhash" />
20+
<img v-if="backgroundFailed !== true"
21+
ref="previewImg"
22+
alt=""
23+
class="files-list__row-icon-preview"
24+
:class="{'files-list__row-icon-preview--loaded': backgroundFailed === false}"
25+
loading="lazy"
26+
:src="previewUrl"
27+
@error="onBackgroundError"
28+
@load="onBackgroundLoad">
29+
</span>
2730

2831
<FileIcon v-else v-once />
2932

@@ -58,6 +61,7 @@ import LinkIcon from 'vue-material-design-icons/Link.vue'
5861
import NetworkIcon from 'vue-material-design-icons/Network.vue'
5962
import TagIcon from 'vue-material-design-icons/Tag.vue'
6063
import PlayCircleIcon from 'vue-material-design-icons/PlayCircle.vue'
64+
import { decode } from 'blurhash'
6165
6266
import CollectivesIcon from './CollectivesIcon.vue'
6367
import FavoriteIcon from './FavoriteIcon.vue'
@@ -107,6 +111,7 @@ export default Vue.extend({
107111
data() {
108112
return {
109113
backgroundFailed: undefined as boolean | undefined,
114+
backgroundLoaded: false as boolean,
110115
}
111116
},
112117
@@ -206,24 +211,53 @@ export default Vue.extend({
206211
207212
return null
208213
},
214+
215+
hasBlurhash() {
216+
return this.source.attributes['metadata-blurhash'] !== undefined
217+
},
218+
},
219+
220+
mounted() {
221+
if (this.hasBlurhash && this.$refs.canvas) {
222+
this.drawBlurhash()
223+
}
209224
},
210225
211226
methods: {
212227
// Called from FileEntry
213228
reset() {
214229
// Reset background state to cancel any ongoing requests
215230
this.backgroundFailed = undefined
231+
this.backgroundLoaded = false
216232
if (this.$refs.previewImg) {
217233
this.$refs.previewImg.src = ''
218234
}
219235
},
220236
237+
onBackgroundLoad(event) {
238+
this.backgroundFailed = false
239+
this.backgroundLoaded = true
240+
},
241+
221242
onBackgroundError(event) {
222243
// Do not fail if we just reset the background
223244
if (event.target?.src === '') {
224245
return
225246
}
226247
this.backgroundFailed = true
248+
this.backgroundLoaded = false
249+
},
250+
251+
drawBlurhash() {
252+
const width = this.$refs.canvas.width
253+
const height = this.$refs.canvas.height
254+
255+
const pixels = decode(this.source.attributes['metadata-blurhash'], width, height)
256+
257+
const ctx = this.$refs.canvas.getContext('2d')
258+
const imageData = ctx.createImageData(width, height)
259+
imageData.data.set(pixels)
260+
ctx.putImageData(imageData, 0, 0)
227261
},
228262
229263
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)