import * as TID from "@atcute/tid"; import { html } from "lit-html"; import * as Output from "~/common/output.js"; import { SCHEME } from "~/components/input/https-json/constants.js"; import { buildURI, parseURI, serverId } from "~/components/input/https-json/common.js"; import { effect } from "~/common/signal.js"; import foundation from "~/common/foundation.js"; import { setup } from "~/facets/connect/common.js"; /** * @import { Server } from "~/components/input/https-json/common.js" */ foundation.setup({ title: "Connect HTTPS / JSON Listing | Diffuse" }); //////////////////////////////////////////// // SETUP //////////////////////////////////////////// const [inputConfigurator, outputOrchestrator, sourcesOrchestrator] = await Promise.all([ foundation.configurator.input(), foundation.orchestrator.output(), foundation.orchestrator.sources(), ]); await Promise.all([ customElements.whenDefined(inputConfigurator.localName), customElements.whenDefined(outputOrchestrator.localName), customElements.whenDefined(sourcesOrchestrator.localName), ]); //////////////////////////////////////////// // UI //////////////////////////////////////////// const { setItems, setError } = setup({ title: "HTTPS + JSON", hasOutput: false, description: html`

Connect to an HTTPS server that serves JSON directory listings (eg. static-web-server with --directory-listing-format=json).

Uses the format: [{ "name": "Example folder", "type": "directory" }, { "name": "file.mp3", "type": "file" }]

`, formFields: html`

* Required.
Excluded directories are matched by name at any depth, comma-separated.

`, onSubmit: () => addServer(), }); const hostInput = /** @type {HTMLInputElement} */ (document.querySelector("#https-json-host")); const dirInput = /** @type {HTMLInputElement} */ (document.querySelector("#https-json-dir")); const excludeInput = /** @type {HTMLInputElement} */ (document.querySelector("#https-json-exclude")); //////////////////////////////////////////// // REACTIVE LIST //////////////////////////////////////////// effect(() => { const inputSources = sourcesOrchestrator.sources()[SCHEME] ?? []; /** @type {Map} */ const allServers = new Map(); for (const source of inputSources) { const parsed = parseURI(source.uri); if (!parsed) continue; const id = serverId(parsed.server); if (!allServers.has(id)) { allServers.set(id, { server: parsed.server, uri: source.uri }); } } setItems( [...allServers.values()].map(({ server, uri }) => ({ name: server.host, detail: server.dir, isInput: true, isOutput: false, isSelectedOutput: false, isDisabled: sourcesOrchestrator.isDisabled(uri), onRemove: () => removeServer(uri), onToggleDisabled: () => sourcesOrchestrator.toggle(uri), })), ); }); //////////////////////////////////////////// // ACTIONS //////////////////////////////////////////// /** @param {string} uri */ async function removeServer(uri) { setError(null); try { const tracks = await Output.data(outputOrchestrator.tracks); const detachedTracks = await inputConfigurator.detach({ fileUriOrScheme: uri, tracks, }); if (detachedTracks) await outputOrchestrator.tracks.save(detachedTracks); } catch (err) { setError(err instanceof Error ? err.message : "Failed to remove server"); } } async function addServer() { const host = hostInput.value?.trim(); const dir = dirInput.value?.trim() || "/"; const exclude = (excludeInput.value ?? "").split(",").map((s) => s.trim()).filter(Boolean); if (!host) return; /** @type {Server} */ const server = { host, dir, exclude: exclude.length ? exclude : undefined }; const uri = buildURI(server); const now = new Date().toISOString(); const tracksCol = outputOrchestrator.tracks.collection(); const existingTracks = tracksCol.state === "loaded" ? tracksCol.data : []; await outputOrchestrator.tracks.save([ ...existingTracks, { $type: "sh.diffuse.output.track", id: TID.now(), createdAt: now, updatedAt: now, kind: "placeholder", uri, }, ]); } //////////////////////////////////////////// // 🚀 //////////////////////////////////////////// foundation.ready();