Skip to content

Commit 53c47f3

Browse files
authored
Add lazy loading for tiles view (#11508)
* Add lazy loading for tiles view * Rm unused prop * Rm dev code * Add same animation as in table * But adjust the colors * Add changelog item
1 parent bb530b9 commit 53c47f3

File tree

5 files changed

+153
-76
lines changed

5 files changed

+153
-76
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Enhancement: Improve tiles view performance
2+
3+
We've made some changes to the tiles view to improve performance.
4+
So that users can see their files and folders faster, when working with a lot of files.
5+
6+
https://github.com/owncloud/web/pull/11508
7+
https://github.com/owncloud/web/issues/11480

packages/web-pkg/src/components/FilesList/ResourceTile.vue

+131-65
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<template>
22
<div
3+
ref="observerTarget"
34
class="oc-tile-card oc-card oc-card-default oc-rounded"
45
:data-item-id="resource.id"
56
:class="{
@@ -9,74 +10,77 @@
910
}"
1011
@contextmenu="$emit('contextmenu', $event)"
1112
>
12-
<resource-link
13-
class="oc-card-media-top oc-flex oc-flex-center oc-flex-middle oc-m-rm"
14-
:resource="resource"
15-
:link="resourceRoute"
16-
:is-resource-clickable="isResourceClickable"
17-
tabindex="-1"
18-
@click="$emit('click')"
19-
>
20-
<div class="oc-tile-card-selection">
21-
<slot name="selection" :item="resource" />
22-
</div>
23-
<oc-tag
24-
v-if="isResourceDisabled && isProjectSpaceResource(resource)"
25-
class="resource-disabled-indicator oc-position-absolute"
26-
type="span"
27-
>
28-
<span v-text="$gettext('Disabled')" />
29-
</oc-tag>
30-
<div
31-
v-oc-tooltip="tooltipLabelIcon"
32-
class="oc-tile-card-preview oc-flex oc-flex-middle oc-flex-center"
33-
:aria-label="tooltipLabelIcon"
13+
<div v-if="isHidden" class="oc-tile-card-lazy-shimmer"></div>
14+
<template v-else>
15+
<resource-link
16+
class="oc-card-media-top oc-flex oc-flex-center oc-flex-middle oc-m-rm"
17+
:resource="resource"
18+
:link="resourceRoute"
19+
:is-resource-clickable="isResourceClickable"
20+
tabindex="-1"
21+
@click="$emit('click')"
3422
>
35-
<div class="oc-tile-card-hover"></div>
36-
<slot name="imageField" :item="resource">
37-
<oc-img
38-
v-if="shouldDisplayThumbnails(resource)"
39-
class="tile-preview"
40-
:src="resource.thumbnail"
41-
/>
42-
<resource-icon
43-
v-else
44-
:resource="resource"
45-
:size="resourceIconSize"
46-
class="tile-default-image oc-pt-xs"
47-
>
48-
<template v-if="showStatusIcon" #status>
49-
<oc-icon v-bind="statusIconAttrs" size="xsmall" />
50-
</template>
51-
</resource-icon>
52-
</slot>
53-
</div>
54-
</resource-link>
55-
<div class="oc-card-body oc-p-s">
56-
<div class="oc-flex oc-flex-between oc-flex-middle">
57-
<div class="oc-flex oc-flex-middle oc-text-truncate resource-name-wrapper">
58-
<resource-list-item
59-
:resource="resource"
60-
:is-icon-displayed="false"
61-
:is-extension-displayed="isExtensionDisplayed"
62-
:is-resource-clickable="isResourceClickable"
63-
:link="resourceRoute"
64-
@click="$emit('click')"
65-
/>
23+
<div class="oc-tile-card-selection">
24+
<slot name="selection" :item="resource" />
25+
</div>
26+
<oc-tag
27+
v-if="isResourceDisabled && isProjectSpaceResource(resource)"
28+
class="resource-disabled-indicator oc-position-absolute"
29+
type="span"
30+
>
31+
<span v-text="$gettext('Disabled')" />
32+
</oc-tag>
33+
<div
34+
v-oc-tooltip="tooltipLabelIcon"
35+
class="oc-tile-card-preview oc-flex oc-flex-middle oc-flex-center"
36+
:aria-label="tooltipLabelIcon"
37+
>
38+
<div class="oc-tile-card-hover"></div>
39+
<slot name="imageField" :item="resource">
40+
<oc-img
41+
v-if="shouldDisplayThumbnails(resource)"
42+
class="tile-preview"
43+
:src="resource.thumbnail"
44+
/>
45+
<resource-icon
46+
v-else
47+
:resource="resource"
48+
:size="resourceIconSize"
49+
class="tile-default-image oc-pt-xs"
50+
>
51+
<template v-if="showStatusIcon" #status>
52+
<oc-icon v-bind="statusIconAttrs" size="xsmall" />
53+
</template>
54+
</resource-icon>
55+
</slot>
6656
</div>
67-
<div class="oc-flex oc-flex-middle">
68-
<!-- Slot for indicators !-->
69-
<slot name="indicators" :item="resource" class="resource-indicators" />
70-
<!-- Slot for individual actions -->
71-
<slot name="actions" :item="resource" />
72-
<!-- Slot for contextmenu -->
73-
<slot name="contextMenu" :item="resource" />
57+
</resource-link>
58+
<div class="oc-card-body oc-p-s">
59+
<div class="oc-flex oc-flex-between oc-flex-middle">
60+
<div class="oc-flex oc-flex-middle oc-text-truncate resource-name-wrapper">
61+
<resource-list-item
62+
:resource="resource"
63+
:is-icon-displayed="false"
64+
:is-extension-displayed="isExtensionDisplayed"
65+
:is-resource-clickable="isResourceClickable"
66+
:link="resourceRoute"
67+
@click="$emit('click')"
68+
/>
69+
</div>
70+
<div class="oc-flex oc-flex-middle">
71+
<!-- Slot for indicators !-->
72+
<slot name="indicators" :item="resource" class="resource-indicators" />
73+
<!-- Slot for individual actions -->
74+
<slot name="actions" :item="resource" />
75+
<!-- Slot for contextmenu -->
76+
<slot name="contextMenu" :item="resource" />
77+
</div>
7478
</div>
79+
<p v-if="resourceDescription" class="oc-text-left oc-my-rm oc-text-truncate">
80+
<small v-text="resourceDescription" />
81+
</p>
7582
</div>
76-
<p v-if="resourceDescription" class="oc-text-left oc-my-rm oc-text-truncate">
77-
<small v-text="resourceDescription" />
78-
</p>
79-
</div>
83+
</template>
8084
</div>
8185
</template>
8286

