From f94ccc01c62d8e823697dea78e716d64ec82f872 Mon Sep 17 00:00:00 2001 From: Leo Vasanko Date: Sat, 31 Jan 2026 22:17:05 +0000 Subject: [PATCH] Optimizing the search faster; don't trigger full re render by URL changes. --- frontend/src/components/HeaderMain.vue | 12 +++++++++-- frontend/src/repositories/Document.ts | 6 ++---- frontend/src/stores/main.ts | 30 +++++++++++++++----------- frontend/src/views/ExplorerView.vue | 17 ++++++++++----- frontend/src/workers/searchWorker.ts | 10 +++++---- 5 files changed, 48 insertions(+), 27 deletions(-) diff --git a/frontend/src/components/HeaderMain.vue b/frontend/src/components/HeaderMain.vue index 6b066f8..51a51ab 100644 --- a/frontend/src/components/HeaderMain.vue +++ b/frontend/src/components/HeaderMain.vue @@ -51,14 +51,22 @@ const closeSearch = (ev: Event) => { breadcrumb.focus() updateSearch(ev) } + const updateSearch = (ev: Event) => { const q = (ev.target as HTMLInputElement).value let p = props.path.join('/') p = p ? `/${p}` : '' const url = q ? `${p}//${q}` : (p || '/') const u = url.replaceAll('?', '%3F').replaceAll('#', '%23') - if (!props.query && q) router.push(u) - else router.replace(u) + + // Start search immediately via store (worker handles it async) + store.search(q, props.path.join('/')) + + // Update route in next frame to keep typing responsive + requestAnimationFrame(() => { + if (!props.query && q) router.push(u) + else router.replace(u) + }) } const toggleSearchInput = (ev: Event) => { showSearchInput.value = !showSearchInput.value diff --git a/frontend/src/repositories/Document.ts b/frontend/src/repositories/Document.ts index 7c736aa..1d11b58 100644 --- a/frontend/src/repositories/Document.ts +++ b/frontend/src/repositories/Document.ts @@ -1,4 +1,4 @@ -import { formatSize, formatUnixDate, haystackFormat } from "@/utils" +import { formatSize, formatUnixDate } from "@/utils" export type FUID = string @@ -16,7 +16,6 @@ export class Doc { public key: FUID = "" public size: number = 0 public mtime: number = 0 - public haystack: string = "" public dir: boolean = false /** @internal Use the name getter/setter instead */ public _name: string = "" @@ -24,13 +23,12 @@ export class Doc { constructor(props: Partial = {}) { const { name, ...rest } = props Object.assign(this, rest) - if (name) this.name = name // Use setter for validation + if (name) this._name = name // Skip validation/haystack for bulk loading } get name() { return this._name } set name(name: string) { if (name.includes('/') || name.startsWith('.')) throw Error(`Invalid name: ${name}`) this._name = name - this.haystack = haystackFormat(name) } get sizedisp(): string { return formatSize(this.size) } get modified(): string { return formatUnixDate(this.mtime) } diff --git a/frontend/src/stores/main.ts b/frontend/src/stores/main.ts index e15457c..b3f3865 100644 --- a/frontend/src/stores/main.ts +++ b/frontend/src/stores/main.ts @@ -9,10 +9,26 @@ import SearchWorker from '@/workers/searchWorker?worker' // Singleton search worker instance let searchWorker: Worker | null = null let searchId = 0 +let searchStore: ReturnType | null = null function getSearchWorker(): Worker { if (!searchWorker) { searchWorker = new SearchWorker() + // Set up message handler once + searchWorker.onmessage = (e) => { + if (!searchStore || e.data.id !== searchId) return // Stale result + + // Convert plain data back to Doc instances (constructor is now lightweight) + const docs = [] + for (const d of e.data.docs) { + docs.push(new Doc(d)) + } + searchStore.searchResults = docs + + if (e.data.done) { + searchStore.searchLoading = false + } + } } return searchWorker } @@ -24,6 +40,7 @@ export const useMainStore = defineStore('main', { query: '' as string, searchResults: [] as Doc[], searchLoading: false, + _searchRouteTimer: null as ReturnType | null, fileExplorer: null as any, error: '' as string, // Permanent status message (e.g., "Reconnecting...") toast: '' as string, // Temporary toast (auto-dismisses) @@ -116,6 +133,7 @@ export const useMainStore = defineStore('main', { search(query: string, loc: string) { const worker = getSearchWorker() const id = ++searchId + searchStore = this // Store reference for worker callback if (!query) { this.searchResults = [] @@ -124,18 +142,6 @@ export const useMainStore = defineStore('main', { } this.searchLoading = true - - worker.onmessage = (e) => { - if (e.data.id !== searchId) return // Stale result - - // Convert plain data back to Doc instances - this.searchResults = e.data.docs.map((d: any) => new Doc(d)) - - if (e.data.done) { - this.searchLoading = false - } - } - worker.postMessage({ type: 'search', query, loc, id }) }, login(username: string, privileged: boolean) { diff --git a/frontend/src/views/ExplorerView.vue b/frontend/src/views/ExplorerView.vue index 5131575..906fcdd 100644 --- a/frontend/src/views/ExplorerView.vue +++ b/frontend/src/views/ExplorerView.vue @@ -2,14 +2,14 @@ @@ -20,7 +20,6 @@