diff --git a/src/_data/facets.json b/src/_data/facets.json
index 66b41ac2..e27cd93e 100644
--- a/src/_data/facets.json
+++ b/src/_data/facets.json
@@ -206,6 +206,12 @@
"category": "Miscellaneous",
"desc": "Connect to Rocksky to setup the Rocksky scrobbler."
},
+ {
+ "url": "facets/setup/index.html",
+ "title": "Setup",
+ "category": "Miscellaneous",
+ "desc": "An interactive getting-started guide. Shows contextual next steps based on your current progress."
+ },
{
"url": "facets/data/sources/index.html",
"title": "Sources",
diff --git a/src/facets/setup/index.html b/src/facets/setup/index.html
new file mode 100644
index 00000000..e1eed218
--- /dev/null
+++ b/src/facets/setup/index.html
@@ -0,0 +1,61 @@
+
+
+
+
+
+
Setup
+
Follow these steps to get up and running with Diffuse.
+
+
+
+
+
+
diff --git a/src/facets/setup/index.inline.js b/src/facets/setup/index.inline.js
new file mode 100644
index 00000000..07b7a1d9
--- /dev/null
+++ b/src/facets/setup/index.inline.js
@@ -0,0 +1,152 @@
+import { html, render as litRender } from "lit-html";
+
+import { effect, signal } from "~/common/signal.js";
+import { insertPreludes } from "~/common/facets/prelude.js";
+import foundation from "~/common/foundation.js";
+import * as Output from "~/common/output.js";
+
+foundation.setup({ title: "Setup | Diffuse" });
+
+/** @param {string} path */
+const loaderHref = (path) => `./l/?path=${encodeURIComponent(path)}`;
+
+////////////////////////////////////////////
+// SETUP
+////////////////////////////////////////////
+
+const [sourcesOrchestrator, outputOrchestrator, processOrchestrator] =
+ await Promise.all([
+ foundation.orchestrator.sources(),
+ foundation.orchestrator.output(),
+ foundation.orchestrator.processTracks({ disableWhenReady: true }),
+ ]);
+
+await Promise.all([
+ customElements.whenDefined(sourcesOrchestrator.localName),
+ customElements.whenDefined(outputOrchestrator.localName),
+]);
+
+const right =
+ /** @type {HTMLElement} */ (document.querySelector("#setup-right"));
+
+////////////////////////////////////////////
+// DEMO
+////////////////////////////////////////////
+
+const demoAdding = signal(false);
+
+async function addDemo() {
+ if (demoAdding.get()) return;
+ demoAdding.set(true);
+
+ try {
+ const input = await foundation.configurator.input();
+ await insertPreludes(await Output.data(outputOrchestrator.facets));
+
+ const s3 =
+ /** @type {import("~/components/input/s3/element.js").CLASS | null} */ (
+ input.querySelector("di-s3")
+ );
+ if (!s3) throw new Error("S3 input not found");
+
+ const demo = await s3.demo();
+ await outputOrchestrator.tracks.save([
+ ...(await Output.data(outputOrchestrator.tracks)),
+ demo.track,
+ ]);
+ await processOrchestrator.process();
+ } catch (err) {
+ console.error(err);
+ } finally {
+ demoAdding.set(false);
+ }
+}
+
+////////////////////////////////////////////
+// UI
+////////////////////////////////////////////
+
+effect(() => {
+ const sourcesRecord = sourcesOrchestrator.sources();
+ const hasSources = Object.values(sourcesRecord).some((s) => s.length > 0);
+
+ const tracksCol = outputOrchestrator.tracks.collection();
+ const hasTracks = tracksCol.state === "loaded" && tracksCol.data.length > 0;
+
+ const isProcessing = processOrchestrator.isProcessing();
+ const { processed, total } = processOrchestrator.progress();
+ const pct = total > 0 ? Math.round((processed / total) * 100) : null;
+
+ const hasContent = hasSources || hasTracks;
+
+ let content;
+
+ if (isProcessing) {
+ content = html`
+
+ Hold on for a bit, translating audio into tracks. This might take a while; you
+ can come back to this later though, progress is saved.
+
+
+ -
+
+
+ ${pct !== null ? `Processing (${pct}%)` : "Listing…"}
+
+
+
+ `;
+ } else if (!hasContent) {
+ content = html`
+
+ The first step is to add audio. You can introduce some from a number of
+ different sources.
+
+
+ `;
+ } else {
+ content = html`
+
+ Audio was added succesfully, time to play some! Let's explore some interfaces
+ to do so.
+
+
+ `;
+ }
+
+ litRender(content, right);
+});
+
+////////////////////////////////////////////
+// 🚀
+////////////////////////////////////////////
+
+foundation.ready();