diff --git a/src/lib/db/client-db/queries.ts b/src/lib/db/client-db/queries.ts index af25064..af79958 100644 --- a/src/lib/db/client-db/queries.ts +++ b/src/lib/db/client-db/queries.ts @@ -161,6 +161,7 @@ export async function getAnimeWithSongsByAnnId(db: ClientDb, annId: number) { export async function getSongsForMalAnimeIds( db: ClientDb, malAnimeIds: number[], + songTypes?: number[], ) { const ids = malAnimeIds.filter((n) => Number.isFinite(n)); if (ids.length === 0) return []; @@ -192,7 +193,14 @@ export async function getSongsForMalAnimeIds( .innerJoin(songs, eq(songs.annSongId, animeSongLinks.annSongId)) .leftJoin(artists, eq(artists.songArtistId, songs.songArtistId)) .leftJoin(groups, eq(groups.songGroupId, songs.songGroupId)) - .where(inArray(anime.malId, ids)) + .where( + songTypes && songTypes.length > 0 + ? and( + inArray(anime.malId, ids), + inArray(animeSongLinks.type, songTypes), + ) + : inArray(anime.malId, ids), + ) .orderBy( desc(anime.year), desc(anime.seasonId), diff --git a/src/routes/list/+page.svelte b/src/routes/list/+page.svelte index 3d0919e..9dd1a60 100644 --- a/src/routes/list/+page.svelte +++ b/src/routes/list/+page.svelte @@ -5,13 +5,16 @@ import { browser } from "$app/environment"; import { invalidate } from "$app/navigation"; import SongEntry from "$lib/components/SongEntry.svelte"; + import { ChipGroup } from "$lib/components/ui/chip-group"; import { db as clientDb } from "$lib/db/client-db"; import { player } from "$lib/player/store.svelte"; import { trackFromSongRow } from "$lib/player/types"; + import { AmqSongLinkTypeMap } from "$lib/types/amq"; import { MalAnimeListQuerySchema, MalAnimeListStatusEnum, } from "$lib/types/mal"; + import { songTypesCodec } from "../songs/schema"; import type { PageData } from "./$types"; const ListSearchSchema = MalAnimeListQuerySchema.extend({ @@ -19,6 +22,7 @@ status: MalAnimeListStatusEnum.or(z.literal("")).default(""), // URL param `mal` is updated only on Search mal: z.string().default(""), + type: songTypesCodec.default([]), }).strict(); const params = useSearchParams(ListSearchSchema, { @@ -130,6 +134,17 @@ +
+ ({ + label: type, + value: AmqSongLinkTypeMap[type as keyof typeof AmqSongLinkTypeMap], + }))} + bind:value={params.type} + /> +
+
{#if data.username} MAL entries: {data.malResponse?.data.length ?? 0} (limited to {data.LIST_QUERY_LIMIT}) diff --git a/src/routes/list/+page.ts b/src/routes/list/+page.ts index c8dd4ed..6045f89 100644 --- a/src/routes/list/+page.ts +++ b/src/routes/list/+page.ts @@ -7,6 +7,7 @@ import { MalAnimeListResponseSchema, MalAnimeListStatusEnum, } from "$lib/types/mal"; +import { songTypesCodec } from "../songs/schema"; import type { PageLoad } from "./$types"; const LIST_QUERY_LIMIT = 1000; @@ -17,6 +18,7 @@ const SearchSchema = MalAnimeListQuerySchema.extend({ // Allow empty string to mean "All" status: MalAnimeListStatusEnum.or(z.literal("")).optional(), + type: songTypesCodec.optional(), }).strict(); type StatusParam = z.infer["status"]; @@ -39,6 +41,7 @@ export const load: PageLoad = async ({ url, fetch, depends }) => { const status = parsed.success ? normalizeStatus(parsed.data.status) : undefined; + const types = parsed.success ? parsed.data.type : undefined; const username = (mal ?? "").trim(); @@ -88,7 +91,7 @@ export const load: PageLoad = async ({ url, fetch, depends }) => { await ensureSeeded({ fetch }); const malIds = malResponse.data.map((e) => e.node.id); - const songRows = await getSongsForMalAnimeIds(db, malIds); + const songRows = await getSongsForMalAnimeIds(db, malIds, types); return { LIST_QUERY_LIMIT, diff --git a/src/routes/songs/schema.ts b/src/routes/songs/schema.ts index 93ceb73..162f2e3 100644 --- a/src/routes/songs/schema.ts +++ b/src/routes/songs/schema.ts @@ -7,7 +7,7 @@ import { const SEP = ","; -const songTypesCodec = z.codec(z.string(), z.array(AmqSongLinkType), { +export const songTypesCodec = z.codec(z.string(), z.array(AmqSongLinkType), { decode: (str) => str ? decodeURIComponent(str)