@@ -90,6 +94,8 @@ import { useGettext } from 'vue3-gettext'
9094
import { isSpaceResource } from '@ownclouders/web-client'
9195
import { isResourceTxtFileAlmostEmpty } from '../../helpers'
9296
import { RouteLocationRaw } from 'vue-router'
97+
import { useIsVisible } from '@ownclouders/design-system/src/composables'
98+
import { customRef, ref, unref } from 'vue'
9399
94100
export default defineComponent({
95101
name: 'ResourceTile',
@@ -132,11 +138,30 @@ export default defineComponent({
132138
validator: (value: string) => {
133139
return ['large', 'xlarge', 'xxlarge', 'xxxlarge'].includes(value)
134140
}
141+
},
142+
lazy: {
143+
type: Boolean,
144+
default: false
135145
}
136146
},
137147
emits: ['click', 'contextmenu'],
138148
setup(props) {
139149
const { $gettext } = useGettext()
150+
151+
const observerTarget = customRef((track, trigger) => {
152+
let $el: HTMLElement
153+
return {
154+
get() {
155+
track()
156+
return $el
157+
},
158+
set(value) {
159+
$el = value
160+
trigger()
161+
}
162+
}
163+
})
164+
140165
const showStatusIcon = computed(() => {
141166
return props.resource.locked || props.resource.processing
142167
})
@@ -176,13 +201,23 @@ export default defineComponent({
176201
return resource.thumbnail && !isResourceTxtFileAlmostEmpty(resource)
177202
}
178203
204+
const { isVisible } = props.lazy
205+
? useIsVisible({
206+
target: observerTarget
207+
})
208+
: { isVisible: ref(true) }
209+
210+
const isHidden = computed(() => !unref(isVisible))
211+
179212
return {
180213
statusIconAttrs,
181214
showStatusIcon,
182215
tooltipLabelIcon,
183216
resourceDescription,
184217
shouldDisplayThumbnails,
185-
isProjectSpaceResource
218+
isProjectSpaceResource,
219+
isHidden,
220+
observerTarget
186221
}
187222
}
188223
})
@@ -306,5 +341,36 @@ export default defineComponent({
306341
max-width: 70%;
307342
overflow: hidden;
308343
}
344+
345+
&-lazy-shimmer {
346+
height: 120px;
347+
opacity: 0.2;
348+
position: relative;
349+
overflow: hidden;
350+
}
351+
352+
&-lazy-shimmer::after {
353+
animation: shimmer 2s infinite;
354+
background-image: linear-gradient(
355+
90deg,
356+
rgba(#4c5f79, 0) 0,
357+
rgba(#4c5f79, 0.2) 20%,
358+
rgba(#4c5f79, 0.5) 60%,
359+
rgba(#4c5f79, 0)
360+
);
361+
bottom: 0;
362+
content: '';
363+
left: 0;
364+
position: absolute;
365+
right: 0;
366+
top: 0;
367+
transform: translateX(-100%);
368+
}
369+
370+
@keyframes shimmer {
371+
100% {
372+
transform: translateX(100%);
373+
}
374+
}
309375
}
310376
</style>

packages/web-pkg/src/components/FilesList/ResourceTiles.vue

+5-2
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
:is-extension-displayed="areFileExtensionsShown"
5151
:resource-icon-size="resourceIconSize"
5252
:draggable="dragDrop"
53+
:lazy="lazy"
5354
@vue:mounted="
5455
$emit('rowMounted', resource, tileRefs.tiles[resource.id], ImageDimension.Tile)
5556
"
@@ -154,7 +155,6 @@ import {
154155
FolderViewModeConstants,
155156
SortDir,
156157
SortField,
157-
useMessages,
158158
useResourceRouteResolver,
159159
useTileSize,
160160
useResourcesStore,
@@ -220,11 +220,14 @@ export default defineComponent({
220220
dragDrop: {
221221
type: Boolean,
222222
default: false
223+
},
224+
lazy: {
225+
type: Boolean,
226+
default: true
223227
}
224228
},
225229
emits: ['fileClick', 'fileDropped', 'rowMounted', 'sort', 'update:selectedIds'],
226230
setup(props, context) {
227-
const { showMessage } = useMessages()
228231
const { $gettext } = useGettext()
229232
const resourcesStore = useResourcesStore()
230233
const { getDefaultAction } = useFileActions()

packages/web-pkg/tests/unit/components/FilesList/ResourceTiles.spec.ts

+1
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ describe('ResourceTiles component', () => {
266266
return {
267267
wrapper: mount(ResourceTiles, {
268268
props: {
269+
lazy: false,
269270
viewSize: 1,
270271
...props
271272
},

pnpm-lock.yaml

+9-9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)