Skip to content

Commit

Permalink
feat(perf): improve preview loading
Browse files Browse the repository at this point in the history
* Adds a limit of parallel preview requests to prevent the server from being overrun with requests
* Cancels ongoing and queued preview requests when they are aborted (e.g. when leaving the page)
* Adds lazy loading for space images
* Prevents the image resource from being fetched for each space
* Uses caching for space images

Also introduces a global `loadPreview` method and removes the visibility observers in each view. Previews are now loaded event-based instead, which should result in a better performance overall.
  • Loading branch information
Jannik Stehle committed Sep 24, 2024
1 parent 80a7b1e commit a28b127
Show file tree
Hide file tree
Showing 26 changed files with 559 additions and 452 deletions.
6 changes: 6 additions & 0 deletions changelog/unreleased/enhancement-preview-loading
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Enhancement: Preview loading performance

We've improved the performance of loading previews, especially for spaces.

https://github.com/owncloud/web/pull/11631
https://github.com/owncloud/ocis/issues/10054
4 changes: 3 additions & 1 deletion packages/design-system/src/components/OcTable/OcTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
@dragleave.prevent="dropRowStyling(itemDomSelector(item), true, $event)"
@mouseleave="dropRowStyling(itemDomSelector(item), true, $event)"
@dragover="dragOver($event)"
@item-visible="$emit('itemVisible', item)"
>
<oc-td
v-for="(field, tdIndex) in fields"
Expand Down Expand Up @@ -318,7 +319,8 @@ export default defineComponent({
EVENT_TROW_MOUNTED,
EVENT_TROW_CONTEXTMENU,
EVENT_SORT,
'dropRowStyling'
'dropRowStyling',
'itemVisible'
],
setup() {
const constants = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,10 @@ export default defineComponent({
'dragenter',
'dragleave',
'dragover',
'mouseleave'
'mouseleave',
'itemVisible'
],
setup(props, ctx) {
setup(props, { emit }) {
const observerTarget = customRef((track, trigger) => {
let $el: HTMLElement
return {
Expand All @@ -65,12 +66,17 @@ export default defineComponent({
const { isVisible } = props.lazy
? useIsVisible({
...props.lazy,
target: observerTarget
target: observerTarget,
onVisibleCallback: () => emit('itemVisible')
})
: { isVisible: ref(true) }
const isHidden = computed(() => !unref(isVisible))
if (!props.lazy) {
emit('itemVisible')
}
return {
observerTarget,
isHidden,
Expand Down
10 changes: 8 additions & 2 deletions packages/design-system/src/composables/useIsVisible/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { Ref, onBeforeUnmount, ref, watch } from 'vue'
import { Ref, onBeforeUnmount, ref, unref, watch } from 'vue'

export const useIsVisible = ({
target,
mode = 'show',
rootMargin = '100px'
rootMargin = '100px',
onVisibleCallback
}: {
target: Ref<Element>
mode?: string
rootMargin?: string
onVisibleCallback?: () => void
}) => {
const isSupported = window && 'IntersectionObserver' in window
if (!isSupported) {
Expand All @@ -27,6 +29,10 @@ export const useIsVisible = ({
const isIntersecting = intersectionObserverEntries.at(-1).isIntersecting

isVisible.value = isIntersecting
if (unref(isVisible) && onVisibleCallback) {
onVisibleCallback()
}

/**
* if given mode is `showHide` we need to keep the observation alive.
*/
Expand Down
51 changes: 13 additions & 38 deletions packages/web-app-files/src/components/Search/List.vue
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,13 @@
:fields-displayed="['name', 'size', 'tags', 'mdate']"
:resource-dom-selector="resourceDomSelector"
@file-click="triggerDefaultAction"
@row-mounted="rowMounted"
@item-visible="
loadPreview({
space: getMatchingSpace($event),
resource: $event,
ignoreAlmostEmptyTxtFiles: true
})
"
@sort="handleSort"
>
<template #additionalResourceContent="{ resource }">
Expand Down Expand Up @@ -155,16 +161,12 @@ import {
useConfigStore,
useResourcesStore
} from '@ownclouders/web-pkg'
import { VisibilityObserver } from '@ownclouders/web-pkg'
import { ImageDimension } from '@ownclouders/web-pkg'
import { NoContentMessage } from '@ownclouders/web-pkg'
import { ResourceTable } from '@ownclouders/web-pkg'
import { ContextActions, FileSideBar } from '@ownclouders/web-pkg'
import { debounce } from 'lodash-es'
import { useGettext } from 'vue3-gettext'
import { AppBar } from '@ownclouders/web-pkg'
import {
ComponentPublicInstance,
computed,
defineComponent,
nextTick,
Expand All @@ -188,7 +190,8 @@ import {
useGetMatchingSpace,
useRoute,
useRouteQuery,
useRouter
useRouter,
useLoadPreview
} from '@ownclouders/web-pkg'
import { onBeforeRouteLeave } from 'vue-router'
import { useTask } from 'vue-concurrency'
Expand All @@ -205,8 +208,6 @@ import {
import { extractDomSelector } from '@ownclouders/web-client'
import { storeToRefs } from 'pinia'
const visibilityObserver = new VisibilityObserver()
type Tag = {
id: string
label: string
Expand Down Expand Up @@ -254,7 +255,7 @@ export default defineComponent({
const { getMatchingSpace } = useGetMatchingSpace()
const resourcesStore = useResourcesStore()
const { initResourceList, clearResourceList, updateResourceField } = resourcesStore
const { initResourceList, clearResourceList } = resourcesStore
const { totalResourcesCount } = storeToRefs(resourcesStore)
const configStore = useConfigStore()
Expand All @@ -265,6 +266,7 @@ export default defineComponent({
const doUseScope = useRouteQuery('useScope')
const resourcesView = useResourcesViewDefaults<Resource, any, any[]>()
const { loadPreview } = useLoadPreview(resourcesView.viewMode)
const keyActions = useKeyboardActions()
useKeyboardTableNavigation(keyActions, resourcesView.paginatedResources, resourcesView.viewMode)
useKeyboardTableMouseActions(keyActions, resourcesView.viewMode)
Expand Down Expand Up @@ -486,8 +488,8 @@ export default defineComponent({
resourceDomSelector,
initResourceList,
clearResourceList,
updateResourceField,
totalResourcesCount
totalResourcesCount,
loadPreview
}
},
computed: {
Expand Down Expand Up @@ -537,33 +539,6 @@ export default defineComponent({
this.scrollToResourceFromRoute(this.paginatedResources, 'files-app-bar')
}
}
},
beforeUnmount() {
visibilityObserver.disconnect()
},
methods: {
rowMounted(resource: Resource, component: ComponentPublicInstance<unknown>) {
const loadPreview = async () => {
const preview = await this.$previewService.loadPreview(
{
space: this.getMatchingSpace(resource),
resource,
dimensions: ImageDimension.Thumbnail
},
true
)
if (preview) {
this.updateResourceField({ id: resource.id, field: 'thumbnail', value: preview })
}
}
const debounced = debounce(({ unobserve }) => {
unobserve()
loadPreview()
}, 250)
visibilityObserver.observe(component.$el, { onEnter: debounced, onExit: debounced.cancel })
}
}
})
</script>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,13 @@
:sort-dir="sortDir"
:grouping-settings="groupingSettings"
@file-click="triggerDefaultAction"
@row-mounted="rowMounted"
@item-visible="
loadPreview({
space: getMatchingSpace($event),
resource: $event,
ignoreAlmostEmptyTxtFiles: true
})
"
@sort="sortHandler"
>
<template #syncEnabled="{ resource }">
Expand Down Expand Up @@ -104,12 +110,10 @@ import {
useConfigStore,
useFileActions,
useFileActionsToggleHideShare,
useLoadPreview,
useResourcesStore
} from '@ownclouders/web-pkg'
import { ComponentPublicInstance, computed, defineComponent, PropType, unref } from 'vue'
import { debounce } from 'lodash-es'
import { ImageDimension } from '@ownclouders/web-pkg'
import { VisibilityObserver } from '@ownclouders/web-pkg'
import { computed, defineComponent, PropType, unref } from 'vue'
import { SortDir, useGetMatchingSpace } from '@ownclouders/web-pkg'
import { createLocationSpaces } from '@ownclouders/web-pkg'
import ListInfo from '../../components/FilesList/ListInfo.vue'
Expand All @@ -121,8 +125,6 @@ import { RouteLocationNamedRaw } from 'vue-router'
import { CreateTargetRouteOptions } from '@ownclouders/web-pkg'
import { createFileRouteOptions } from '@ownclouders/web-pkg'
const visibilityObserver = new VisibilityObserver()
export default defineComponent({
components: {
ResourceTable,
Expand Down Expand Up @@ -197,6 +199,7 @@ export default defineComponent({
const capabilityStore = useCapabilityStore()
const configStore = useConfigStore()
const { getMatchingSpace } = useGetMatchingSpace()
const { loadPreview } = useLoadPreview()
const { triggerDefaultAction } = useFileActions()
const { actions: hideShareActions } = useFileActionsToggleHideShare()
Expand Down Expand Up @@ -229,7 +232,8 @@ export default defineComponent({
getMatchingSpace,
updateResourceField,
isExternalShare,
ShareTypes
ShareTypes,
loadPreview
}
},
Expand All @@ -254,35 +258,7 @@ export default defineComponent({
return this.items.slice(0, this.showMoreToggleCount)
}
},
beforeUnmount() {
visibilityObserver.disconnect()
},
methods: {
rowMounted(resource: IncomingShareResource, component: ComponentPublicInstance<unknown>) {
const loadPreview = async () => {
const preview = await this.$previewService.loadPreview(
{
space: this.getMatchingSpace(resource),
resource,
dimensions: ImageDimension.Thumbnail
},
true
)
if (preview) {
this.updateResourceField({ id: resource.id, field: 'thumbnail', value: preview })
}
}
const debounced = debounce(({ unobserve }) => {
unobserve()
loadPreview()
}, 250)
visibilityObserver.observe(component.$el, {
onEnter: debounced,
onExit: debounced.cancel
})
},
toggleShowMore() {
this.showMore = !this.showMore
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,8 @@ import {
useConfigStore,
useResourcesStore,
formatDateFromJSDate,
useResourceContents
useResourceContents,
useLoadPreview
} from '@ownclouders/web-pkg'
import upperFirst from 'lodash-es/upperFirst'
import {
Expand All @@ -147,7 +148,7 @@ import {
isTrashResource,
ShareTypes
} from '@ownclouders/web-client'
import { usePreviewService, useGetMatchingSpace } from '@ownclouders/web-pkg'
import { useGetMatchingSpace } from '@ownclouders/web-pkg'
import { getIndicators } from '@ownclouders/web-pkg'
import {
formatDateFromHTTP,
Expand All @@ -157,7 +158,6 @@ import {
import { eventBus } from '@ownclouders/web-pkg'
import { SideBarEventTopics } from '@ownclouders/web-pkg'
import { Resource, SpaceResource } from '@ownclouders/web-client'
import { useTask } from 'vue-concurrency'
import { useGettext } from 'vue3-gettext'
import { getSharedAncestorRoute } from '@ownclouders/web-pkg'
import { ResourceIcon } from '@ownclouders/web-pkg'
Expand Down Expand Up @@ -187,6 +187,7 @@ export default defineComponent({
const capabilityStore = useCapabilityStore()
const { getMatchingSpace } = useGetMatchingSpace()
const { resourceContentsText } = useResourceContents({ showSizeInformation: false })
const { loadPreview, previewsLoading } = useLoadPreview()
const language = useGettext()
const { $gettext, current: currentLanguage } = language
Expand All @@ -200,35 +201,12 @@ export default defineComponent({
const versions = inject<Ref<Resource[]>>('versions')
const space = inject<Ref<SpaceResource>>('space')
const previewService = usePreviewService()
const preview = ref(undefined)
const preview = ref<string>(undefined)
const authStore = useAuthStore()
const { publicLinkContextReady } = storeToRefs(authStore)
const isPreviewEnabled = computed(() => {
if (unref(resource).isFolder) {
return false
}
return props.previewEnabled
})
const loadPreviewTask = useTask(function* (signal, resource) {
if (!unref(isPreviewEnabled)) {
preview.value = undefined
return
}
preview.value = yield previewService.loadPreview({
space: unref(space),
resource,
dimensions: ImageDimension.Preview
})
}).restartable()
const isPreviewLoading = computed(() => {
if (!unref(isPreviewEnabled)) {
return false
}
return loadPreviewTask.isRunning || !loadPreviewTask.last
})
const isPreviewLoading = computed(() => props.previewEnabled && unref(previewsLoading))
const sharedAncestor = computed(() => {
return Object.values(unref(ancestorMetaData)).find(
Expand Down Expand Up @@ -370,10 +348,16 @@ export default defineComponent({
}
watch(
resource,
() => {
() => unref(resource).id,
async () => {
if (unref(resource)) {
loadPreviewTask.perform(unref(resource))
preview.value = await loadPreview({
space: unref(space),
resource: unref(resource),
dimensions: ImageDimension.Preview,
cancelRunning: true,
updateStore: false
})
}
},
{ immediate: true }
Expand Down
Loading

0 comments on commit a28b127

Please sign in to comment.