Experiment to rebuild Diffuse using web applets.
1import { computed, effect, type Signal, signal } from "spellcaster";
2import { type Props, repeat, tags, text } from "spellcaster/hyperscript.js";
3
4import type { Server } from "./types.d.ts";
5import { loadServers, saveServers, serverId } from "./common";
6
7////////////////////////////////////////////
8// UI
9////////////////////////////////////////////
10export const [servers, setServers] = signal<Record<string, Server>>(await loadServers());
11const [form, setForm] = signal<{
12 api_key?: string;
13 host?: string;
14 password?: string;
15 username?: string;
16}>({});
17
18const serversMap = computed(() => {
19 return new Map(Object.entries(servers()));
20});
21
22effect(() => {
23 saveServers(servers());
24});
25
26////////////////////////////////////////////
27// UI ~ SERVERS
28////////////////////////////////////////////
29const Server = (server: Signal<Server>) => {
30 const onclick = () => {
31 const b = server();
32 const id = serverId(b);
33
34 const col = { ...servers() };
35 delete col[id];
36
37 setServers(col);
38 };
39
40 return tags.li({ onclick, style: "cursor: pointer" }, text(server().host));
41};
42
43const ServerList = computed(() => {
44 if (serversMap().size === 0) {
45 return tags.p({ id: "servers" }, [tags.small({}, text("Nothing added so far."))]);
46 }
47
48 return tags.ul({ id: "servers" }, repeat(serversMap, Server));
49});
50
51effect(() => {
52 document.querySelector("#servers")?.replaceWith(ServerList());
53});
54
55////////////////////////////////////////////
56// UI ~ FORM
57////////////////////////////////////////////
58function addServer(event: Event) {
59 event.preventDefault();
60
61 const f = form();
62
63 const server: Server = {
64 apiKey: f.api_key,
65 host: f.host?.replace(/^https?:\/\//, "").replace(/\/+$/, "") || "localhost:4533",
66 username: f.username,
67 tls: f.host?.startsWith("http://") || f.host?.startsWith("localhost") ? false : true,
68 password: f.password,
69 };
70
71 setServers({
72 ...servers(),
73 [serverId(server)]: server,
74 });
75}
76
77function Form() {
78 return tags.form({ onsubmit: addServer }, [
79 tags.fieldset({ className: "grid" }, [
80 Input("host", "Server host", "my.opensubsonic.server:4747", { required: true }),
81 ]),
82 tags.fieldset({ className: "grid" }, [
83 Input("username", "Server name", "username", { required: true }),
84 Input("password", "Password", "password", { required: true, type: "password" }),
85 ]),
86 tags.fieldset({ className: "grid" }, [tags.input({ type: "submit", value: "Connect" }, [])]),
87 ]);
88}
89
90function Input(name: string, label: string, placeholder: string, opts: Props = {}) {
91 return tags.label({}, [
92 tags.span({}, [
93 tags.span({}, text(label)),
94 tags.small({}, text("required" in opts ? "" : " (optional)")),
95 ]),
96 tags.input({
97 ...opts,
98 name,
99 placeholder,
100 oninput: (event: InputEvent) => formInput(name, (event.target as HTMLInputElement).value),
101 }),
102 ]);
103}
104
105function formInput(name: string, value: string) {
106 setForm({ ...form(), [name]: value });
107}
108
109// 🚀
110document.querySelector("#form")?.replaceWith(Form());