+27
-52
src/components/account.tsx
+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
+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
+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
+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>