songs page refactor schema
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { useSearchParams } from "runed/kit";
|
||||
import { onMount } from "svelte";
|
||||
import { z } from "zod";
|
||||
import { browser } from "$app/environment";
|
||||
import { invalidate } from "$app/navigation";
|
||||
import SongEntry from "$lib/components/SongEntry.svelte";
|
||||
@@ -10,30 +9,9 @@
|
||||
import { trackFromSongRow } from "$lib/player/types";
|
||||
import { SongCategoryMap, SongTypeReverseMap } from "$lib/utils/amq";
|
||||
import type { PageData } from "./$types";
|
||||
import { type SvelteSearchForm, SvelteSearchParamsSchema } from "./schema";
|
||||
|
||||
// Zod schema for URL search parameters to ensure type safety and parsing
|
||||
const SongsSearchSchema = z
|
||||
.object({
|
||||
q: z.string().default(""), // song name
|
||||
artist: z.string().default(""), // artist name
|
||||
anime: z.string().default(""), // anime mainName
|
||||
type: z.array(z.string()).default([]), // song type (e.g., "OP", "ED", "INS")
|
||||
gpm: z
|
||||
.string()
|
||||
.optional()
|
||||
.transform((s) => (s ? parseInt(s, 10) : undefined)), // global percent min
|
||||
gpx: z
|
||||
.string()
|
||||
.optional()
|
||||
.transform((s) => (s ? parseInt(s, 10) : undefined)), // global percent max
|
||||
cat: z
|
||||
.string()
|
||||
.optional()
|
||||
.transform((s) => (s ? parseInt(s, 10) : undefined)), // category
|
||||
})
|
||||
.strict();
|
||||
|
||||
const params = useSearchParams(SongsSearchSchema, {
|
||||
const params = useSearchParams(SvelteSearchParamsSchema, {
|
||||
pushHistory: false,
|
||||
showDefaults: false,
|
||||
});
|
||||
@@ -41,7 +19,7 @@
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
// Local form values, bound to inputs, which will update params.
|
||||
let form = $state({
|
||||
let form: SvelteSearchForm = $state({
|
||||
q: params.q,
|
||||
artist: params.artist,
|
||||
anime: params.anime,
|
||||
@@ -274,7 +252,7 @@
|
||||
<h2 class="mt-6 text-lg font-semibold">Songs</h2>
|
||||
|
||||
<ul class="mt-3 space-y-2">
|
||||
{#each data.songRows as r (String(r.annSongId) + "-" + String(r.animeAnnId) + "-" + String(r.type) + "-" + String(r.number))}
|
||||
{#each data.songRows as r (r.annSongId)}
|
||||
<li>
|
||||
<SongEntry
|
||||
annSongId={r.annSongId}
|
||||
|
||||
@@ -1,47 +1,12 @@
|
||||
import { z } from "zod";
|
||||
import type { SongFilters } from "$lib/db/client-db";
|
||||
import { db, ensureSeeded, getSongsWithFilters } from "$lib/db/client-db";
|
||||
import {
|
||||
SongCategoryMap,
|
||||
SongTypeMap,
|
||||
SongTypeReverseMap,
|
||||
} from "$lib/utils/amq";
|
||||
import type { PageLoad } from "./$types";
|
||||
|
||||
const SearchSchema = z
|
||||
.object({
|
||||
q: z.string().optional(), // song name
|
||||
artist: z.string().optional(), // artist name
|
||||
anime: z.string().optional(), // anime mainName
|
||||
type: z
|
||||
.string()
|
||||
.optional()
|
||||
.transform((s) => {
|
||||
if (!s) return undefined;
|
||||
return s
|
||||
.split(",")
|
||||
.map((t) => SongTypeMap[t.trim().toUpperCase()])
|
||||
.filter((n) => n !== undefined);
|
||||
}),
|
||||
gpm: z
|
||||
.string()
|
||||
.optional()
|
||||
.transform((s) => (s ? parseInt(s, 10) : undefined)), // global percent min
|
||||
gpx: z
|
||||
.string()
|
||||
.optional()
|
||||
.transform((s) => (s ? parseInt(s, 10) : undefined)), // global percent max
|
||||
cat: z
|
||||
.string()
|
||||
.optional()
|
||||
.transform((s) => (s ? parseInt(s, 10) : undefined)), // category
|
||||
})
|
||||
.strict();
|
||||
import { LoadSearchParamsSchema } from "./schema";
|
||||
|
||||
export const load: PageLoad = async ({ url, fetch, depends }) => {
|
||||
depends("clientdb:songs");
|
||||
|
||||
const parsed = SearchSchema.safeParse(
|
||||
const parsed = LoadSearchParamsSchema.safeParse(
|
||||
Object.fromEntries(url.searchParams.entries()),
|
||||
);
|
||||
|
||||
|
||||
38
src/routes/songs/schema.ts
Normal file
38
src/routes/songs/schema.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { z } from "zod";
|
||||
import { SongTypeMap } from "$lib/utils/amq";
|
||||
|
||||
// Base schema for raw URL search parameters as strings
|
||||
const BaseSearchParamsSchema = z.object({
|
||||
q: z.string().optional(),
|
||||
artist: z.string().optional(),
|
||||
anime: z.string().optional(),
|
||||
gpm: z.string().optional(),
|
||||
gpx: z.string().optional(),
|
||||
cat: z.string().optional(),
|
||||
});
|
||||
|
||||
// Schema for +page.ts load function (parses comma-separated types into number array)
|
||||
export const LoadSearchParamsSchema = BaseSearchParamsSchema.extend({
|
||||
type: z
|
||||
.string()
|
||||
.optional()
|
||||
.transform((s) => {
|
||||
if (!s) return undefined;
|
||||
return s
|
||||
.split(",")
|
||||
.map((t) => SongTypeMap[t.trim().toUpperCase()])
|
||||
.filter((n) => n !== undefined);
|
||||
}),
|
||||
}).strict();
|
||||
|
||||
// Schema for +page.svelte useSearchParams (handles 'type' as an array of strings from URL)
|
||||
export const SvelteSearchParamsSchema = BaseSearchParamsSchema.extend({
|
||||
type: z.array(z.string()).default([]),
|
||||
}).strict();
|
||||
|
||||
// Define the type for the Svelte form, which will have default values
|
||||
export type SvelteSearchForm = z.infer<typeof SvelteSearchParamsSchema> & {
|
||||
gpm: string; // To allow empty string in form input
|
||||
gpx: string; // To allow empty string in form input
|
||||
cat: string; // To allow empty string in form input
|
||||
};
|
||||
Reference in New Issue
Block a user