refactor song type schema on search page
This commit is contained in:
@@ -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(),
|
||||
|
||||
@@ -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 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label for="song-type">Song Type</Label>
|
||||
<NativeSelect id="song-type" bind:value={params.songType}>
|
||||
<NativeSelectOption value="0">All</NativeSelectOption>
|
||||
<NativeSelectOption value="1">OP</NativeSelectOption>
|
||||
<NativeSelectOption value="2">ED</NativeSelectOption>
|
||||
<NativeSelectOption value="3">INS</NativeSelectOption>
|
||||
</NativeSelect>
|
||||
<Label for="song-types">Song Type</Label>
|
||||
<fieldset id="song-types">
|
||||
{#each Object.keys(AmqSongLinkTypeMap) as type}
|
||||
<label for="song-type-{type.toLowerCase()}">{type}</label>
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:group={params.type}
|
||||
value={AmqSongLinkTypeMap[
|
||||
type as keyof typeof AmqSongLinkTypeMap
|
||||
]}
|
||||
id="song-type-{type.toLowerCase()}"
|
||||
/>
|
||||
{/each}
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label for="songs-limit">Limit</Label>
|
||||
@@ -140,7 +144,7 @@
|
||||
max="200"
|
||||
step="20"
|
||||
class="w-1/2"
|
||||
bind:value={params.songsLimit}
|
||||
bind:value={params.limit}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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([]),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user