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 { Bucket } from "./types";
5import { bucketId, loadBuckets, saveBuckets } from "./common";
6
7////////////////////////////////////////////
8// UI
9////////////////////////////////////////////
10export const [buckets, setBuckets] = signal<Record<string, Bucket>>(await loadBuckets());
11export const [form, setForm] = signal<{
12 access_key?: string;
13 bucket_name?: string;
14 host?: string;
15 path?: string;
16 region?: string;
17 secret_key?: string;
18}>({});
19
20export const bucketsMap = computed(() => {
21 return new Map(Object.entries(buckets()));
22});
23
24effect(() => {
25 saveBuckets(buckets());
26});
27
28////////////////////////////////////////////
29// UI ~ BUCKETS
30////////////////////////////////////////////
31const Bucket = (bucket: Signal<Bucket>) => {
32 const onclick = () => {
33 const b = bucket();
34 const id = bucketId(b);
35
36 const col = { ...buckets() };
37 delete col[id];
38
39 setBuckets(col);
40 };
41
42 return tags.li({ onclick, style: "cursor: pointer" }, text(bucket().host));
43};
44
45const BucketList = computed(() => {
46 if (bucketsMap().size === 0) {
47 return tags.p({ id: "buckets" }, [tags.small({}, text("Nothing added so far."))]);
48 }
49
50 return tags.ul({ id: "buckets" }, repeat(bucketsMap, Bucket));
51});
52
53effect(() => {
54 document.querySelector("#buckets")?.replaceWith(BucketList());
55});
56
57////////////////////////////////////////////
58// UI ~ FORM
59////////////////////////////////////////////
60function addBucket(event: Event) {
61 event.preventDefault();
62
63 const f = form();
64
65 const bucket: Bucket = {
66 accessKey: f.access_key || "",
67 bucketName: f.bucket_name || "",
68 host: f.host || "s3.amazonaws.com",
69 path: f.path || "/",
70 region: f.region || "us-east-1",
71 secretKey: f.secret_key || "",
72 };
73
74 setBuckets({
75 ...buckets(),
76 [bucketId(bucket)]: bucket,
77 });
78}
79
80function Form() {
81 return tags.form({ onsubmit: addBucket }, [
82 tags.fieldset({ className: "grid" }, [
83 Input("access_key", "Access key", "r31w7m9c", { required: true }),
84 Input("secret_key", "Secret key", "v02g2l29", { required: true }),
85 ]),
86 tags.fieldset({ className: "grid" }, [
87 Input("bucket_name", "Bucket name", "bucket", { required: true }),
88 Input("region", "Region", "us-east-1", { required: true }),
89 ]),
90 tags.fieldset({ className: "grid" }, [
91 Input("host", "Host", "s3.amazonaws.com", { required: true }),
92 Input("path", "Path", "/"),
93 ]),
94 tags.fieldset({ className: "grid" }, [tags.input({ type: "submit", value: "Connect" }, [])]),
95 ]);
96}
97
98function Input(name: string, label: string, placeholder: string, opts: Props = {}) {
99 return tags.label({}, [
100 tags.span({}, [
101 tags.span({}, text(label)),
102 tags.small({}, text("required" in opts ? "" : " (optional)")),
103 ]),
104 tags.input({
105 ...opts,
106 name,
107 placeholder,
108 oninput: (event: InputEvent) => formInput(name, (event.target as HTMLInputElement).value),
109 }),
110 ]);
111}
112
113function formInput(name: string, value: string) {
114 setForm({ ...form(), [name]: value });
115}
116
117// 🚀
118document.querySelector("#form")?.replaceWith(Form());