diff --git a/frontend/src/components/assets/AssetCard.vue b/frontend/src/components/assets/AssetCard.vue
index a80a0ce..21669fe 100644
--- a/frontend/src/components/assets/AssetCard.vue
+++ b/frontend/src/components/assets/AssetCard.vue
@@ -12,6 +12,8 @@
:src="asset.thumbnail_url"
alt=""
draggable="false"
+ loading="lazy"
+ decoding="async"
class="size-full object-cover"
/>
diff --git a/frontend/src/components/common/FileTypeIcon.vue b/frontend/src/components/common/FileTypeIcon.vue
index 5e94c65..6f4e720 100644
--- a/frontend/src/components/common/FileTypeIcon.vue
+++ b/frontend/src/components/common/FileTypeIcon.vue
@@ -7,7 +7,14 @@
'grid place-items-center',
]"
>
-
![]()
+
diff --git a/frontend/src/components/projects/useProjectBrowser.ts b/frontend/src/components/projects/useProjectBrowser.ts
index 457e8a2..b54fb9e 100644
--- a/frontend/src/components/projects/useProjectBrowser.ts
+++ b/frontend/src/components/projects/useProjectBrowser.ts
@@ -40,6 +40,8 @@ export function useProjectBrowser(
const tag = ref(null)
const sort = ref({ field: 'creation', order: 'desc' })
const limit = ref(PAGE_SIZE)
+ const loadingMore = ref(false)
+ const reachedEnd = ref(false)
const selection = ref([])
const preview = ref<{ asset: Asset; url: string } | null>(null)
const view = ref<'grid' | 'list'>(storedView())
@@ -141,13 +143,21 @@ export function useProjectBrowser(
}
return counts
})
- const hasMore = computed(() => assets.value.length < total.value)
+ const hasMore = computed(() => !reachedEnd.value && assets.value.length < total.value)
const hasProcessing = computed(() => assets.value.some((asset) => asset.status === 'Processing'))
watch(searchInput, (value) => {
clearTimeout(debounceTimer)
debounceTimer = setTimeout(() => (search.value = value.trim()), 250)
})
+ watch(
+ () => assetsCall.loading,
+ (loading) => {
+ if (loading) return
+ loadingMore.value = false
+ reachedEnd.value = assets.value.length < limit.value
+ },
+ )
watch([currentProject, currentFolder, category, tag, search, sort], resetList, { deep: true })
watch(view, (value) => localStorage.setItem('vms_asset_view', value))
watch(hasProcessing, configurePolling, { immediate: true })
@@ -303,9 +313,17 @@ export function useProjectBrowser(
function resetList() {
limit.value = PAGE_SIZE
+ loadingMore.value = false
+ reachedEnd.value = false
selection.value = []
}
+ function loadMore() {
+ if (assetsCall.loading || !hasMore.value) return
+ loadingMore.value = true
+ limit.value += PAGE_SIZE
+ }
+
return {
project,
folders,
@@ -322,6 +340,7 @@ export function useProjectBrowser(
folderCounts,
selectedAssets,
hasMore,
+ loadingMore,
searchInput,
category,
tag,
@@ -333,14 +352,13 @@ export function useProjectBrowser(
hasNextPreview,
showPreviousPreview: () => stepPreview(-1),
showNextPreview: () => stepPreview(1),
- limit,
openAsset,
moveAssets,
moveFolder,
deleteFolder,
reloadAssets,
reloadAll,
- loadMore: () => (limit.value += PAGE_SIZE),
+ loadMore,
}
}
diff --git a/frontend/src/composables/useInfiniteScroll.ts b/frontend/src/composables/useInfiniteScroll.ts
new file mode 100644
index 0000000..fdc737f
--- /dev/null
+++ b/frontend/src/composables/useInfiniteScroll.ts
@@ -0,0 +1,40 @@
+import { onScopeDispose, watch, type Ref } from 'vue'
+
+export function useInfiniteScroll(
+ target: Ref,
+ busy: Ref,
+ canLoad: () => boolean,
+ onLoadMore: () => void,
+) {
+ let observer: IntersectionObserver | undefined
+ let intersecting = false
+
+ function maybeLoad() {
+ if (intersecting && !busy.value && canLoad()) onLoadMore()
+ }
+
+ watch(
+ target,
+ (el) => {
+ observer?.disconnect()
+ observer = undefined
+ intersecting = false
+ if (!el) return
+ observer = new IntersectionObserver(
+ (entries) => {
+ intersecting = entries[entries.length - 1]?.isIntersecting ?? false
+ maybeLoad()
+ },
+ { rootMargin: '600px 0px' },
+ )
+ observer.observe(el)
+ },
+ { immediate: true },
+ )
+
+ watch(busy, (now, before) => {
+ if (before && !now) maybeLoad()
+ })
+
+ onScopeDispose(() => observer?.disconnect())
+}
diff --git a/frontend/src/pages/ProjectDetailPage.vue b/frontend/src/pages/ProjectDetailPage.vue
index c909643..ca0407d 100644
--- a/frontend/src/pages/ProjectDetailPage.vue
+++ b/frontend/src/pages/ProjectDetailPage.vue
@@ -174,13 +174,9 @@
@drop-assets="(names, target) => moveAssets(names, target)"
@drop-folder="(name, target) => moveFolder(name, target)"
/>
-