flesh out anime page

This commit is contained in:
2026-02-06 05:02:05 -08:00
parent 82714100e4
commit 665eb31775
2 changed files with 108 additions and 6 deletions

View File

@@ -30,6 +30,7 @@ export async function getAnimeList(db: ClientDb, limit = DEFAULT_LIST_LIMIT) {
year: anime.year, year: anime.year,
seasonId: anime.seasonId, seasonId: anime.seasonId,
malId: anime.malId, malId: anime.malId,
aniListId: anime.aniListId,
}) })
.from(anime) .from(anime)
.orderBy(desc(anime.year), desc(anime.seasonId), desc(anime.annId)) .orderBy(desc(anime.year), desc(anime.seasonId), desc(anime.annId))
@@ -60,6 +61,7 @@ export async function searchAnimeByName(
year: anime.year, year: anime.year,
seasonId: anime.seasonId, seasonId: anime.seasonId,
malId: anime.malId, malId: anime.malId,
aniListId: anime.aniListId,
}) })
.from(anime) .from(anime)
.where(like(anime.mainName, pattern)) .where(like(anime.mainName, pattern))
@@ -84,6 +86,7 @@ export async function getAnimeWithSongsByAnnId(db: ClientDb, annId: number) {
year: anime.year, year: anime.year,
seasonId: anime.seasonId, seasonId: anime.seasonId,
malId: anime.malId, malId: anime.malId,
aniListId: anime.aniListId,
}) })
.from(anime) .from(anime)
.where(eq(anime.annId, annId)) .where(eq(anime.annId, annId))
@@ -142,6 +145,7 @@ export async function getSongsForMalAnimeIds(
.select({ .select({
annId: anime.annId, annId: anime.annId,
malId: anime.malId, malId: anime.malId,
aniListId: anime.aniListId,
animeName: anime.mainName, animeName: anime.mainName,
year: anime.year, year: anime.year,
seasonId: anime.seasonId, seasonId: anime.seasonId,

View File

@@ -3,6 +3,8 @@
import { invalidate } from "$app/navigation"; import { invalidate } from "$app/navigation";
import SongEntry from "$lib/components/SongEntry.svelte"; import SongEntry from "$lib/components/SongEntry.svelte";
import { db as clientDb } from "$lib/db/client-db"; import { db as clientDb } from "$lib/db/client-db";
import { addAllToQueue, playAllNext } from "$lib/player/player.svelte";
import { trackFromSongRow } from "$lib/player/types";
import { seasonName } from "$lib/utils/amq"; import { seasonName } from "$lib/utils/amq";
import type { PageData } from "./$types"; import type { PageData } from "./$types";
@@ -21,6 +23,31 @@
return; return;
} }
}); });
function playableTracks() {
const rows = data.animeWithSongs?.songs ?? [];
return rows
.map((s) =>
trackFromSongRow({
annSongId: s.annSongId,
animeName: data.animeWithSongs?.anime.mainName ?? "Unknown Anime",
type: s.type,
number: s.number,
songName: s.songName,
artistName: s.artistName,
fileName: s.fileName ?? null,
}),
)
.filter((t) => t !== null);
}
function queueAll() {
addAllToQueue(playableTracks());
}
function playAllNextFromAnime() {
playAllNext(playableTracks());
}
</script> </script>
{#if !clientDb} {#if !clientDb}
@@ -36,18 +63,89 @@
{:else if !data.animeWithSongs} {:else if !data.animeWithSongs}
<p class="mt-3 text-sm text-muted-foreground">Loading anime…</p> <p class="mt-3 text-sm text-muted-foreground">Loading anime…</p>
{:else} {:else}
<header class="mt-2 space-y-1"> <header class="mt-2 space-y-2">
<h1 class="text-2xl font-semibold">{data.animeWithSongs.anime.mainName}</h1> <h1 class="text-2xl font-semibold">{data.animeWithSongs.anime.mainName}</h1>
<p class="text-sm text-muted-foreground"> <p class="text-sm text-muted-foreground">
{data.animeWithSongs.anime.year}{seasonName( {data.animeWithSongs.anime.year}
Number(data.animeWithSongs.anime.seasonId), {seasonName(Number(data.animeWithSongs.anime.seasonId))}
)} • ANN {data.animeWithSongs.anime.annId} • MAL {data.animeWithSongs
.anime.malId}
</p> </p>
<div class="flex flex-wrap items-center gap-2 text-sm">
<a
class="underline underline-offset-4 hover:no-underline text-muted-foreground hover:text-foreground"
href={`https://www.animenewsnetwork.com/encyclopedia/anime.php?id=${data.animeWithSongs.anime.annId}`}
target="_blank"
rel="noreferrer"
>
ANN
</a>
{#if data.animeWithSongs.anime.malId != null}
<a
class="underline underline-offset-4 hover:no-underline text-muted-foreground hover:text-foreground"
href={`https://myanimelist.net/anime/${data.animeWithSongs.anime.malId}/`}
target="_blank"
rel="noreferrer"
>
MAL
</a>
{:else}
<span
class="cursor-not-allowed select-none text-muted-foreground/60 underline underline-offset-4 decoration-dotted"
title="MAL link unavailable (missing MAL id)"
aria-disabled="true"
>
MAL
</span>
{/if}
{#if data.animeWithSongs.anime.aniListId != null}
<a
class="underline underline-offset-4 hover:no-underline text-muted-foreground hover:text-foreground"
href={`https://anilist.co/anime/${data.animeWithSongs.anime.aniListId}`}
target="_blank"
rel="noreferrer"
>
AniList
</a>
{:else}
<span
class="cursor-not-allowed select-none text-muted-foreground/60 underline underline-offset-4 decoration-dotted"
title="AniList link unavailable (missing AniList id)"
aria-disabled="true"
>
AniList
</span>
{/if}
</div>
</header> </header>
<section class="mt-6"> <section class="mt-6">
<h2 class="text-lg font-semibold">Songs</h2> <div class="flex flex-wrap items-center justify-between gap-3">
<h2 class="text-lg font-semibold">Songs</h2>
<div class="flex flex-wrap items-center gap-2">
<button
type="button"
class="inline-flex h-9 items-center justify-center rounded-md border px-3 text-sm font-medium hover:bg-accent hover:text-accent-foreground disabled:opacity-50"
onclick={queueAll}
disabled={data.animeWithSongs.songs.length === 0}
title="Add all playable songs from this anime to the end of the queue"
>
Queue all
</button>
<button
type="button"
class="inline-flex h-9 items-center justify-center rounded-md border px-3 text-sm font-medium hover:bg-accent hover:text-accent-foreground disabled:opacity-50"
onclick={playAllNextFromAnime}
disabled={data.animeWithSongs.songs.length === 0}
title="Insert all playable songs from this anime right after the current track"
>
Play all next
</button>
</div>
</div>
{#if data.animeWithSongs.songs.length === 0} {#if data.animeWithSongs.songs.length === 0}
<p class="mt-2 text-sm text-muted-foreground"> <p class="mt-2 text-sm text-muted-foreground">