success pt. 5 search box
This commit is contained in:
@@ -1,28 +1,68 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { db, ensureSeeded, getAnimeList } from "$lib/db/client-db";
|
||||
import {
|
||||
db,
|
||||
ensureSeeded,
|
||||
getAnimeList,
|
||||
searchAnimeByName,
|
||||
} from "$lib/db/client-db";
|
||||
|
||||
let status = $state<"idle" | "loading" | "ready" | "error">("idle");
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
let query = $state("");
|
||||
let isSearching = $state(false);
|
||||
|
||||
type AnimeItem = Awaited<ReturnType<typeof getAnimeList>>[number];
|
||||
let anime = $state<AnimeItem[]>([]);
|
||||
|
||||
async function loadInitial() {
|
||||
status = "loading";
|
||||
error = null;
|
||||
|
||||
try {
|
||||
await ensureSeeded();
|
||||
anime = await getAnimeList(db, 20);
|
||||
status = "ready";
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : String(e);
|
||||
status = "error";
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
(async () => {
|
||||
status = "loading";
|
||||
error = null;
|
||||
void loadInitial();
|
||||
});
|
||||
|
||||
// Debounced search
|
||||
let debounceTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
function scheduleSearch(nextQuery: string) {
|
||||
query = nextQuery;
|
||||
|
||||
if (debounceTimer) clearTimeout(debounceTimer);
|
||||
|
||||
debounceTimer = setTimeout(async () => {
|
||||
if (status !== "ready") return;
|
||||
|
||||
const q = query.trim();
|
||||
|
||||
try {
|
||||
await ensureSeeded();
|
||||
anime = await getAnimeList(db, 20);
|
||||
status = "ready";
|
||||
isSearching = true;
|
||||
|
||||
if (!q) {
|
||||
anime = await getAnimeList(db, 20);
|
||||
} else {
|
||||
anime = await searchAnimeByName(db, q, 20);
|
||||
}
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : String(e);
|
||||
status = "error";
|
||||
} finally {
|
||||
isSearching = false;
|
||||
}
|
||||
})();
|
||||
});
|
||||
}, 200);
|
||||
}
|
||||
|
||||
function seasonName(seasonId: number) {
|
||||
switch (seasonId) {
|
||||
@@ -47,7 +87,28 @@
|
||||
{:else if status === "error"}
|
||||
<p class="mt-3 text-sm text-red-600">Error: {error}</p>
|
||||
{:else if status === "ready"}
|
||||
<p class="mt-3 text-sm text-muted-foreground">Showing {anime.length} anime</p>
|
||||
<div class="mt-3 flex flex-col gap-2">
|
||||
<label class="text-sm text-muted-foreground" for="anime-search">
|
||||
Search anime
|
||||
</label>
|
||||
<input
|
||||
id="anime-search"
|
||||
class="rounded border px-3 py-2 text-sm"
|
||||
placeholder="Type to search by name…"
|
||||
value={query}
|
||||
oninput={(e) =>
|
||||
scheduleSearch((e.currentTarget as HTMLInputElement).value)}
|
||||
autocomplete="off"
|
||||
spellcheck={false}
|
||||
/>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
{#if isSearching}
|
||||
Searching…
|
||||
{:else}
|
||||
Showing {anime.length} anime
|
||||
{/if}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ul class="mt-4 space-y-2">
|
||||
{#each anime as a (a.annId)}
|
||||
|
||||
Reference in New Issue
Block a user