Something went wrong. Try again.
Experiment to rebuild Diffuse using web applets.
Something went wrong. Try again.
6.8 kB · 252 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253import { type Signal, computed, effect, signal } from "spellcaster/spellcaster.js";import { type ElementConfigurator, h, repeat, text } from "spellcaster/hyperscript.js";
import { applet, hs, reactive } from "@scripts/applet/common";import { CUSTOM_KEY } from "./constants";import { active, setActive } from "./signals";import { connection } from "./connections";import { context } from "./context";import type { List, ListItem, Method } from "./types";import { setContextData } from "./events";
// const h = (// tag: string,// props?: Record<string, any> | Signal<Record<string, any>>,// configure?: ElementConfigurator,// ) => hs(tag, scope, props, configure);
////////////////////////////////////////////// EFFECTS////////////////////////////////////////////reactive( context.scope, (data) => data.tracks.cacheId, () => { // Export data URI const dl = document.querySelector("#download"); if (dl) { const json = JSON.stringify(context.data.tracks.collection, null, 2); const href = URL.createObjectURL(new Blob([json], { type: "application/json" })); dl.setAttribute("href", href); } },);
// Mount + Unmountasync function mountStorageMethod(method: Method) { switch (method) { case "custom": setModalIsOpen(true); break; default: const conn = await connection(method); try { await conn.sendAction("mount", undefined, { timeoutDuration: 60000 }); setActive(method); } catch (err) { const msg: string = err && typeof err === "object" && "message" in err ? `${err.message}` : `${err}`; if (msg.startsWith("[user] ")) alert(msg.slice(7)); } break; }}
async function unmountStorageMethod(method: Method) { const conn = await connection(method); conn.removeEventListener("data", setContextData); await conn.sendAction("unmount", undefined, { timeoutDuration: 60000 });}
////////////////////////////////////////////// LIST////////////////////////////////////////////const list = computed<List>(() => { const a = active();
return new Map([ [ `browser-${a === "browser"}`, { title: "Browser storage", icon: "iconoir-app-window", method: "browser", activated: a === "browser", }, ], [ `device-${a === "device"}`, { title: "Device storage", icon: "iconoir-laptop", method: "device", activated: a === "device", }, ], [ `custom-${a === "custom"}`, { title: "Custom applet", icon: "iconoir-globe", method: "custom", activated: a === "custom", }, ], ]);});
const Item = (signal: Signal<ListItem<Method>>) => { const item = signal();
const colorClass = item.activated ? "pico-color-jade-500" : "pico-color-grey-500"; const icon = item.activated ? "iconoir-check-circle-solid" : "iconoir-check-circle";
return h( "p", { onclick: clickHandler(item.method), style: "cursor: pointer", }, [ h("span", { className: "with-icon" }, [ h("i", { className: item.icon }), h("strong", {}, text(item.title)), ]), h("br"), h("span", { className: `with-icon ${colorClass}` }, [ h("i", { className: icon }), h("span", {}, text(item.activated ? "Active" : "Select")), ]), ], );};
function clickHandler(method: Method) { return async () => { const currentlyActive = active(); if (currentlyActive === method && currentlyActive !== "custom") return; if (currentlyActive) unmountStorageMethod(currentlyActive); await mountStorageMethod(method); };}
const Options = computed(() => { return h("div", { id: "options" }, repeat(list, Item));});
// Add to DOMdocument.getElementById("options")?.replaceWith(Options());
////////////////////////////////////////////// CUSTOM APPLET////////////////////////////////////////////type CustomAppletState = "waiting" | "connecting" | { error: string } | "connected";
const [modalIsOpen, setModalIsOpen] = signal(false);const [customState, setCustomState] = signal<CustomAppletState>("waiting");
const Modal = () => { const Header = h("header", {}, [ h("button", { attrs: { rel: "prev" }, ariaLabel: "Close", onclick: close, }), h("p", {}, [ h("strong", {}, [ h("span", { className: "with-icon" }, [ h("i", { className: "iconoir-globe" }), h("span", {}, text("Load a custom applet")), ]), ]), ]), ]);
const Content = h("form", { onsubmit: submit }, [ h("fieldset", { role: "group" }, [ h("input", { type: "url", name: "url", placeholder: "https://applets.diffuse.sh/storage/output/indexed-db/", required: true, value: localStorage.getItem(CUSTOM_KEY) || "", }), h("input", { type: "submit", value: "Connect" }), ]), h("p", {}, [ h("small", { className: "with-icon" }, (element) => { const comp = computed(() => { const s = customState();
if (s === "connecting") { return [ h("i", { className: "iconoir-ev-plug-charging" }), h("span", {}, text("Connecting ...")), ]; } else if (typeof s !== "string") { return [ h("i", { className: "iconoir-warning-circle" }), h("span", {}, text(`Error: ${s.error}`)), ]; }
return [h("span", {}, text("Enter the URL to the applet."))]; });
effect(() => { element.replaceChildren(...comp()); }); }), ]), ]);
return h( "dialog", computed(() => ({ open: modalIsOpen() })), [h("article", {}, [Header, Content])], );};
// Eventsfunction close() { setModalIsOpen(false);}
async function submit(event: SubmitEvent) { event.preventDefault();
const input: HTMLInputElement | null = (event.target as HTMLFormElement).querySelector( `input[type="url"]`, );
if (!input) return;
const url = input.value; setCustomState("connecting");
const apl = await applet(url).catch((err) => { setCustomState({ error: "Failed to connect" }); throw err; });
let missingAction;
["tracks", "mount", "unmount"].forEach((method) => { if (!apl.manifest.actions?.[method]) missingAction = method; });
if (missingAction) { setCustomState({ error: `Applet is missing a required action: "${missingAction}"` }); return; }
localStorage.setItem(CUSTOM_KEY, url); await apl.sendAction("mount", undefined, { timeoutDuration: 60000 });
setActive("custom"); setModalIsOpen(false); setCustomState("waiting");}
// Add to DOMdocument.querySelector("main")?.appendChild(Modal());