songs page refactor schema
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { useSearchParams } from "runed/kit";
|
import { useSearchParams } from "runed/kit";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { z } from "zod";
|
|
||||||
import { browser } from "$app/environment";
|
import { browser } from "$app/environment";
|
||||||
import { invalidate } from "$app/navigation";
|
import { invalidate } from "$app/navigation";
|
||||||
import SongEntry from "$lib/components/SongEntry.svelte";
|
import SongEntry from "$lib/components/SongEntry.svelte";
|
||||||
@@ -10,30 +9,9 @@
|
|||||||
import { trackFromSongRow } from "$lib/player/types";
|
import { trackFromSongRow } from "$lib/player/types";
|
||||||
import { SongCategoryMap, SongTypeReverseMap } from "$lib/utils/amq";
|
import { SongCategoryMap, SongTypeReverseMap } from "$lib/utils/amq";
|
||||||
import type { PageData } from "./$types";
|
import type { PageData } from "./$types";
|
||||||
|
import { type SvelteSearchForm, SvelteSearchParamsSchema } from "./schema";
|
||||||
|
|
||||||
// Zod schema for URL search parameters to ensure type safety and parsing
|
const params = useSearchParams(SvelteSearchParamsSchema, {
|
||||||
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, {
|
|
||||||
pushHistory: false,
|
pushHistory: false,
|
||||||
showDefaults: false,
|
showDefaults: false,
|
||||||
});
|
});
|
||||||
@@ -41,7 +19,7 @@
|
|||||||
let { data }: { data: PageData } = $props();
|
let { data }: { data: PageData } = $props();
|
||||||
|
|
||||||
// Local form values, bound to inputs, which will update params.
|
// Local form values, bound to inputs, which will update params.
|
||||||
let form = $state({
|
let form: SvelteSearchForm = $state({
|
||||||
q: params.q,
|
q: params.q,
|
||||||
artist: params.artist,
|
artist: params.artist,
|
||||||
anime: params.anime,
|
anime: params.anime,
|
||||||
@@ -274,7 +252,7 @@
|
|||||||
<h2 class="mt-6 text-lg font-semibold">Songs</h2>
|
<h2 class="mt-6 text-lg font-semibold">Songs</h2>
|
||||||
|
|
||||||
<ul class="mt-3 space-y-2">
|
<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>
|
<li>
|
||||||
<SongEntry
|
<SongEntry
|
||||||
annSongId={r.annSongId}
|
annSongId={r.annSongId}
|
||||||
|
|||||||
@@ -1,47 +1,12 @@
|
|||||||
import { z } from "zod";
|
|
||||||
import type { SongFilters } from "$lib/db/client-db";
|
import type { SongFilters } from "$lib/db/client-db";
|
||||||
import { db, ensureSeeded, getSongsWithFilters } 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";
|
import type { PageLoad } from "./$types";
|
||||||
|
import { LoadSearchParamsSchema } from "./schema";
|
||||||
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();
|
|
||||||
|
|
||||||
export const load: PageLoad = async ({ url, fetch, depends }) => {
|
export const load: PageLoad = async ({ url, fetch, depends }) => {
|
||||||
depends("clientdb:songs");
|
depends("clientdb:songs");
|
||||||
|
|
||||||
const parsed = SearchSchema.safeParse(
|
const parsed = LoadSearchParamsSchema.safeParse(
|
||||||
Object.fromEntries(url.searchParams.entries()),
|
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