forked from pdsls.dev/pdsls
this repo has no description

add modal component

Changed files
+282 -312
src
+27 -52
src/components/account.tsx
··· 1 - import { createSignal, onMount, Show, onCleanup, createEffect, For } from "solid-js"; 1 + import { createSignal, onMount, For } from "solid-js"; 2 2 import Tooltip from "./tooltip.jsx"; 3 3 import { deleteStoredSession, getSession, OAuthUserAgent } from "@atcute/oauth-browser-client"; 4 4 import { agent, Login, loginState, setLoginState } from "./login.jsx"; ··· 6 6 import { resolveDidDoc } from "../utils/api.js"; 7 7 import { createStore } from "solid-js/store"; 8 8 import { Client, CredentialManager } from "@atcute/client"; 9 + import { Modal } from "./modal.jsx"; 9 10 10 11 const AccountManager = () => { 11 - const [modal, setModal] = createSignal<HTMLDialogElement>(); 12 12 const [openManager, setOpenManager] = createSignal(false); 13 13 const [sessions, setSessions] = createStore<Record<string, string | undefined>>(); 14 14 const [avatar, setAvatar] = createSignal<string>(); 15 15 16 - const clickEvent = (event: MouseEvent) => { 17 - if (modal() && event.target == modal()) setOpenManager(false); 18 - }; 19 - const keyEvent = (event: KeyboardEvent) => { 20 - if (modal() && event.key == "Escape") setOpenManager(false); 21 - }; 22 - 23 16 onMount(async () => { 24 - window.addEventListener("keydown", keyEvent); 25 - window.addEventListener("click", clickEvent); 26 - 27 17 const storedSessions = localStorage.getItem("atcute-oauth:sessions"); 28 18 if (storedSessions) { 29 19 const sessionDids = Object.keys(JSON.parse(storedSessions)) as Did[]; ··· 43 33 if (repo) setAvatar(await getAvatar(repo as Did)); 44 34 }); 45 35 46 - onCleanup(() => { 47 - window.removeEventListener("keydown", keyEvent); 48 - window.removeEventListener("click", clickEvent); 49 - }); 50 - 51 - createEffect(() => { 52 - if (openManager()) document.body.style.overflow = "hidden"; 53 - else document.body.style.overflow = "auto"; 54 - }); 55 - 56 36 const resumeSession = (did: Did) => { 57 37 localStorage.setItem("lastSignedIn", did); 58 38 window.location.href = "/"; ··· 87 67 88 68 return ( 89 69 <> 90 - <Show when={openManager()}> 91 - <dialog 92 - ref={setModal} 93 - class="fixed left-0 top-0 z-20 flex h-screen w-screen items-center justify-center bg-transparent" 94 - > 95 - <div class="starting:opacity-0 dark:bg-dark-800/70 backdrop-blur-xs border-0.5 dark:shadow-dark-900 absolute top-12 rounded-md border-neutral-300 bg-zinc-200/70 p-4 text-slate-900 shadow-md transition-opacity duration-300 dark:border-neutral-700 dark:text-slate-100"> 96 - <h3 class="mb-2 font-bold">Manage accounts</h3> 97 - <div class="border-b-0.5 mb-2 max-h-[20rem] overflow-y-auto border-neutral-500 pb-2 md:max-h-[25rem]"> 98 - <For each={Object.keys(sessions)}> 99 - {(did) => ( 100 - <div class="group/select flex w-full items-center justify-between gap-x-2"> 101 - <button 102 - classList={{ 103 - "bg-transparent basis-full text-left max-w-[32ch] truncate group-hover/select:bg-zinc-200 px-1 rounded dark:group-hover/select:bg-neutral-600": true, 104 - "text-blue-500 dark:text-blue-400 font-bold": did === agent?.sub, 105 - }} 106 - onclick={() => resumeSession(did as Did)} 107 - > 108 - {sessions[did]?.length ? sessions[did] : did} 109 - </button> 110 - <button onclick={() => removeSession(did as Did)}> 111 - <div class="i-lucide-x text-xl text-red-500 hover:text-red-600 dark:text-red-400 dark:hover:text-red-500" /> 112 - </button> 113 - </div> 114 - )} 115 - </For> 116 - </div> 117 - <Login /> 70 + <Modal open={openManager()} onClose={() => setOpenManager(false)}> 71 + <div class="starting:opacity-0 dark:bg-dark-800/70 border-0.5 dark:shadow-dark-900 backdrop-blur-xs left-50% absolute top-12 -translate-x-1/2 rounded-md border-neutral-300 bg-zinc-200/70 p-4 text-slate-900 shadow-md transition-opacity duration-300 dark:border-neutral-700 dark:text-slate-100"> 72 + <h3 class="mb-2 font-bold">Manage accounts</h3> 73 + <div class="border-b-0.5 mb-2 max-h-[20rem] overflow-y-auto border-neutral-500 pb-2 md:max-h-[25rem]"> 74 + <For each={Object.keys(sessions)}> 75 + {(did) => ( 76 + <div class="group/select flex w-full items-center justify-between gap-x-2"> 77 + <button 78 + classList={{ 79 + "bg-transparent basis-full text-left max-w-[32ch] truncate group-hover/select:bg-zinc-200 px-1 rounded dark:group-hover/select:bg-neutral-600": true, 80 + "text-blue-500 dark:text-blue-400 font-bold": did === agent?.sub, 81 + }} 82 + onclick={() => resumeSession(did as Did)} 83 + > 84 + {sessions[did]?.length ? sessions[did] : did} 85 + </button> 86 + <button onclick={() => removeSession(did as Did)}> 87 + <div class="i-lucide-x text-xl text-red-500 hover:text-red-600 dark:text-red-400 dark:hover:text-red-500" /> 88 + </button> 89 + </div> 90 + )} 91 + </For> 118 92 </div> 119 - </dialog> 120 - </Show> 93 + <Login /> 94 + </div> 95 + </Modal> 121 96 <button onclick={() => setOpenManager(true)}> 122 97 <Tooltip text="Accounts"> 123 98 {loginState() && avatar() ?
+97 -116
src/components/create.tsx
··· 1 - import { createSignal, onMount, Show, onCleanup, createEffect } from "solid-js"; 1 + import { createSignal, Show } from "solid-js"; 2 2 import { Client } from "@atcute/client"; 3 3 import { agent } from "../components/login.jsx"; 4 4 import { editor, Editor } from "../components/editor.jsx"; ··· 9 9 import { useParams } from "@solidjs/router"; 10 10 import { remove } from "@mary/exif-rm"; 11 11 import { TextInput } from "./text-input.jsx"; 12 + import { Modal } from "./modal.jsx"; 12 13 13 14 export const RecordEditor = (props: { create: boolean; record?: any }) => { 14 15 const params = useParams(); 15 - const [modal, setModal] = createSignal<HTMLDialogElement>(); 16 16 const [openDialog, setOpenDialog] = createSignal(false); 17 17 const [notice, setNotice] = createSignal(""); 18 18 const [uploading, setUploading] = createSignal(false); ··· 35 35 createdAt: date, 36 36 }; 37 37 }; 38 - 39 - const keyEvent = (event: KeyboardEvent) => { 40 - if (modal() && event.key == "Escape") setOpenDialog(false); 41 - }; 42 - 43 - onMount(() => window.addEventListener("keydown", keyEvent)); 44 - 45 - onCleanup(() => window.removeEventListener("keydown", keyEvent)); 46 38 47 39 const createRecord = async (formData: FormData) => { 48 40 const rpc = new Client({ handler: agent }); ··· 169 161 editor.trigger("editor", "editor.action.formatDocument", {}); 170 162 }; 171 163 172 - createEffect(() => { 173 - if (openDialog()) document.body.style.overflow = "hidden"; 174 - else document.body.style.overflow = "auto"; 175 - setNotice(""); 176 - }); 177 - 178 164 const createModel = () => { 179 165 if (!model) 180 166 model = monaco.editor.createModel( ··· 189 175 190 176 return ( 191 177 <> 192 - <Show when={openDialog()}> 193 - <dialog 194 - ref={setModal} 195 - class="fixed left-0 top-0 z-20 flex h-screen w-screen items-center justify-center bg-transparent" 196 - > 197 - <div class="w-21rem sm:w-xl lg:w-50rem starting:opacity-0 dark:bg-dark-800/70 backdrop-blur-xs border-0.5 dark:shadow-dark-900 absolute top-12 rounded-md border-neutral-300 bg-zinc-200/70 p-2 text-slate-900 shadow-md transition-opacity duration-300 sm:p-4 dark:border-neutral-700 dark:text-slate-100"> 198 - <div class="mb-2 flex w-full justify-between"> 199 - <h3 class="font-bold">{props.create ? "Creating" : "Editing"} record</h3> 200 - <div 201 - class="i-lucide-x text-xl hover:text-red-500 dark:hover:text-red-400" 202 - onclick={() => setOpenDialog(false)} 203 - /> 204 - </div> 205 - <form ref={formRef} class="flex flex-col gap-y-2"> 206 - <div class="flex w-fit flex-col gap-y-1 text-xs sm:text-sm"> 207 - <Show when={props.create}> 208 - <div class="flex items-center gap-x-2"> 209 - <label for="collection" class="min-w-20 select-none"> 210 - Collection 211 - </label> 212 - <TextInput 213 - id="collection" 214 - name="collection" 215 - placeholder="Optional (default: record type)" 216 - class="w-14rem" 178 + <Modal open={openDialog()} onClose={() => setOpenDialog(false)}> 179 + <div class="w-21rem sm:w-xl lg:w-50rem starting:opacity-0 dark:bg-dark-800/70 left-50% backdrop-blur-xs border-0.5 dark:shadow-dark-900 absolute top-12 -translate-x-1/2 rounded-md border-neutral-300 bg-zinc-200/70 p-2 text-slate-900 shadow-md transition-opacity duration-300 sm:p-4 dark:border-neutral-700 dark:text-slate-100"> 180 + <div class="mb-2 flex w-full justify-between"> 181 + <h3 class="font-bold">{props.create ? "Creating" : "Editing"} record</h3> 182 + <div 183 + class="i-lucide-x text-xl hover:text-red-500 dark:hover:text-red-400" 184 + onclick={() => setOpenDialog(false)} 185 + /> 186 + </div> 187 + <form ref={formRef} class="flex flex-col gap-y-2"> 188 + <div class="flex w-fit flex-col gap-y-1 text-xs sm:text-sm"> 189 + <Show when={props.create}> 190 + <div class="flex items-center gap-x-2"> 191 + <label for="collection" class="min-w-20 select-none"> 192 + Collection 193 + </label> 194 + <TextInput 195 + id="collection" 196 + name="collection" 197 + placeholder="Optional (default: record type)" 198 + class="w-14rem" 199 + /> 200 + </div> 201 + <div class="flex items-center gap-x-2"> 202 + <label for="rkey" class="min-w-20 select-none"> 203 + Record key 204 + </label> 205 + <TextInput id="rkey" name="rkey" placeholder="Optional" class="w-14rem" /> 206 + </div> 207 + </Show> 208 + <div class="flex items-center gap-x-2"> 209 + <label for="validate" class="min-w-20 select-none"> 210 + Validate 211 + </label> 212 + <select 213 + name="validate" 214 + id="validate" 215 + class="dark:bg-dark-100 focus:outline-1.5 dark:shadow-dark-900 rounded-lg bg-white px-1 py-1 shadow-sm focus:outline-blue-500" 216 + > 217 + <option value="unset">Unset</option> 218 + <option value="true">True</option> 219 + <option value="false">False</option> 220 + </select> 221 + </div> 222 + <div class="flex flex-row items-center gap-2"> 223 + <Show when={!uploading()}> 224 + <Tooltip text="Upload file"> 225 + <input 226 + type="file" 227 + title="" 228 + id="blob" 229 + class="i-lucide-upload text-xl" 230 + onChange={() => uploadBlob()} 217 231 /> 218 - </div> 219 - <div class="flex items-center gap-x-2"> 220 - <label for="rkey" class="min-w-20 select-none"> 221 - Record key 222 - </label> 223 - <TextInput id="rkey" name="rkey" placeholder="Optional" class="w-14rem" /> 224 - </div> 232 + </Tooltip> 233 + <p>Blob metadata will be pasted after the cursor</p> 234 + </Show> 235 + <Show when={uploading()}> 236 + <div class="i-lucide-loader-circle animate-spin text-xl" /> 225 237 </Show> 238 + </div> 239 + <div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between"> 226 240 <div class="flex items-center gap-x-2"> 227 - <label for="validate" class="min-w-20 select-none"> 228 - Validate 241 + <label for="mimetype" class="min-w-20 select-none"> 242 + MIME type 229 243 </label> 230 - <select 231 - name="validate" 232 - id="validate" 233 - class="dark:bg-dark-100 focus:outline-1.5 dark:shadow-dark-900 rounded-lg bg-white px-1 py-1 shadow-sm focus:outline-blue-500" 234 - > 235 - <option value="unset">Unset</option> 236 - <option value="true">True</option> 237 - <option value="false">False</option> 238 - </select> 244 + <TextInput id="mimetype" placeholder="Optional" class="w-14rem" /> 239 245 </div> 240 - <div class="flex flex-row items-center gap-2"> 241 - <Show when={!uploading()}> 242 - <Tooltip text="Upload file"> 243 - <input 244 - type="file" 245 - title="" 246 - id="blob" 247 - class="i-lucide-upload text-xl" 248 - onChange={() => uploadBlob()} 249 - /> 250 - </Tooltip> 251 - <p>Blob metadata will be pasted after the cursor</p> 252 - </Show> 253 - <Show when={uploading()}> 254 - <div class="i-lucide-loader-circle animate-spin text-xl" /> 255 - </Show> 246 + <div class="flex items-center gap-1"> 247 + <input id="exif-rm" class="size-4" type="checkbox" checked /> 248 + <label for="exif-rm" class="select-none"> 249 + Remove EXIF data 250 + </label> 256 251 </div> 257 - <div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between"> 258 - <div class="flex items-center gap-x-2"> 259 - <label for="mimetype" class="min-w-20 select-none"> 260 - MIME type 261 - </label> 262 - <TextInput id="mimetype" placeholder="Optional" class="w-14rem" /> 263 - </div> 252 + </div> 253 + </div> 254 + <Editor theme={theme().color} model={model!} /> 255 + <div class="flex flex-col gap-2"> 256 + <Show when={notice()}> 257 + <div class="text-red-500 dark:text-red-400">{notice()}</div> 258 + </Show> 259 + <div class="flex items-center justify-end gap-2"> 260 + <Show when={!props.create}> 264 261 <div class="flex items-center gap-1"> 265 - <input id="exif-rm" class="size-4" type="checkbox" checked /> 266 - <label for="exif-rm" class="select-none"> 267 - Remove EXIF data 262 + <input id="recreate" class="size-4" name="recreate" type="checkbox" /> 263 + <label for="recreate" class="select-none"> 264 + Recreate record 268 265 </label> 269 266 </div> 270 - </div> 271 - </div> 272 - <Editor theme={theme().color} model={model!} /> 273 - <div class="flex flex-col gap-2"> 274 - <Show when={notice()}> 275 - <div class="text-red-500 dark:text-red-400">{notice()}</div> 276 267 </Show> 277 - <div class="flex items-center justify-end gap-2"> 278 - <Show when={!props.create}> 279 - <div class="flex items-center gap-1"> 280 - <input id="recreate" class="size-4" name="recreate" type="checkbox" /> 281 - <label for="recreate" class="select-none"> 282 - Recreate record 283 - </label> 284 - </div> 285 - </Show> 286 - <button 287 - type="button" 288 - onclick={() => 289 - props.create ? 290 - createRecord(new FormData(formRef)) 291 - : editRecord(new FormData(formRef)) 292 - } 293 - class="focus:outline-1.5 dark:shadow-dark-900 rounded-lg bg-blue-500 px-2 py-1.5 text-xs font-bold text-slate-100 shadow-sm hover:bg-blue-400 focus:outline-blue-500 sm:text-sm dark:bg-blue-600 dark:hover:bg-blue-500" 294 - > 295 - Confirm 296 - </button> 297 - </div> 268 + <button 269 + type="button" 270 + onclick={() => 271 + props.create ? 272 + createRecord(new FormData(formRef)) 273 + : editRecord(new FormData(formRef)) 274 + } 275 + class="focus:outline-1.5 dark:shadow-dark-900 rounded-lg bg-blue-500 px-2 py-1.5 text-xs font-bold text-slate-100 shadow-sm hover:bg-blue-400 focus:outline-blue-500 sm:text-sm dark:bg-blue-600 dark:hover:bg-blue-500" 276 + > 277 + Confirm 278 + </button> 298 279 </div> 299 - </form> 300 - </div> 301 - </dialog> 302 - </Show> 280 + </div> 281 + </form> 282 + </div> 283 + </Modal> 303 284 <Show when={props.create}> 304 285 <button 305 286 onclick={() => {
+34
src/components/modal.tsx
··· 1 + import { ComponentProps, onCleanup, onMount, Show } from "solid-js"; 2 + 3 + export interface ModalProps extends Pick<ComponentProps<"svg">, "children"> { 4 + open?: boolean; 5 + onClose?: () => void; 6 + } 7 + 8 + export const Modal = (props: ModalProps) => { 9 + return ( 10 + <Show when={props.open}> 11 + <dialog 12 + ref={(node) => { 13 + onMount(() => { 14 + document.body.style.overflow = "hidden"; 15 + node.showModal(); 16 + }); 17 + onCleanup(() => node.close()); 18 + }} 19 + onClick={(ev) => { 20 + if (ev.target === ev.currentTarget) { 21 + if (props.onClose) props.onClose(); 22 + } 23 + }} 24 + onClose={() => { 25 + document.body.style.overflow = "auto"; 26 + if (props.onClose) props.onClose(); 27 + }} 28 + class="h-full max-h-none w-full max-w-none bg-transparent backdrop:bg-transparent" 29 + > 30 + {props.children} 31 + </dialog> 32 + </Show> 33 + ); 34 + };
+124 -144
src/components/settings.tsx
··· 1 - import { createSignal, onMount, Show, onCleanup, createEffect } from "solid-js"; 1 + import { createSignal, onMount, Show, onCleanup } from "solid-js"; 2 2 import Tooltip from "./tooltip.jsx"; 3 3 import { TextInput } from "./text-input.jsx"; 4 + import { Modal } from "./modal.jsx"; 4 5 5 6 const getInitialTheme = () => { 6 7 const isDarkMode = ··· 21 22 export const [kawaii, setKawaii] = createSignal(localStorage.kawaii === "true"); 22 23 23 24 const Settings = () => { 24 - const [modal, setModal] = createSignal<HTMLDialogElement>(); 25 25 const [openSettings, setOpenSettings] = createSignal(false); 26 26 27 - const clickEvent = (event: MouseEvent) => { 28 - if (modal() && event.target == modal()) setOpenSettings(false); 29 - }; 30 - const keyEvent = (event: KeyboardEvent) => { 31 - if (modal() && event.key == "Escape") setOpenSettings(false); 32 - }; 33 27 const themeEvent = () => { 34 28 if (!theme().system) return; 35 29 const isDark = window.matchMedia("(prefers-color-scheme: dark)").matches; ··· 42 36 }; 43 37 44 38 onMount(() => { 45 - window.addEventListener("keydown", keyEvent); 46 - window.addEventListener("click", clickEvent); 47 39 window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", themeEvent); 48 40 }); 49 41 50 42 onCleanup(() => { 51 - window.removeEventListener("keydown", keyEvent); 52 - window.removeEventListener("click", clickEvent); 53 43 window.matchMedia("(prefers-color-scheme: dark)").removeEventListener("change", themeEvent); 54 44 }); 55 45 56 - createEffect(() => { 57 - if (openSettings()) document.body.style.overflow = "hidden"; 58 - else document.body.style.overflow = "auto"; 59 - }); 60 - 61 46 const updateTheme = (newTheme: { color: string; system: boolean }) => { 62 47 setTheme(newTheme); 63 48 document.documentElement.classList.toggle("dark", newTheme.color === "dark"); ··· 70 55 71 56 return ( 72 57 <> 73 - <Show when={openSettings()}> 74 - <dialog 75 - ref={setModal} 76 - class="fixed left-0 top-0 z-20 flex h-screen w-screen items-center justify-center bg-transparent" 77 - > 78 - <div class="starting:opacity-0 dark:bg-dark-800/70 border-0.5 dark:shadow-dark-900 backdrop-blur-xs absolute top-12 rounded-md border-neutral-300 bg-zinc-200/70 p-4 text-slate-900 shadow-md transition-opacity duration-300 dark:border-neutral-700 dark:text-slate-100"> 79 - <h3 class="border-b-0.5 mb-2 border-neutral-500 pb-2 font-bold">Settings</h3> 80 - <h4 class="mb-1 font-semibold">Theme</h4> 81 - <div class="w-xs flex gap-2"> 82 - <button 83 - classList={{ 84 - "basis-1/3 py-1 rounded-lg": true, 85 - "bg-transparent hover:bg-neutral-200 dark:hover:bg-dark-200": !theme().system, 86 - "bg-neutral-300 dark:bg-neutral-600 font-semibold": theme().system, 58 + <Modal open={openSettings()} onClose={() => setOpenSettings(false)}> 59 + <div class="starting:opacity-0 dark:bg-dark-800/70 border-0.5 dark:shadow-dark-900 backdrop-blur-xs left-50% absolute top-12 -translate-x-1/2 rounded-md border-neutral-300 bg-zinc-200/70 p-4 text-slate-900 shadow-md transition-opacity duration-300 dark:border-neutral-700 dark:text-slate-100"> 60 + <h3 class="border-b-0.5 mb-2 border-neutral-500 pb-2 font-bold">Settings</h3> 61 + <h4 class="mb-1 font-semibold">Theme</h4> 62 + <div class="w-xs flex gap-2"> 63 + <button 64 + classList={{ 65 + "basis-1/3 py-1 rounded-lg": true, 66 + "bg-transparent hover:bg-neutral-200 dark:hover:bg-dark-200": !theme().system, 67 + "bg-neutral-300 dark:bg-neutral-600 font-semibold": theme().system, 68 + }} 69 + onclick={() => 70 + updateTheme({ 71 + color: 72 + window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light", 73 + system: true, 74 + }) 75 + } 76 + > 77 + System 78 + </button> 79 + <button 80 + classList={{ 81 + "basis-1/3 py-1 rounded-lg": true, 82 + "bg-transparent hover:bg-neutral-200 dark:hover:bg-dark-200": 83 + theme().color !== "light" || theme().system, 84 + "bg-neutral-300 font-semibold": theme().color === "light" && !theme().system, 85 + }} 86 + onclick={() => updateTheme({ color: "light", system: false })} 87 + > 88 + Light 89 + </button> 90 + <button 91 + classList={{ 92 + "basis-1/3 py-1 rounded-lg": true, 93 + "bg-transparent hover:bg-neutral-200 dark:hover:bg-dark-200": 94 + theme().color !== "dark" || theme().system, 95 + "bg-neutral-600 font-semibold": theme().color === "dark" && !theme().system, 96 + }} 97 + onclick={() => updateTheme({ color: "dark", system: false })} 98 + > 99 + Dark 100 + </button> 101 + </div> 102 + <div class="border-t-0.5 mt-4 flex flex-col gap-1 border-neutral-500 pt-2"> 103 + <div class="flex items-center gap-1"> 104 + <input 105 + id="backlinks" 106 + class="size-4" 107 + type="checkbox" 108 + checked={localStorage.backlinks === "true"} 109 + onChange={(e) => { 110 + localStorage.backlinks = e.currentTarget.checked; 111 + setBacklinksEnabled(e.currentTarget.checked); 87 112 }} 88 - onclick={() => 89 - updateTheme({ 90 - color: 91 - window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light", 92 - system: true, 93 - }) 94 - } 95 - > 96 - System 97 - </button> 98 - <button 113 + /> 114 + <label for="backlinks" class="select-none font-semibold"> 115 + Backlinks 116 + </label> 117 + <div class="i-lucide-send-to-back" /> 118 + </div> 119 + <div class="flex flex-col gap-1"> 120 + <label 121 + for="constellation" 99 122 classList={{ 100 - "basis-1/3 py-1 rounded-lg": true, 101 - "bg-transparent hover:bg-neutral-200 dark:hover:bg-dark-200": 102 - theme().color !== "light" || theme().system, 103 - "bg-neutral-300 font-semibold": theme().color === "light" && !theme().system, 123 + "select-none": true, 124 + "text-gray-500": !backlinksEnabled(), 104 125 }} 105 - onclick={() => updateTheme({ color: "light", system: false })} 106 126 > 107 - Light 108 - </button> 109 - <button 110 - classList={{ 111 - "basis-1/3 py-1 rounded-lg": true, 112 - "bg-transparent hover:bg-neutral-200 dark:hover:bg-dark-200": 113 - theme().color !== "dark" || theme().system, 114 - "bg-neutral-600 font-semibold": theme().color === "dark" && !theme().system, 127 + Constellation host 128 + </label> 129 + <TextInput 130 + id="constellation" 131 + value={localStorage.constellationHost || "https://constellation.microcosm.blue"} 132 + disabled={!backlinksEnabled()} 133 + class="disabled:bg-gray-50 disabled:text-gray-500 dark:disabled:bg-gray-800/20" 134 + onInput={(e) => { 135 + e.currentTarget.value.length ? 136 + (localStorage.constellationHost = e.currentTarget.value) 137 + : localStorage.removeItem("constellationHost"); 115 138 }} 116 - onclick={() => updateTheme({ color: "dark", system: false })} 117 - > 118 - Dark 119 - </button> 139 + /> 120 140 </div> 121 - <div class="border-t-0.5 mt-4 flex flex-col gap-1 border-neutral-500 pt-2"> 141 + <div class="border-t-0.5 mt-2 flex flex-col gap-1 border-neutral-500 pt-2"> 142 + <div class="flex flex-col gap-1"> 143 + <label for="plcDirectory" class="select-none font-semibold"> 144 + PLC Directory 145 + </label> 146 + <TextInput 147 + id="plcDirectory" 148 + value={localStorage.plcDirectory || "https://plc.directory"} 149 + onInput={(e) => { 150 + e.currentTarget.value.length ? 151 + (localStorage.plcDirectory = e.currentTarget.value) 152 + : localStorage.removeItem("plcDirectory"); 153 + }} 154 + /> 155 + </div> 156 + </div> 157 + <div class="border-t-0.5 mt-2 flex flex-col gap-1 border-neutral-500 pt-2"> 122 158 <div class="flex items-center gap-1"> 123 159 <input 124 - id="backlinks" 160 + id="showHandle" 125 161 class="size-4" 126 162 type="checkbox" 127 - checked={localStorage.backlinks === "true"} 163 + checked={localStorage.showHandle === "true"} 128 164 onChange={(e) => { 129 - localStorage.backlinks = e.currentTarget.checked; 130 - setBacklinksEnabled(e.currentTarget.checked); 165 + localStorage.showHandle = e.currentTarget.checked; 166 + setShowHandle(e.currentTarget.checked); 131 167 }} 132 168 /> 133 - <label for="backlinks" class="select-none font-semibold"> 134 - Backlinks 169 + <label for="showHandle" class="select-none"> 170 + Default to showing handle 135 171 </label> 136 - <div class="i-lucide-send-to-back" /> 137 172 </div> 138 - <div class="flex flex-col gap-1"> 139 - <label 140 - for="constellation" 141 - classList={{ 142 - "select-none": true, 143 - "text-gray-500": !backlinksEnabled(), 144 - }} 145 - > 146 - Constellation host 147 - </label> 148 - <TextInput 149 - id="constellation" 150 - value={localStorage.constellationHost || "https://constellation.microcosm.blue"} 151 - disabled={!backlinksEnabled()} 152 - class="disabled:bg-gray-50 disabled:text-gray-500 dark:disabled:bg-gray-800/20" 153 - onInput={(e) => { 154 - e.currentTarget.value.length ? 155 - (localStorage.constellationHost = e.currentTarget.value) 156 - : localStorage.removeItem("constellationHost"); 173 + <div class="flex items-center gap-1"> 174 + <input 175 + id="disableMedia" 176 + class="size-4" 177 + type="checkbox" 178 + checked={localStorage.hideMedia === "true"} 179 + onChange={(e) => { 180 + localStorage.hideMedia = e.currentTarget.checked; 181 + setHideMedia(e.currentTarget.checked); 157 182 }} 158 183 /> 159 - </div> 160 - <div class="border-t-0.5 mt-2 flex flex-col gap-1 border-neutral-500 pt-2"> 161 - <div class="flex flex-col gap-1"> 162 - <label for="plcDirectory" class="select-none font-semibold"> 163 - PLC Directory 164 - </label> 165 - <TextInput 166 - id="plcDirectory" 167 - value={localStorage.plcDirectory || "https://plc.directory"} 168 - onInput={(e) => { 169 - e.currentTarget.value.length ? 170 - (localStorage.plcDirectory = e.currentTarget.value) 171 - : localStorage.removeItem("plcDirectory"); 172 - }} 173 - /> 174 - </div> 184 + <label for="disableMedia" class="select-none"> 185 + Hide media embeds 186 + </label> 175 187 </div> 176 - <div class="border-t-0.5 mt-2 flex flex-col gap-1 border-neutral-500 pt-2"> 188 + <Show when={localStorage.kawaii}> 177 189 <div class="flex items-center gap-1"> 178 190 <input 179 - id="showHandle" 191 + id="enableKawaii" 180 192 class="size-4" 181 193 type="checkbox" 182 - checked={localStorage.showHandle === "true"} 194 + checked={localStorage.kawaii === "true"} 183 195 onChange={(e) => { 184 - localStorage.showHandle = e.currentTarget.checked; 185 - setShowHandle(e.currentTarget.checked); 196 + localStorage.kawaii = e.currentTarget.checked; 197 + setKawaii(e.currentTarget.checked); 186 198 }} 187 199 /> 188 - <label for="showHandle" class="select-none"> 189 - Default to showing handle 200 + <label for="enableKawaii" class="select-none"> 201 + Kawaii mode 190 202 </label> 191 203 </div> 192 - <div class="flex items-center gap-1"> 193 - <input 194 - id="disableMedia" 195 - class="size-4" 196 - type="checkbox" 197 - checked={localStorage.hideMedia === "true"} 198 - onChange={(e) => { 199 - localStorage.hideMedia = e.currentTarget.checked; 200 - setHideMedia(e.currentTarget.checked); 201 - }} 202 - /> 203 - <label for="disableMedia" class="select-none"> 204 - Hide media embeds 205 - </label> 206 - </div> 207 - <Show when={localStorage.kawaii}> 208 - <div class="flex items-center gap-1"> 209 - <input 210 - id="enableKawaii" 211 - class="size-4" 212 - type="checkbox" 213 - checked={localStorage.kawaii === "true"} 214 - onChange={(e) => { 215 - localStorage.kawaii = e.currentTarget.checked; 216 - setKawaii(e.currentTarget.checked); 217 - }} 218 - /> 219 - <label for="enableKawaii" class="select-none"> 220 - Kawaii mode 221 - </label> 222 - </div> 223 - </Show> 224 - </div> 204 + </Show> 225 205 </div> 226 206 </div> 227 - </dialog> 228 - </Show> 207 + </div> 208 + </Modal> 229 209 <button onclick={() => setOpenSettings(true)}> 230 210 <Tooltip text="Settings" children={<div class="i-lucide-settings text-xl" />} /> 231 211 </button>