learn and share notes on atproto (wip) 馃 malfestio.stormlightlabs.org/
readability solid axum atproto srs
at main 138 lines 5.0 kB view raw
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}