list page pt. 6 remove hydration variables

This commit is contained in:
2026-02-05 22:18:10 -08:00
parent 61dcd2e173
commit 6c03916c2b

View File

@@ -1,7 +1,9 @@
<script lang="ts">
import { useSearchParams } from "runed/kit";
import { onMount } from "svelte";
import { z } from "zod";
import { db, ensureSeeded, getSongsForMalAnimeIds } from "$lib/db/client-db";
import { invalidate } from "$app/navigation";
import { db as clientDb } from "$lib/db/client-db";
import {
MalAnimeListQuerySchema,
MalAnimeListStatusEnum,
@@ -27,38 +29,40 @@
// Local username field that does NOT update the URL as you type.
let formMal = $state<string>("");
// Songs displayed in the UI: start with server-provided `songRows`,
// then hydrate client-side if needed.
let hydratedSongRows = $state<PageData["songRows"]>([]);
// Keep form input synced with navigation (back/forward) and initial load.
$effect(() => {
formMal = data.username || params.mal || "";
});
// Keep displayed songs synced to the latest `data` (avoid stale capture).
$effect(() => {
hydratedSongRows = data.songRows;
});
// If SSR returned no songRows (because client DB wasn't available), hydrate in browser.
$effect(() => {
void (async () => {
// If server already provided songs, we are done.
// If SSR returned no songRows (because client DB wasn't available),
// re-run load on the client once the DB is ready by invalidating.
onMount(() => {
if (data.songRows.length > 0) return;
if (!data.username || !data.malResponse) return;
// If we don't have an active username or MAL response, nothing to hydrate.
if (!data.username) return;
if (!data.malResponse) return;
if (clientDb) {
void invalidate("clientdb:songs");
return;
}
// If client DB isn't ready (SSR) we'll re-run once it exists (module init).
if (!db) return;
let cancelled = false;
await ensureSeeded();
const waitForDbAndInvalidate = async () => {
for (let i = 0; i < 50; i++) {
if (cancelled) return;
if (clientDb) {
await invalidate("clientdb:songs");
return;
}
await new Promise((r) => setTimeout(r, 50));
}
};
const malIds = data.malResponse.data.map((e) => e.node.id);
hydratedSongRows = await getSongsForMalAnimeIds(db, malIds);
})();
void waitForDbAndInvalidate();
return () => {
cancelled = true;
};
});
function onSearch() {
@@ -66,7 +70,7 @@
params.mal = formMal;
}
function songArtistLabel(r: (typeof hydratedSongRows)[number]) {
function songArtistLabel(r: (typeof data.songRows)[number]) {
return r.artistName ?? r.groupName ?? null;
}
@@ -141,7 +145,7 @@
Waiting for username…
{:else}
MAL entries: {data.malResponse?.data.length ?? 0} (limited to {LIST_QUERY_LIMIT})
• Songs found: {hydratedSongRows.length}
• Songs found: {data.songRows.length}
{/if}
</div>
@@ -165,18 +169,18 @@
</p>
{/if}
{#if (formMal ?? "").trim() && data.username && (data.malResponse?.data.length ?? 0) > 0 && hydratedSongRows.length === 0}
{#if (formMal ?? "").trim() && data.username && (data.malResponse?.data.length ?? 0) > 0 && data.songRows.length === 0}
<p class="mt-4 text-sm text-muted-foreground">
No songs matched in the local database. This likely means none of the MAL
anime IDs exist in the AMQ DB.
</p>
{/if}
{#if hydratedSongRows.length > 0}
{#if data.songRows.length > 0}
<h2 class="mt-6 text-lg font-semibold">Songs</h2>
<ul class="mt-3 space-y-2">
{#each hydratedSongRows as r (String(r.annId) + ":" + String(r.annSongId))}
{#each data.songRows as r (String(r.annId) + ":" + String(r.annSongId))}
<li class="rounded border px-3 py-2">
<div class="font-medium">
{r.songName}