Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add lazy loading for tiles view #11508

Merged
merged 6 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Enhancement: Improve tiles view performance

We've made some changes to the tiles view to improve performance.
So that users can see their files and folders faster, when working with a lot of files.

https://github.com/owncloud/web/pull/11508
https://github.com/owncloud/web/issues/11480
196 changes: 131 additions & 65 deletions packages/web-pkg/src/components/FilesList/ResourceTile.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<template>
<div
ref="observerTarget"
class="oc-tile-card oc-card oc-card-default oc-rounded"
:data-item-id="resource.id"
:class="{
Expand All @@ -9,74 +10,77 @@
}"
@contextmenu="$emit('contextmenu', $event)"
>
<resource-link
class="oc-card-media-top oc-flex oc-flex-center oc-flex-middle oc-m-rm"
:resource="resource"
:link="resourceRoute"
:is-resource-clickable="isResourceClickable"
tabindex="-1"
@click="$emit('click')"
>
<div class="oc-tile-card-selection">
<slot name="selection" :item="resource" />
</div>
<oc-tag
v-if="isResourceDisabled && isProjectSpaceResource(resource)"
class="resource-disabled-indicator oc-position-absolute"
type="span"
>
<span v-text="$gettext('Disabled')" />
</oc-tag>
<div
v-oc-tooltip="tooltipLabelIcon"
class="oc-tile-card-preview oc-flex oc-flex-middle oc-flex-center"
:aria-label="tooltipLabelIcon"
<div v-if="isHidden" class="oc-tile-card-lazy-shimmer"></div>
<template v-else>
<resource-link
class="oc-card-media-top oc-flex oc-flex-center oc-flex-middle oc-m-rm"
:resource="resource"
:link="resourceRoute"
:is-resource-clickable="isResourceClickable"
tabindex="-1"
@click="$emit('click')"
>
<div class="oc-tile-card-hover"></div>
<slot name="imageField" :item="resource">
<oc-img
v-if="shouldDisplayThumbnails(resource)"
class="tile-preview"
:src="resource.thumbnail"
/>
<resource-icon
v-else
:resource="resource"
:size="resourceIconSize"
class="tile-default-image oc-pt-xs"
>
<template v-if="showStatusIcon" #status>
<oc-icon v-bind="statusIconAttrs" size="xsmall" />
</template>
</resource-icon>
</slot>
</div>
</resource-link>
<div class="oc-card-body oc-p-s">
<div class="oc-flex oc-flex-between oc-flex-middle">
<div class="oc-flex oc-flex-middle oc-text-truncate resource-name-wrapper">
<resource-list-item
:resource="resource"
:is-icon-displayed="false"
:is-extension-displayed="isExtensionDisplayed"
:is-resource-clickable="isResourceClickable"
:link="resourceRoute"
@click="$emit('click')"
/>
<div class="oc-tile-card-selection">
<slot name="selection" :item="resource" />
</div>
<oc-tag
v-if="isResourceDisabled && isProjectSpaceResource(resource)"
class="resource-disabled-indicator oc-position-absolute"
type="span"
>
<span v-text="$gettext('Disabled')" />
</oc-tag>
<div
v-oc-tooltip="tooltipLabelIcon"
class="oc-tile-card-preview oc-flex oc-flex-middle oc-flex-center"
:aria-label="tooltipLabelIcon"
>
<div class="oc-tile-card-hover"></div>
<slot name="imageField" :item="resource">
<oc-img
v-if="shouldDisplayThumbnails(resource)"
class="tile-preview"
:src="resource.thumbnail"
/>
<resource-icon
v-else
:resource="resource"
:size="resourceIconSize"
class="tile-default-image oc-pt-xs"
>
<template v-if="showStatusIcon" #status>
<oc-icon v-bind="statusIconAttrs" size="xsmall" />
</template>
</resource-icon>
</slot>
</div>
<div class="oc-flex oc-flex-middle">
<!-- Slot for indicators !-->
<slot name="indicators" :item="resource" class="resource-indicators" />
<!-- Slot for individual actions -->
<slot name="actions" :item="resource" />
<!-- Slot for contextmenu -->
<slot name="contextMenu" :item="resource" />
</resource-link>
<div class="oc-card-body oc-p-s">
<div class="oc-flex oc-flex-between oc-flex-middle">
<div class="oc-flex oc-flex-middle oc-text-truncate resource-name-wrapper">
<resource-list-item
:resource="resource"
:is-icon-displayed="false"
:is-extension-displayed="isExtensionDisplayed"
:is-resource-clickable="isResourceClickable"
:link="resourceRoute"
@click="$emit('click')"
/>
</div>
<div class="oc-flex oc-flex-middle">
<!-- Slot for indicators !-->
<slot name="indicators" :item="resource" class="resource-indicators" />
<!-- Slot for individual actions -->
<slot name="actions" :item="resource" />
<!-- Slot for contextmenu -->
<slot name="contextMenu" :item="resource" />
</div>
</div>
<p v-if="resourceDescription" class="oc-text-left oc-my-rm oc-text-truncate">
<small v-text="resourceDescription" />
</p>
</div>
<p v-if="resourceDescription" class="oc-text-left oc-my-rm oc-text-truncate">
<small v-text="resourceDescription" />
</p>
</div>
</template>
</div>
</template>

