Layout and search fix
🏗️ Build and Test JellyTau / Run Tests (push) Failing after 2m4s
🏗️ Build and Test JellyTau / Android Compile Check (push) Has been skipped
Traceability Validation / Check Requirement Traces (push) Successful in 23s
Build & Release / Run Tests (push) Failing after 2m45s
Build & Release / Build Linux (push) Has been skipped
Build & Release / Build Android (push) Has been skipped
Build & Release / Create Release (push) Has been skipped
🏗️ Build and Test JellyTau / Run Tests (push) Failing after 2m4s
🏗️ Build and Test JellyTau / Android Compile Check (push) Has been skipped
Traceability Validation / Check Requirement Traces (push) Successful in 23s
Build & Release / Run Tests (push) Failing after 2m45s
Build & Release / Build Linux (push) Has been skipped
Build & Release / Build Android (push) Has been skipped
Build & Release / Create Release (push) Has been skipped
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
<!-- TRACES: UR-007, UR-029, UR-030 | DR-007, DR-032, DR-033 -->
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { onMount, onDestroy } from "svelte";
|
||||
import { listen, type UnlistenFn } from "@tauri-apps/api/event";
|
||||
import { goto } from "$app/navigation";
|
||||
import { navigateBack } from "$lib/utils/navigation";
|
||||
import { currentLibrary } from "$lib/stores/library";
|
||||
@@ -13,7 +14,7 @@
|
||||
import BackButton from "$lib/components/common/BackButton.svelte";
|
||||
import ResultsCounter from "$lib/components/common/ResultsCounter.svelte";
|
||||
import { useServerReachabilityReload } from "$lib/composables/useServerReachabilityReload";
|
||||
import type { MediaItem, Library, ItemType } from "$lib/api/types";
|
||||
import type { MediaItem, Library, ItemType, SearchResult } from "$lib/api/types";
|
||||
import LibraryGrid from "./LibraryGrid.svelte";
|
||||
import TrackList from "./TrackList.svelte";
|
||||
import AlphabetScrollBar from "./AlphabetScrollBar.svelte";
|
||||
@@ -54,6 +55,31 @@
|
||||
let searchTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||
let initialLoadDone = false;
|
||||
|
||||
/**
|
||||
* Payload of the backend `search-event` (mirrors Rust `SearchUpdateEvent`).
|
||||
* `repo.search()` resolves instantly with cache-only (downloaded) results;
|
||||
* the merged cache+server union arrives later via this event.
|
||||
*/
|
||||
interface SearchUpdateEvent {
|
||||
requestId: number;
|
||||
result: SearchResult;
|
||||
}
|
||||
|
||||
// Monotonic id identifying the latest search request. The deferred
|
||||
// `search-event` is only applied when its requestId still matches, so
|
||||
// out-of-order / superseded server results never clobber fresher ones.
|
||||
let searchRequestId = 0;
|
||||
let unlistenSearch: UnlistenFn | null = null;
|
||||
|
||||
async function ensureSearchListener() {
|
||||
if (unlistenSearch) return;
|
||||
unlistenSearch = await listen<SearchUpdateEvent>("search-event", (event) => {
|
||||
const { requestId, result } = event.payload;
|
||||
if (requestId !== searchRequestId) return;
|
||||
items = excludePodcasts(result.items);
|
||||
});
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
sortBy = config.defaultSort;
|
||||
});
|
||||
@@ -82,12 +108,26 @@
|
||||
// Use backend search if search query is provided, otherwise use getItems with sort
|
||||
// HACK: excludePodcasts drops the "Podcasts" folder stored in the music library.
|
||||
if (debouncedSearchQuery.trim()) {
|
||||
const result = await repo.search(debouncedSearchQuery, {
|
||||
includeItemTypes: [config.itemType],
|
||||
limit: 10000,
|
||||
});
|
||||
items = excludePodcasts(result.items);
|
||||
// Phase 1: instant cache-only (downloaded) results. The merged
|
||||
// cache+server union arrives later via the `search-event` listener,
|
||||
// tagged with this requestId so superseded queries are ignored.
|
||||
await ensureSearchListener();
|
||||
const requestId = ++searchRequestId;
|
||||
const result = await repo.search(
|
||||
debouncedSearchQuery,
|
||||
{
|
||||
includeItemTypes: [config.itemType],
|
||||
limit: 10000,
|
||||
},
|
||||
requestId
|
||||
);
|
||||
// Only apply if this is still the active query.
|
||||
if (requestId === searchRequestId) {
|
||||
items = excludePodcasts(result.items);
|
||||
}
|
||||
} else {
|
||||
// Leaving search — invalidate any in-flight server results.
|
||||
searchRequestId++;
|
||||
const result = await repo.getItems($currentLibrary.id, {
|
||||
includeItemTypes: [config.itemType],
|
||||
sortBy,
|
||||
@@ -120,6 +160,11 @@
|
||||
}, 300);
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
if (unlistenSearch) unlistenSearch();
|
||||
if (searchTimeout) clearTimeout(searchTimeout);
|
||||
});
|
||||
|
||||
function handleSort(newSort: string) {
|
||||
sortBy = newSort;
|
||||
loadItems();
|
||||
|
||||
Reference in New Issue
Block a user