prototype anime list input
This commit is contained in:
@@ -0,0 +1,36 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { AnimeListCodec } from "./schema";
|
||||||
|
import { ChipGroup } from "$lib/components/ui/chip-group";
|
||||||
|
import { AnimeListWatchStatus } from "$lib/utils/list";
|
||||||
|
import NativeSelect from "$lib/components/ui/native-select/native-select.svelte";
|
||||||
|
import NativeSelectOption from "$lib/components/ui/native-select/native-select-option.svelte";
|
||||||
|
import Input from "$lib/components/ui/input/input.svelte";
|
||||||
|
import { Label } from "$lib/components/ui/label";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
let { value = $bindable() }: { value: z.infer<typeof AnimeListCodec> } =
|
||||||
|
$props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<Label for="list-kind">Kind</Label>
|
||||||
|
<NativeSelect id="list-kind" bind:value={value.kind}>
|
||||||
|
<NativeSelectOption value="mal">MAL</NativeSelectOption>
|
||||||
|
<NativeSelectOption value="anilist">AniList</NativeSelectOption>
|
||||||
|
<NativeSelectOption value="kitsu">Kitsu</NativeSelectOption>
|
||||||
|
</NativeSelect>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<Label for="list-username">Username</Label>
|
||||||
|
<Input id="list-username" bind:value={value.username} />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<Label for="list-status">Status</Label>
|
||||||
|
<ChipGroup
|
||||||
|
items={AnimeListWatchStatus.options.map((v) => ({
|
||||||
|
label: v.toUpperCase(),
|
||||||
|
value: v,
|
||||||
|
}))}
|
||||||
|
bind:value={value.status}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
2
src/lib/components/inputs/anime-list-input/index.ts
Normal file
2
src/lib/components/inputs/anime-list-input/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export { default as AnimeListInput } from "./AnimeListInput.svelte";
|
||||||
|
export * from "./schema";
|
||||||
16
src/lib/components/inputs/anime-list-input/schema.ts
Normal file
16
src/lib/components/inputs/anime-list-input/schema.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { AnimeList, AnimeListWatchStatus } from "$lib/utils/list";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
const SEP_FIELD = ":";
|
||||||
|
const SEP_VALUE = ",";
|
||||||
|
|
||||||
|
export const AnimeListCodec = z.codec(z.string(), AnimeList, {
|
||||||
|
decode: (s) => {
|
||||||
|
const [kind, ...rest] = decodeURIComponent(s).split(SEP_FIELD);
|
||||||
|
const statusStr = rest.pop();
|
||||||
|
const status = statusStr ? statusStr.split(SEP_VALUE).map((v) => AnimeListWatchStatus.parse(v)) : [];
|
||||||
|
const username = rest.join("");
|
||||||
|
return AnimeList.parse({ kind, username, status });
|
||||||
|
},
|
||||||
|
encode: (list) => encodeURIComponent(`${list.kind}${SEP_FIELD}${list.username}${SEP_FIELD}${list.status.join(SEP_VALUE)}`),
|
||||||
|
});
|
||||||
@@ -21,7 +21,7 @@ export const AnimeListWatchStatus = z.enum({
|
|||||||
export const AnimeList = z.object({
|
export const AnimeList = z.object({
|
||||||
kind: AnimeListKind,
|
kind: AnimeListKind,
|
||||||
username: z.string(),
|
username: z.string(),
|
||||||
status: z.array(AnimeListWatchStatus).default([]),
|
status: z.array(AnimeListWatchStatus),
|
||||||
});
|
});
|
||||||
|
|
||||||
export function listExternalUrl(list: z.infer<typeof AnimeList>) {
|
export function listExternalUrl(list: z.infer<typeof AnimeList>) {
|
||||||
|
|||||||
@@ -2,46 +2,43 @@
|
|||||||
import { useSearchParams } from "runed/kit";
|
import { useSearchParams } from "runed/kit";
|
||||||
import type { PageData } from "./$types";
|
import type { PageData } from "./$types";
|
||||||
import { SearchParamsSchema } from "./schema";
|
import { SearchParamsSchema } from "./schema";
|
||||||
import { ChipGroup } from "$lib/components/ui/chip-group";
|
|
||||||
import { AnimeListWatchStatus } from "$lib/utils/list";
|
|
||||||
import NativeSelect from "$lib/components/ui/native-select/native-select.svelte";
|
|
||||||
import NativeSelectOption from "$lib/components/ui/native-select/native-select-option.svelte";
|
|
||||||
import Input from "$lib/components/ui/input/input.svelte";
|
|
||||||
import { Label } from "$lib/components/ui/label";
|
|
||||||
import { Button } from "$lib/components/ui/button";
|
import { Button } from "$lib/components/ui/button";
|
||||||
|
import { z } from "zod";
|
||||||
|
import {
|
||||||
|
AnimeListCodec,
|
||||||
|
AnimeListInput,
|
||||||
|
} from "$lib/components/inputs/anime-list-input";
|
||||||
|
|
||||||
let { data }: { data: PageData } = $props();
|
let { data }: { data: PageData } = $props();
|
||||||
|
|
||||||
const params = useSearchParams(SearchParamsSchema, {
|
const params = useSearchParams(SearchParamsSchema, {
|
||||||
showDefaults: true,
|
pushHistory: false,
|
||||||
updateURL: false,
|
});
|
||||||
|
|
||||||
|
let formState: z.infer<typeof AnimeListCodec> = $state({
|
||||||
|
kind: "mal",
|
||||||
|
username: "",
|
||||||
|
status: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
// $inspect(formState);
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
console.log("formState", formState);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h1 class="text-2xl font-semibold">List Search WIP</h1>
|
<h1 class="text-2xl font-semibold">List Search WIP</h1>
|
||||||
|
|
||||||
<form class="flex flex-wrap gap-2">
|
<form
|
||||||
<div class="flex flex-col gap-2">
|
onsubmit={(e) => {
|
||||||
<Label for="list-kind">Kind</Label>
|
e.preventDefault();
|
||||||
<NativeSelect id="list-kind" bind:value={params.kind}>
|
params.kind = formState.kind;
|
||||||
<NativeSelectOption value="mal">MAL</NativeSelectOption>
|
params.username = formState.username;
|
||||||
<NativeSelectOption value="anilist">AniList</NativeSelectOption>
|
params.status = formState.status;
|
||||||
<NativeSelectOption value="kitsu">Kitsu</NativeSelectOption>
|
}}
|
||||||
</NativeSelect>
|
class="flex flex-wrap items-end gap-2"
|
||||||
</div>
|
>
|
||||||
<div class="flex flex-col gap-2">
|
<AnimeListInput bind:value={formState} />
|
||||||
<Label for="list-username">Username</Label>
|
|
||||||
<Input id="list-username" bind:value={params.username} />
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-2">
|
|
||||||
<Label for="list-status">Status</Label>
|
|
||||||
<ChipGroup
|
|
||||||
items={AnimeListWatchStatus.options.map((v) => ({
|
|
||||||
label: v.toUpperCase(),
|
|
||||||
value: v,
|
|
||||||
}))}
|
|
||||||
bind:value={params.status}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Button type="submit">Search</Button>
|
<Button type="submit">Search</Button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,15 +1,8 @@
|
|||||||
import { AnimeListKind, AnimeListWatchStatus } from "$lib/utils/list";
|
import { AnimeListKind, AnimeListWatchStatus } from "$lib/utils/list";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
const valueSep = ".";
|
|
||||||
|
|
||||||
const statusCodec = z.codec(z.string(), z.array(AnimeListWatchStatus), {
|
|
||||||
decode: (s) => s ? decodeURIComponent(s).split(valueSep).map((s) => AnimeListWatchStatus.parse(s)) : [],
|
|
||||||
encode: (v) => v.length ? encodeURIComponent(v.join(valueSep)) : "",
|
|
||||||
})
|
|
||||||
|
|
||||||
export const SearchParamsSchema = z.object({
|
export const SearchParamsSchema = z.object({
|
||||||
kind: AnimeListKind.default("mal"),
|
kind: AnimeListKind.default("mal"),
|
||||||
username: z.string().optional().default(""),
|
username: z.string().default(""),
|
||||||
status: statusCodec.default([]),
|
status: z.array(AnimeListWatchStatus).default([]),
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user