Expand All @@ -90,6 +94,8 @@ import { useGettext } from 'vue3-gettext'
import { isSpaceResource } from '@ownclouders/web-client'
import { isResourceTxtFileAlmostEmpty } from '../../helpers'
import { RouteLocationRaw } from 'vue-router'
import { useIsVisible } from '@ownclouders/design-system/src/composables'
import { customRef, ref, unref } from 'vue'

export default defineComponent({
name: 'ResourceTile',
Expand Down Expand Up @@ -132,11 +138,30 @@ export default defineComponent({
validator: (value: string) => {
return ['large', 'xlarge', 'xxlarge', 'xxxlarge'].includes(value)
}
},
lazy: {
type: Boolean,
default: false
}
},
emits: ['click', 'contextmenu'],
setup(props) {
const { $gettext } = useGettext()

const observerTarget = customRef((track, trigger) => {
let $el: HTMLElement
return {
get() {
track()
return $el
},
set(value) {
$el = value
trigger()
}
}
})

const showStatusIcon = computed(() => {
return props.resource.locked || props.resource.processing
})
Expand Down Expand Up @@ -176,13 +201,23 @@ export default defineComponent({
return resource.thumbnail && !isResourceTxtFileAlmostEmpty(resource)
}

const { isVisible } = props.lazy
? useIsVisible({
target: observerTarget
})
: { isVisible: ref(true) }

const isHidden = computed(() => !unref(isVisible))

return {
statusIconAttrs,
showStatusIcon,
tooltipLabelIcon,
resourceDescription,
shouldDisplayThumbnails,
isProjectSpaceResource
isProjectSpaceResource,
isHidden,
observerTarget
}
}
})
Expand Down Expand Up @@ -306,5 +341,36 @@ export default defineComponent({
max-width: 70%;
overflow: hidden;
}

&-lazy-shimmer {
height: 120px;
opacity: 0.2;
position: relative;
overflow: hidden;
}

&-lazy-shimmer::after {
animation: shimmer 2s infinite;
background-image: linear-gradient(
90deg,
rgba(#4c5f79, 0) 0,
rgba(#4c5f79, 0.2) 20%,
rgba(#4c5f79, 0.5) 60%,
rgba(#4c5f79, 0)
);
bottom: 0;
content: '';
left: 0;
position: absolute;
right: 0;
top: 0;
transform: translateX(-100%);
}

@keyframes shimmer {
100% {
transform: translateX(100%);
}
}
}
</style>
7 changes: 5 additions & 2 deletions packages/web-pkg/src/components/FilesList/ResourceTiles.vue
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
:is-extension-displayed="areFileExtensionsShown"
:resource-icon-size="resourceIconSize"
:draggable="dragDrop"
:lazy="lazy"
@vue:mounted="
$emit('rowMounted', resource, tileRefs.tiles[resource.id], ImageDimension.Tile)
"
Expand Down Expand Up @@ -154,7 +155,6 @@ import {
FolderViewModeConstants,
SortDir,
SortField,
useMessages,
useResourceRouteResolver,
useTileSize,
useResourcesStore,
Expand Down Expand Up @@ -220,11 +220,14 @@ export default defineComponent({
dragDrop: {
type: Boolean,
default: false
},
lazy: {
type: Boolean,
default: true
}
},
emits: ['fileClick', 'fileDropped', 'rowMounted', 'sort', 'update:selectedIds'],
setup(props, context) {
const { showMessage } = useMessages()
const { $gettext } = useGettext()
const resourcesStore = useResourcesStore()
const { getDefaultAction } = useFileActions()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ describe('ResourceTiles component', () => {
return {
wrapper: mount(ResourceTiles, {
props: {
lazy: false,
viewSize: 1,
...props
},
Expand Down
18 changes: 9 additions & 9 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.