Files
amqtrain/src/routes/list/+page.svelte

170 lines
4.3 KiB
Svelte

<script lang="ts">
import { useSearchParams } from "runed/kit";
import { onMount } from "svelte";
import { z } from "zod";
import { invalidate } from "$app/navigation";
import SongEntry from "$lib/components/SongEntry.svelte";
import { db as clientDb } from "$lib/db/client-db";
import {
MalAnimeListQuerySchema,
MalAnimeListStatusEnum,
} from "$lib/types/mal";
import type { PageData } from "./$types";
const ListSearchSchema = MalAnimeListQuerySchema.extend({
// Allow empty string to mean "All"
status: MalAnimeListStatusEnum.or(z.literal("")).default(""),
// URL param `mal` is updated only on Search
mal: z.string().default(""),
}).strict();
const params = useSearchParams(ListSearchSchema, {
pushHistory: false,
showDefaults: false,
});
let { data }: { data: PageData } = $props();
// Local username field that does NOT update the URL as you type.
let formMal = $state<string>(params.mal);
// 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 (clientDb) {
void invalidate("clientdb:songs");
return;
}
});
function songArtistLabel(r: (typeof data.songRows)[number]) {
return r.artistName ?? r.groupName ?? null;
}
function makeMalHref(username: string) {
return `https://myanimelist.net/profile/${encodeURIComponent(username)}`;
}
</script>
<h1 class="text-2xl font-semibold">MAL List → Songs</h1>
<p class="mt-2 text-sm text-muted-foreground">
{#if !clientDb}
Loading DB...
{/if}
</p>
<form
class="mt-4 flex flex-col gap-2"
onsubmit={(e) => {
e.preventDefault();
params.mal = formMal;
}}
>
<div class="flex flex-wrap gap-2">
<div class="flex flex-col gap-2">
<label class="text-sm text-muted-foreground" for="mal-user"
>MAL username</label
>
<input
id="mal-user"
class="rounded border px-3 py-2 text-sm"
placeholder="e.g. CaZzzer"
bind:value={formMal}
autocomplete="off"
spellcheck={false}
/>
</div>
<div class="flex flex-col gap-2">
<label class="text-sm text-muted-foreground" for="mal-status"
>Status</label
>
<select
id="mal-status"
class="rounded border px-3 py-2 text-sm"
bind:value={params.status}
>
<option value="">All</option>
<option value="watching">Watching</option>
<option value="completed">Completed</option>
<option value="on_hold">On hold</option>
<option value="dropped">Dropped</option>
<option value="plan_to_watch">Plan to watch</option>
</select>
</div>
<div class="flex flex-col justify-end">
<button
type="submit"
class="rounded border px-3 py-2 text-sm"
disabled={!(formMal ?? "").trim()}
>
Search
</button>
</div>
</div>
<div class="text-sm text-muted-foreground">
{#if data.username}
MAL entries: {data.malResponse?.data.length ?? 0} (limited to {data.LIST_QUERY_LIMIT})
• Songs found: {data.songRows.length}
{/if}
</div>
{#if data.username}
<div class="text-sm">
<a
class="hover:underline"
href={makeMalHref(data.username)}
target="_blank"
rel="noreferrer"
>
View {data.username} on MAL
</a>
</div>
{/if}
</form>
{#if (formMal ?? "").trim() && data.username && (data.malResponse?.data.length ?? 0) === 0}
<p class="mt-4 text-sm text-muted-foreground">
No anime returned from MAL (did you set a restrictive status?).
</p>
{/if}
{#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 data.songRows.length > 0}
<h2 class="mt-6 text-lg font-semibold">Songs</h2>
<ul class="mt-3 space-y-2">
{#each data.songRows as r (String(r.annId) + ":" + String(r.annSongId))}
<li>
<SongEntry
animeName={r.animeName}
type={r.type}
number={r.number}
songName={r.songName}
artistName={songArtistLabel(r)}
fileName={r.fileName}
showPlayer={true}
/>
</li>
{/each}
</ul>
{/if}
{#if data.malResponse?.paging?.next}
<p class="mt-6 text-sm text-muted-foreground">
More results exist on MAL, but pagination is not wired yet.
</p>
{/if}