Files
amqtrain/src/routes/+page.svelte

125 lines
2.7 KiB
Svelte

<script lang="ts">
import { onMount } from "svelte";
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(() => {
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 {
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) {
case 0:
return "Winter";
case 1:
return "Spring";
case 2:
return "Summer";
case 3:
return "Fall";
default:
return `Season ${seasonId}`;
}
}
</script>
<h1 class="text-2xl font-semibold">AMQ Browser</h1>
{#if status === "loading"}
<p class="mt-3 text-sm text-muted-foreground">Loading client database…</p>
{:else if status === "error"}
<p class="mt-3 text-sm text-red-600">Error: {error}</p>
{:else if status === "ready"}
<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)}
<li class="rounded border px-3 py-2">
<div class="font-medium">{a.mainName}</div>
<div class="text-sm text-muted-foreground">
{a.year}
{seasonName(a.seasonId)} • ANN {a.annId} • MAL {a.malId}
</div>
</li>
{/each}
</ul>
{/if}