170 lines
4.3 KiB
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}
|