From 72ee0260d9b832ea301c3e3e235c837888fce663 Mon Sep 17 00:00:00 2001 From: Yuri Tatishchev Date: Mon, 9 Feb 2026 20:49:47 -0800 Subject: [PATCH] refactor song type schema on search page --- src/lib/types/amq/anime.ts | 22 ++++++++++++++++------ src/routes/songs/+page.svelte | 32 ++++++++++++++++++-------------- src/routes/songs/+page.ts | 12 ++++-------- src/routes/songs/schema.ts | 33 ++++++++++++++++++++++++--------- 4 files changed, 62 insertions(+), 37 deletions(-) diff --git a/src/lib/types/amq/anime.ts b/src/lib/types/amq/anime.ts index 701c64e..f184e85 100644 --- a/src/lib/types/amq/anime.ts +++ b/src/lib/types/amq/anime.ts @@ -2,18 +2,28 @@ import { z } from "zod"; import { AmqAnimeCategory, AmqAnimeGenre, AmqAnimeTag } from "./anime-extended"; -export const Season = z.enum({ +export const AmqSeasonMap = { Winter: 0, Spring: 1, Summer: 2, Fall: 3, -} as const); +} as const; -export const SongLinkType = z.enum({ +export const AmqSeason = z.enum(AmqSeasonMap); + +export const AmqSongLinkTypeMap = { OP: 1, ED: 2, INS: 3, -} as const); +} as const; + +export const AmqSongLinkTypeMapReverse = { + 1: "OP", + 2: "ED", + 3: "INS", +} as const; + +export const AmqSongLinkType = z.enum(AmqSongLinkTypeMap); const BooleanInt = z.enum({ false: 0, @@ -23,7 +33,7 @@ const BooleanInt = z.enum({ export const AmqSongLink = z.object({ songId: z.int().positive(), number: z.int().nonnegative(), - type: SongLinkType, + type: AmqSongLinkType, annSongId: z.int().positive(), uploaded: BooleanInt, rebroadcast: BooleanInt, @@ -53,7 +63,7 @@ export const AmqAnimeSchema = z.object({ }), ), year: z.int().positive(), - seasonId: Season, + seasonId: AmqSeason, songLinks: z.array(AmqSongLink), opCount: z.int().nonnegative(), edCount: z.int().nonnegative(), diff --git a/src/routes/songs/+page.svelte b/src/routes/songs/+page.svelte index f809f67..4a74e19 100644 --- a/src/routes/songs/+page.svelte +++ b/src/routes/songs/+page.svelte @@ -7,17 +7,14 @@ import { Button } from "$lib/components/ui/button"; import { Input } from "$lib/components/ui/input"; import { Label } from "$lib/components/ui/label"; - import { - NativeSelect, - NativeSelectOption, - } from "$lib/components/ui/native-select"; import { db as clientDb } from "$lib/db/client-db"; import { addAllToQueue, playAllNext } from "$lib/player/player.svelte"; import { trackFromSongRow } from "$lib/player/types"; + import { AmqSongLinkTypeMap } from "$lib/types/amq"; import type { PageData } from "./$types"; - import { SearchParamsSchemaClient } from "./schema"; + import { SearchParamsSchema } from "./schema"; - const params = useSearchParams(SearchParamsSchemaClient, { + const params = useSearchParams(SearchParamsSchema, { pushHistory: false, showDefaults: false, }); @@ -123,13 +120,20 @@
- - - All - OP - ED - INS - + +
+ {#each Object.keys(AmqSongLinkTypeMap) as type} + + + {/each} +
@@ -140,7 +144,7 @@ max="200" step="20" class="w-1/2" - bind:value={params.songsLimit} + bind:value={params.limit} />
diff --git a/src/routes/songs/+page.ts b/src/routes/songs/+page.ts index fc6417f..efcefb7 100644 --- a/src/routes/songs/+page.ts +++ b/src/routes/songs/+page.ts @@ -1,12 +1,12 @@ import type { SongFilters } from "$lib/db/client-db"; import { db, ensureSeeded, getSongsWithFilters } from "$lib/db/client-db"; import type { PageLoad } from "./$types"; -import { SearchParamsSchemaServer } from "./schema"; +import { SearchParamsSchema } from "./schema"; export const load: PageLoad = async ({ url, fetch, depends }) => { depends("clientdb:songs"); - const parsed = SearchParamsSchemaServer.safeParse( + const parsed = SearchParamsSchema.safeParse( Object.fromEntries(url.searchParams.entries()), ); @@ -19,7 +19,7 @@ export const load: PageLoad = async ({ url, fetch, depends }) => { filters.globalPercentMin = parsed.data.gpm; if (parsed.data.gpx !== undefined) filters.globalPercentMax = parsed.data.gpx; - if (parsed.data.songType) filters.songTypes = [parsed.data.songType]; + if (parsed.data.type) filters.songTypes = parsed.data.type; } // Client-only DB: on the server `db` is null, so return [] and let hydration re-run load in browser. @@ -32,11 +32,7 @@ export const load: PageLoad = async ({ url, fetch, depends }) => { await ensureSeeded({ fetch }); - const songRows = await getSongsWithFilters( - db, - filters, - parsed.data?.songsLimit, - ); + const songRows = await getSongsWithFilters(db, filters, parsed.data?.limit); return { filters: parsed.success ? parsed.data : {}, // Return original parsed data for form state diff --git a/src/routes/songs/schema.ts b/src/routes/songs/schema.ts index f9230c8..93ceb73 100644 --- a/src/routes/songs/schema.ts +++ b/src/routes/songs/schema.ts @@ -1,18 +1,33 @@ import { z } from "zod"; +import { + AmqSongLinkType, + AmqSongLinkTypeMap, + AmqSongLinkTypeMapReverse, +} from "$lib/types/amq"; + +const SEP = ","; + +const songTypesCodec = z.codec(z.string(), z.array(AmqSongLinkType), { + decode: (str) => + str + ? decodeURIComponent(str) + .split(SEP) + .map((s) => AmqSongLinkTypeMap[s as keyof typeof AmqSongLinkTypeMap]) + : [], + encode: (arr) => + arr + ? encodeURIComponent( + arr.map((a) => AmqSongLinkTypeMapReverse[a]).join(SEP), + ) + : "", +}); export const SearchParamsSchema = z.object({ - songsLimit: z.coerce.number().int().default(20), + limit: z.coerce.number().int().default(20), song: z.string().optional().default(""), artist: z.string().optional().default(""), anime: z.string().optional().default(""), gpm: z.coerce.number().int().optional().default(0), gpx: z.coerce.number().int().optional().default(100), -}); - -export const SearchParamsSchemaClient = SearchParamsSchema.extend({ - songType: z.string().optional().default("0"), -}); - -export const SearchParamsSchemaServer = SearchParamsSchema.extend({ - songType: z.coerce.number().int().optional(), + type: songTypesCodec.default([]), });