forked from
tokono.ma/diffuse
A music player that connects to your cloud/distributed storage.
1import * as TID from "@atcute/tid";
2import { html } from "lit-html";
3
4import * as Output from "~/common/output.js";
5import { SCHEME } from "~/components/input/https/constants.js";
6import { parseURI } from "~/components/input/https/common.js";
7import { effect } from "~/common/signal.js";
8import foundation from "~/common/foundation.js";
9
10import { setup } from "~/facets/connect/common.js";
11
12foundation.setup({ title: "Connect HTTPS | Diffuse" });
13
14////////////////////////////////////////////
15// SETUP
16////////////////////////////////////////////
17
18const [inputConfigurator, outputOrchestrator, sourcesOrchestrator] =
19 await Promise.all([
20 foundation.configurator.input(),
21 foundation.orchestrator.output(),
22 foundation.orchestrator.sources(),
23 ]);
24
25await Promise.all([
26 customElements.whenDefined(inputConfigurator.localName),
27 customElements.whenDefined(outputOrchestrator.localName),
28 customElements.whenDefined(sourcesOrchestrator.localName),
29]);
30
31////////////////////////////////////////////
32// UI
33////////////////////////////////////////////
34
35const { setItems, setError } = setup({
36 title: "HTTPS",
37 hasOutput: false,
38
39 description: html`
40 <p>Add HTTPS urls as audio sources.</p>
41 `,
42
43 formFields: html`
44 <label>URL <input id="https-url" type="url" placeholder="https://example.com/audio.mp3" required></label>
45 `,
46
47 onSubmit: () => addUrl(),
48});
49
50const urlInput = /** @type {HTMLInputElement} */ (document.querySelector("#https-url"));
51
52////////////////////////////////////////////
53// REACTIVE LIST
54////////////////////////////////////////////
55
56effect(() => {
57 const inputSources = sourcesOrchestrator.sources()[SCHEME] ?? [];
58
59 setItems(
60 inputSources.map((source) => {
61 const parsed = parseURI(source.uri);
62 return {
63 name: source.uri,
64 detail: parsed?.host ?? "",
65 isInput: true,
66 isOutput: false,
67 isSelectedOutput: false,
68 isDisabled: sourcesOrchestrator.isDisabled(source.uri),
69 onRemove: () => removeUrl(source.uri),
70 onToggleDisabled: () => sourcesOrchestrator.toggle(source.uri),
71 };
72 }),
73 );
74});
75////////////////////////////////////////////
76// ACTIONS
77////////////////////////////////////////////
78
79/** @param {string} uri */
80async function removeUrl(uri) {
81 setError(null);
82 try {
83 const tracks = await Output.data(outputOrchestrator.tracks);
84 const detachedTracks = await inputConfigurator.detach({
85 fileUriOrScheme: uri,
86 tracks,
87 });
88
89 if (detachedTracks) await outputOrchestrator.tracks.save(detachedTracks);
90 } catch (err) {
91 setError(err instanceof Error ? err.message : "Failed to remove URL");
92 }
93}
94
95async function addUrl() {
96 const url = urlInput.value?.trim();
97 if (!url) return;
98
99 const now = new Date().toISOString();
100 const tracksCol = outputOrchestrator.tracks.collection();
101 const existingTracks = tracksCol.state === "loaded" ? tracksCol.data : [];
102
103 await outputOrchestrator.tracks.save([
104 ...existingTracks,
105 {
106 $type: "sh.diffuse.output.track",
107 id: TID.now(),
108 createdAt: now,
109 updatedAt: now,
110 kind: "placeholder",
111 uri: url,
112 },
113 ]);
114}
115
116////////////////////////////////////////////
117// 🚀
118////////////////////////////////////////////
119
120foundation.ready();