learn and share notes on atproto (wip) 馃
malfestio.stormlightlabs.org/
readability
solid
axum
atproto
srs
1import type { CardType } from "$lib/model";
2import { Button } from "$ui/Button";
3import { createEffect, createSignal, Show } from "solid-js";
4
5type CardEditorProps = {
6 front?: string;
7 back?: string;
8 mediaUrl?: string;
9 cardType?: CardType;
10 hints?: string[];
11 onSave: (data: { front: string; back: string; mediaUrl?: string; cardType: CardType; hints: string[] }) => void;
12 onCancel?: () => void;
13};
14
15export function CardEditor(props: CardEditorProps) {
16 const [front, setFront] = createSignal("");
17 const [back, setBack] = createSignal("");
18 const [mediaUrl, setMediaUrl] = createSignal("");
19 const [cardType, setCardType] = createSignal<CardType>("basic");
20 const [hints, setHints] = createSignal("");
21
22 createEffect(() => {
23 if (props.front) setFront(props.front);
24 if (props.back) setBack(props.back);
25 if (props.mediaUrl) setMediaUrl(props.mediaUrl);
26 if (props.cardType) setCardType(props.cardType);
27 if (props.hints) setHints(props.hints.join(", "));
28 });
29
30 const handleSubmit = (e: Event) => {
31 e.preventDefault();
32 const hintsArray = hints().split(",").map(h => h.trim()).filter(h => h);
33 props.onSave({
34 front: front(),
35 back: back(),
36 mediaUrl: mediaUrl() || undefined,
37 cardType: cardType(),
38 hints: hintsArray,
39 });
40 if (!props.front) {
41 setFront("");
42 setBack("");
43 setMediaUrl("");
44 setCardType("basic");
45 setHints("");
46 }
47 };
48
49 return (
50 <form onSubmit={handleSubmit} class="space-y-4 p-4 border border-gray-800 rounded bg-gray-900/50">
51 <div class="flex gap-4 items-center">
52 <label class="text-sm font-medium text-gray-400">Card Type</label>
53 <div class="flex gap-4">
54 <label class="flex items-center gap-2 cursor-pointer">
55 <input
56 type="radio"
57 name="cardType"
58 value="basic"
59 checked={cardType() === "basic"}
60 onChange={() => setCardType("basic")}
61 class="text-blue-500 focus:ring-blue-500" />
62 <span class="text-gray-300">Basic</span>
63 </label>
64 <label class="flex items-center gap-2 cursor-pointer">
65 <input
66 type="radio"
67 name="cardType"
68 value="cloze"
69 checked={cardType() === "cloze"}
70 onChange={() => setCardType("cloze")}
71 class="text-blue-500 focus:ring-blue-500" />
72 <span class="text-gray-300">Cloze</span>
73 </label>
74 </div>
75 </div>
76
77 <div>
78 <label class="block text-sm font-medium text-gray-400 mb-1">
79 {cardType() === "cloze" ? "Text (use {{...}} for deletions)" : "Front"}
80 </label>
81 <textarea
82 value={front()}
83 onInput={(e) => setFront(e.target.value)}
84 class="w-full bg-gray-800 border-gray-700 text-white rounded p-2 focus:ring-blue-500 focus:border-blue-500"
85 placeholder={cardType() === "cloze" ? "The capital of France is {{Paris}}." : "Front of card..."}
86 rows={2}
87 required />
88 </div>
89
90 <Show when={cardType() === "basic"}>
91 <div>
92 <label class="block text-sm font-medium text-gray-400 mb-1">Back</label>
93 <textarea
94 value={back()}
95 onInput={(e) => setBack(e.target.value)}
96 class="w-full bg-gray-800 border-gray-700 text-white rounded p-2 focus:ring-blue-500 focus:border-blue-500"
97 placeholder="Back of card..."
98 rows={3}
99 required />
100 </div>
101 </Show>
102
103 <Show when={cardType() === "cloze"}>
104 <div class="p-3 bg-gray-800/50 rounded border border-gray-700">
105 <div class="text-xs text-gray-500 mb-1">Preview</div>
106 <div class="text-gray-300">{front().replace(/\{\{([^}]+)\}\}/g, "[...]")}</div>
107 </div>
108 </Show>
109
110 <div>
111 <label class="block text-sm font-medium text-gray-400 mb-1">Hints (comma separated, optional)</label>
112 <input
113 type="text"
114 value={hints()}
115 onInput={(e) => setHints(e.target.value)}
116 class="w-full bg-gray-800 border-gray-700 text-white rounded p-2 focus:ring-blue-500 focus:border-blue-500"
117 placeholder="First letter: P, Country in Europe..." />
118 </div>
119
120 <div>
121 <label class="block text-sm font-medium text-gray-400 mb-1">Media URL (Optional)</label>
122 <input
123 type="url"
124 value={mediaUrl()}
125 onInput={(e) => setMediaUrl(e.target.value)}
126 class="w-full bg-gray-800 border-gray-700 text-white rounded p-2 focus:ring-blue-500 focus:border-blue-500"
127 placeholder="https://..." />
128 </div>
129
130 <div class="flex justify-end gap-2">
131 <Show when={props.onCancel}>
132 <Button type="button" variant="ghost" onClick={props.onCancel}>Cancel</Button>
133 </Show>
134 <Button type="submit">{props.front ? "Update Card" : "Add Card"}</Button>
135 </div>
136 </form>
137 );
138}