From d7aae07af0547c9f96886f8f5038be2a5a2a8dc6 Mon Sep 17 00:00:00 2001 From: Leo Vasanko Date: Wed, 4 Feb 2026 00:18:28 +0000 Subject: [PATCH] Prebuild lookup structures to reduce UI lag on very large file lists. --- frontend/src/components/UploadButton.vue | 2 +- frontend/src/stores/main.ts | 18 ++++++++++++++++++ frontend/src/utils/fileutil.ts | 3 +-- frontend/src/views/ExplorerView.vue | 2 +- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/UploadButton.vue b/frontend/src/components/UploadButton.vue index 64bf732..645a6d6 100644 --- a/frontend/src/components/UploadButton.vue +++ b/frontend/src/components/UploadButton.vue @@ -98,7 +98,7 @@ const uploadCloudFiles = (files: CloudFile[]) => { files.sort((a, b) => collator.compare(a.cloudName, b.cloudName)) // Optimistic update: ghost folders and files const now = Math.floor(Date.now() / 1000) - const byPath = new Map(store.document.map(d => [d.loc ? `${d.loc}/${d.name}` : d.name, d])) + const byPath = store.docsByPath const added = new Set() for (const f of files) { const lastSlash = f.cloudName.lastIndexOf('/') diff --git a/frontend/src/stores/main.ts b/frontend/src/stores/main.ts index 9161729..fa53940 100644 --- a/frontend/src/stores/main.ts +++ b/frontend/src/stores/main.ts @@ -269,6 +269,24 @@ export const useMainStore = defineStore('main', { sortOrder(): SortOrder { return this.query ? this.prefs.sortFiltered : this.prefs.sortListing }, isUserLogged(): boolean { return this.user.isLoggedIn }, recentDocuments(): Doc[] { return sorted(this.document, 'modified') }, + /** Set of all full paths - for O(1) existence checks */ + pathSet(): Set { + return new Set(this.document.map(d => d.loc ? `${d.loc}/${d.name}` : d.name)) + }, + /** Map from location to documents in that folder - for O(1) folder listing */ + docsByLoc(): Map { + const map = new Map() + for (const doc of this.document) { + const arr = map.get(doc.loc) + if (arr) arr.push(doc) + else map.set(doc.loc, [doc]) + } + return map + }, + /** Map from full path to Doc - for O(1) path lookup */ + docsByPath(): Map { + return new Map(this.document.map(d => [d.loc ? `${d.loc}/${d.name}` : d.name, d])) + }, selectedFiles(): SelectedItems { const selected = this.selected const found = new Set() diff --git a/frontend/src/utils/fileutil.ts b/frontend/src/utils/fileutil.ts index a5e91e0..b3cdb40 100644 --- a/frontend/src/utils/fileutil.ts +++ b/frontend/src/utils/fileutil.ts @@ -3,8 +3,7 @@ import { useMainStore } from '@/stores/main' export const exists = (path: string[]) => { const store = useMainStore() - const p = path.join('/') - return store.document.some(doc => (doc.loc ? `${doc.loc}/${doc.name}` : doc.name) === p) + return store.pathSet.has(path.join('/')) } /** Strip file extension intelligently (handles .tar.gz, name.with.dots.pdf, etc.) */ diff --git a/frontend/src/views/ExplorerView.vue b/frontend/src/views/ExplorerView.vue index 8017819..ae76ae1 100644 --- a/frontend/src/views/ExplorerView.vue +++ b/frontend/src/views/ExplorerView.vue @@ -51,7 +51,7 @@ const documents = computed(() => { // List the current location (no search) if (!query) return sorted( - store.document.filter(doc => doc.loc === loc), + store.docsByLoc.get(loc) ?? [], store.prefs.sortListing, )