A music player that connects to your cloud/distributed storage.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

chore: dashboard/guide/build improvements

+206 -187
+1 -1
_config.ts
··· 23 23 src: "./src", 24 24 server: { 25 25 debugBar: false, 26 - middlewares: [], // [facetHtmlMiddleware], 26 + middlewares: [facetHtmlMiddleware], 27 27 }, 28 28 }); 29 29
+7 -4
src/build.vto
··· 20 20 <!-- BUILD --> 21 21 <section style="margin-top: var(--space-lg);"> 22 22 <form id="build-form" class="columns"> 23 - <div class="flex"> 23 + <div class="flex" style="flex-basis: 35%"> 24 24 <div id="html-input-container" class="code-editor monospace-font"> 25 25 </div> 26 26 </div> 27 27 28 - <div style="flex: 1; max-width: var(--container-sm)"> 28 + <div style="flex: 1; font-size: var(--fs-sm); max-width: var(--container-sm)"> 29 29 <p style="margin-top: 0"> 30 30 Your code here builds on the <a href="#foundation">foundation</a> listed below, and will be injected into a <span style="white-space: nowrap"><code>&lt;div id="container"&gt;</code></span> element in the body. 31 31 </p> ··· 57 57 <h2 id="foundation">Foundation</h2> 58 58 59 59 <p> 60 - Diffuse comes with a foundation that preconfigures all elements so you don't have to set them up yourself, along with a combination of elements for certain features. It internally tracks the DOM addition of the custom elements, so no need to worry about setting up an element multiple times. 60 + <strong>Diffuse comes with a foundation that preconfigures a lot of elements so you don't have to set them up yourself</strong>, along with a combination of elements for certain features. It internally tracks the DOM addition of the custom elements, so no need to worry about setting up an element multiple times. It also provides signals which can be used to track element creations. An example of this can be found in the "scrobbles" feature. 61 + </p> 62 + <p> 63 + That said, you are not required not use this! You can, for example, setup a Diffuse audio engine element yourself that's in a different group so that it doesn't communicate with the default one; in case you want to make a dj-deck, or something like that, which would have multiple audio items playing at the same time. This does mean you will need to pay attention to more things, such as, how does this interact with other features the user has enabled. 61 64 </p> 62 65 <p> 63 - <small><i class="ph-fill ph-info"></i> Refer to the <a href="#elements">elements index</a> to find out what each element does.</small> 66 + <small><i class="ph-fill ph-info"></i> Refer to the <a href="elements/">elements index</a> to find out what each element does.</small> 64 67 </p> 65 68 <div class="code-block"> 66 69 <code>
+13 -152
src/common/pages/dashboard.js
··· 5 5 6 6 import * as FacetCategory from "~/common/facets/category.js"; 7 7 import { effect, signal } from "~/common/signal.js"; 8 - import { facetFromURI } from "~/common/facets/utils.js"; 8 + 9 9 import { nothing } from "~/common/element.js"; 10 10 11 - import { deleteFacet, saveFacet } from "./crud.js"; 11 + import { deleteFacet } from "./crud.js"; 12 12 import { output } from "./output.js"; 13 + import { openAddFromURIModal } from "./from-uri.js"; 13 14 14 15 // Signals 15 16 const activeFilter = signal("all"); ··· 18 19 * @import OutputOrchestrator from "~/components/orchestrator/output/element.js"; 19 20 */ 20 21 21 - const addFromUri = () => 22 - html` 23 - <li 24 - class="grid-item" 25 - style="color: ${activeFilter.value === "all" 26 - ? "inherit" 27 - : FacetCategory.color( 28 - /** @type {any} */ ({ kind: activeFilter.value }), 29 - )}; background: oklch(from currentColor l c h / 0.0625);" 30 - > 31 - <div 32 - class="grid-item__contents" 33 - style="display: flex; align-items: center; justify-content: center;" 34 - > 35 - <button 36 - class="button--transparent with-icon" 37 - style="color: inherit; font-size: var(--fs-sm); font-weight: 600;" 38 - @click="${openAddFromURIModal}" 39 - > 40 - <i class="ph-fill ph-plus-circle"></i> 41 - Add from URI 42 - </button> 43 - </div> 44 - </li> 45 - `; 46 - 47 22 const emptyFacetsList = () => 48 23 html` 49 24 <p> ··· 58 33 `; 59 34 60 35 //////////////////////////////////////////// 61 - // DIALOG 62 - //////////////////////////////////////////// 63 - 64 - function openAddFromURIModal() { 65 - let dialog = /** @type {HTMLDialogElement | null} */ ( 66 - document.getElementById("add-from-uri-dialog") 67 - ); 68 - 69 - if (!dialog) { 70 - dialog = /** @type {HTMLDialogElement} */ ( 71 - document.createElement("dialog") 72 - ); 73 - 74 - dialog.id = "add-from-uri-dialog"; 75 - dialog.style.cssText = 76 - "position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); margin: 0;"; 77 - 78 - render( 79 - html` 80 - <form id="add-from-uri-form"> 81 - <p> 82 - <strong>Load a facet from a URI.</strong> Currently supported URI schemes: 83 - <code>https</code>, <code>at</code> (AT Protocol) and <code>diffuse</code> 84 - (references internal facets). 85 - </p> 86 - 87 - <div style="display: flex; flex-direction: column; gap: var(--space-xs)"> 88 - <div> 89 - <label>Name</label> 90 - <input 91 - id="add-uri-name" 92 - type="text" 93 - placeholder="My Feature Name" 94 - required 95 - autocomplete="off" 96 - /> 97 - </div> 98 - <div> 99 - <label>Kind</label> 100 - <select id="add-uri-kind"> 101 - <option value="interactive">interface</option> 102 - <option value="prelude">feature</option> 103 - </select> 104 - </div> 105 - <div> 106 - <label>URI</label> 107 - <input 108 - id="add-uri-uri" 109 - type="url" 110 - placeholder="at://..." 111 - required 112 - autocomplete="off" 113 - /> 114 - </div> 115 - </div> 116 - <div style="display: flex; gap: var(--space-xs); margin-top: var(--space-sm)"> 117 - <button type="submit">Add</button> 118 - <button type="button" id="add-uri-cancel"> 119 - Cancel 120 - </button> 121 - </div> 122 - </form> 123 - `, 124 - dialog, 125 - ); 126 - 127 - document.body.appendChild(dialog); 128 - 129 - dialog.querySelector("#add-uri-cancel")?.addEventListener("click", () => { 130 - /** @type {HTMLDialogElement} */ (dialog).close(); 131 - }); 132 - 133 - dialog.querySelector("#add-from-uri-form")?.addEventListener( 134 - "submit", 135 - async (e) => { 136 - e.preventDefault(); 137 - 138 - const nameEl = /** @type {HTMLInputElement} */ ( 139 - dialog?.querySelector("#add-uri-name") 140 - ); 141 - 142 - const kindEl = /** @type {HTMLSelectElement} */ ( 143 - dialog?.querySelector("#add-uri-kind") 144 - ); 145 - 146 - const uriEl = /** @type {HTMLInputElement} */ ( 147 - dialog?.querySelector("#add-uri-uri") 148 - ); 149 - 150 - const name = nameEl?.value.trim() ?? ""; 151 - const kind = kindEl?.value ?? "interactive"; 152 - const uri = uriEl?.value.trim() ?? ""; 153 - if (!name || !uri) return; 154 - 155 - const facet = await facetFromURI({ kind, name, uri }, { 156 - fetchHTML: false, 157 - }); 158 - await saveFacet(facet); 159 - 160 - /** @type {HTMLDialogElement} */ (dialog).close(); 161 - }, 162 - ); 163 - } 164 - 165 - const nameEl = /** @type {HTMLInputElement} */ ( 166 - dialog.querySelector("#add-uri-name") 167 - ); 168 - const kindEl = /** @type {HTMLSelectElement} */ ( 169 - dialog.querySelector("#add-uri-kind") 170 - ); 171 - const uriEl = /** @type {HTMLInputElement} */ ( 172 - dialog.querySelector("#add-uri-uri") 173 - ); 174 - if (nameEl) nameEl.value = ""; 175 - if (kindEl) kindEl.value = "interactive"; 176 - if (uriEl) uriEl.value = ""; 177 - 178 - dialog.showModal(); 179 - } 180 - 181 - //////////////////////////////////////////// 182 36 // LIST 183 37 //////////////////////////////////////////// 184 38 ··· 273 127 Interfaces 274 128 </button> 275 129 130 + <span class="divider"></span> 131 + 132 + <button 133 + class="button--border button--tiny button--bg-accent button--tr-accent button--transparent with-icon" 134 + @click="${() => openAddFromURIModal()}" 135 + > 136 + <i class="ph-fill ph-plus-circle"></i> 137 + Add from URI 138 + </button> 139 + 276 140 <div style="flex: 1"></div> 277 141 278 142 <span class="grid-filter--label grid-filter--label-output" ··· 368 232 ` 369 233 : html` 370 234 ${filterBar} ${emptyFacetsList()} 371 - <ul class="grid" style="margin: var(--space-sm) 0 0"> 372 - ${addFromUri()} 373 - </ul> 374 235 `; 375 236 376 237 render(h, listEl);
+126
src/common/pages/from-uri.js
··· 1 + import { html, render } from "lit-html"; 2 + 3 + import { facetFromURI } from "~/common/facets/utils.js"; 4 + import { saveFacet } from "./crud.js"; 5 + 6 + //////////////////////////////////////////// 7 + // DIALOG 8 + //////////////////////////////////////////// 9 + 10 + export function openAddFromURIModal() { 11 + let dialog = /** @type {HTMLDialogElement | null} */ ( 12 + document.getElementById("add-from-uri-dialog") 13 + ); 14 + 15 + if (!dialog) { 16 + dialog = /** @type {HTMLDialogElement} */ ( 17 + document.createElement("dialog") 18 + ); 19 + 20 + dialog.id = "add-from-uri-dialog"; 21 + dialog.style.cssText = 22 + "position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); margin: 0;"; 23 + 24 + render( 25 + html` 26 + <form id="add-from-uri-form"> 27 + <p> 28 + <strong>Load a facet from a URI.</strong> Currently supported URI schemes: 29 + <code>https</code>, <code>at</code> (AT Protocol) and <code>diffuse</code> 30 + (references internal facets). 31 + </p> 32 + 33 + <div style="display: flex; flex-direction: column; gap: var(--space-xs)"> 34 + <div> 35 + <label>Name</label> 36 + <input 37 + id="add-uri-name" 38 + type="text" 39 + placeholder="My Feature Name" 40 + required 41 + autocomplete="off" 42 + /> 43 + </div> 44 + <div> 45 + <label>Kind</label> 46 + <select id="add-uri-kind"> 47 + <option value="interactive">interface</option> 48 + <option value="prelude">feature</option> 49 + </select> 50 + </div> 51 + <div> 52 + <label>URI</label> 53 + <input 54 + id="add-uri-uri" 55 + type="url" 56 + placeholder="at://..." 57 + required 58 + autocomplete="off" 59 + /> 60 + </div> 61 + </div> 62 + <div style="display: flex; gap: var(--space-xs); margin-top: var(--space-sm)"> 63 + <button type="submit" class="button--bg-accent">Add</button> 64 + <button type="button" id="add-uri-cancel"> 65 + Cancel 66 + </button> 67 + </div> 68 + </form> 69 + `, 70 + dialog, 71 + ); 72 + 73 + document.body.appendChild(dialog); 74 + 75 + dialog.querySelector("#add-uri-cancel")?.addEventListener("click", () => { 76 + /** @type {HTMLDialogElement} */ (dialog).close(); 77 + }); 78 + 79 + dialog.querySelector("#add-from-uri-form")?.addEventListener( 80 + "submit", 81 + async (e) => { 82 + e.preventDefault(); 83 + 84 + const nameEl = /** @type {HTMLInputElement} */ ( 85 + dialog?.querySelector("#add-uri-name") 86 + ); 87 + 88 + const kindEl = /** @type {HTMLSelectElement} */ ( 89 + dialog?.querySelector("#add-uri-kind") 90 + ); 91 + 92 + const uriEl = /** @type {HTMLInputElement} */ ( 93 + dialog?.querySelector("#add-uri-uri") 94 + ); 95 + 96 + const name = nameEl?.value.trim() ?? ""; 97 + const kind = kindEl?.value ?? "interactive"; 98 + const uri = uriEl?.value.trim() ?? ""; 99 + if (!name || !uri) return; 100 + 101 + const facet = await facetFromURI({ kind, name, uri }, { 102 + fetchHTML: false, 103 + }); 104 + 105 + await saveFacet(facet); 106 + 107 + /** @type {HTMLDialogElement} */ (dialog).close(); 108 + }, 109 + ); 110 + } 111 + 112 + const nameEl = /** @type {HTMLInputElement} */ ( 113 + dialog.querySelector("#add-uri-name") 114 + ); 115 + const kindEl = /** @type {HTMLSelectElement} */ ( 116 + dialog.querySelector("#add-uri-kind") 117 + ); 118 + const uriEl = /** @type {HTMLInputElement} */ ( 119 + dialog.querySelector("#add-uri-uri") 120 + ); 121 + if (nameEl) nameEl.value = ""; 122 + if (kindEl) kindEl.value = "interactive"; 123 + if (uriEl) uriEl.value = ""; 124 + 125 + dialog.showModal(); 126 + }
+22 -11
src/common/pages/grid.js
··· 10 10 11 11 export function setupFilter() { 12 12 /** @type {NodeListOf<HTMLElement>} */ 13 - const buttons = document.querySelectorAll(".grid-filter button"); 13 + const buttons = document.querySelectorAll(".grid-filter button[data-filter]"); 14 14 15 15 /** @type {NodeListOf<HTMLElement>} */ 16 16 const items = document.querySelectorAll(".grid-item"); 17 17 18 + function applyFilter(filter) { 19 + buttons.forEach((b) => { 20 + if (b.dataset.filter === filter) b.classList.remove("button--transparent"); 21 + else b.classList.add("button--transparent"); 22 + }); 23 + items.forEach((item) => { 24 + const kind = item.dataset.kind; 25 + const show = filter === "all" || kind === filter; 26 + item.hidden = !show; 27 + }); 28 + } 29 + 18 30 buttons.forEach((b) => { 19 31 b.addEventListener("click", () => { 20 - buttons.forEach((b) => b.classList.add("button--transparent")); 21 - b.classList.remove("button--transparent"); 22 - 23 - const filter = b.dataset.filter; 24 - 25 - items.forEach((item) => { 26 - const kind = item.dataset.kind; 27 - const show = filter === "all" || kind === filter; 28 - item.hidden = !show; 29 - }); 32 + const filter = b.dataset.filter ?? "all"; 33 + const url = new URL(location.href); 34 + if (filter === "all") url.searchParams.delete("filter"); 35 + else url.searchParams.set("filter", filter); 36 + history.replaceState(null, "", url); 37 + applyFilter(filter); 30 38 }); 31 39 }); 40 + 41 + const initial = new URL(location.href).searchParams.get("filter") ?? "all"; 42 + applyFilter(initial); 32 43 } 33 44 34 45 ////////////////////////////////////////////
+1 -2
src/elements.vto
··· 10 10 - vendor/@phosphor-icons/web/fill/style.css 11 11 12 12 scripts: 13 - - index.js 14 13 - common/pages/version-upgrade.js 15 14 16 15 # ELEMENTS ··· 306 305 307 306 <!-- DEFINITIONS --> 308 307 <section> 309 - <h2 id="definitions">Definitions</h2> 308 + <h2 id="definitions" style="color: var(--accent)">Definitions</h2> 310 309 311 310 <p>All of the elements here are built with these data definitions in mind. That said, you can mix elements that use different definitions; you just have to put a transformer between them in order to translate between them, if needed.</p> 312 311
+33 -11
src/guide.vto
··· 6 6 7 7 <h1 hidden>Guide</h1> 8 8 9 + <style> 10 + strong { 11 + font-weight: 600; 12 + } 13 + </style> 14 + 9 15 <div class="columns"> 10 16 <section> 11 17 <h3>Concept</h3> ··· 30 36 <section> 31 37 <h3>Getting started</h3> 32 38 33 - <ul style="display: flex; flex-direction: column; gap: var(--space-sm); padding-left: 0"> 39 + <p> 40 + <small><i class="ph-fill ph-info"></i> <em>There's a <a href="guide/#tutorial">tutorial</a> below if you prefer a long-form introduction.</em></small> 41 + </p> 42 + 43 + <ul style="display: flex; flex-direction: column; gap: var(--space-md); margin-top: var(--space-md); padding-left: 0"> 34 44 <!-- 1 --> 35 - <li class="with-icon" style="gap: var(--space-xs)"> 36 - <i class="ph-fill ph-file-audio"></i> 37 - <span>Add audio files or streams. The <a href="{{ ('facets/data/connect/index.html') |> facetLoaderURL }}">connect</a> interfaces can be used for this, or use the demo from the tutorial below.</span> 45 + <li class="with-icon" style="gap: var(--space-sm)"> 46 + <i class="ph-fill ph-file-audio" style="opacity: 0.4"></i> 47 + <span><strong>Add some audio files or streams.</strong> The <a href="{{ ('facets/data/connect/index.html') |> facetLoaderURL }}">connect</a> interfaces can be used for this, or use the demo from the tutorial below.</span> 38 48 </li> 39 49 40 50 <!-- 2 --> 41 - <li class="with-icon" style="gap: var(--space-xs)"> 42 - <i class="ph-fill ph-gear-six"></i> 43 - <span>Wait until the processing of audio sources into tracks is completed.</span> 51 + <li class="with-icon" style="gap: var(--space-sm)"> 52 + <i class="ph-fill ph-gear-six" style="opacity: 0.4"></i> 53 + <span><strong>Wait until the processing of audio sources into tracks is completed.</strong> The demo button will show you the status or you can monitor it using this <a href="{{ ('facets/data/process-tracks/index.html') |> facetLoaderURL }}">interface</a>.</span> 44 54 </li> 45 55 46 56 <!-- 3 --> 47 - <li class="with-icon" style="gap: var(--space-xs)"> 48 - <i class="ph-fill ph-paint-brush"></i> 49 - <span>Explore the interfaces that can be used.</span> 57 + <li class="with-icon" style="gap: var(--space-sm)"> 58 + <i class="ph-fill ph-paint-brush" style="opacity: 0.4"></i> 59 + <span><strong style="color: var(--accent-twist-2)">Explore the interfaces that can be used.</strong> Check out the <a href="featured/?filter=interface">featured interfaces</a>.</span> 60 + </li> 61 + 62 + <!-- 4 --> 63 + <li class="with-icon" style="gap: var(--space-sm)"> 64 + <i class="ph-fill ph-sparkle" style="opacity: 0.4"></i> 65 + <span><strong style="color: var(--accent-twist-4)">Continue exploring, enable some additional features</strong>, such as making played audio available offline automatically.</span> 66 + </li> 67 + 68 + <!-- 5 --> 69 + <li class="with-icon" style="gap: var(--space-sm)"> 70 + <i class="ph-fill ph-person" style="opacity: 0.4"></i> 71 + <span><strong>Sync your user-data with your other devices.</strong> Or share your data with other people.</span> 50 72 </li> 51 73 </ul> 52 74 </section> 53 75 </div> 54 76 55 77 <section> 56 - <h3>Tutorial</h3> 78 + <h3 id="tutorial">Tutorial</h3> 57 79 58 80 <p> 59 81 <strong>Diffuse is not your typical streaming service, we have to add sources of audio so we have stuff to play.</strong> This button below adds some demo content, so you can experiment with the software right away.
+3 -6
src/styles/diffuse/page.css
··· 20 20 background: var(--code-color); 21 21 border-radius: var(--radius-sm); 22 22 color: var(--text-color); 23 - font-size: var(--fs-sm); 23 + font-size: 86%; 24 24 padding: var(--space-3xs); 25 25 } 26 26 ··· 610 610 } 611 611 612 612 .divider { 613 + align-self: stretch; 613 614 background-color: var(--border-color); 614 - height: 2.5em; 615 615 width: 1px; 616 616 } 617 617 ··· 658 658 659 659 a { 660 660 text-underline-offset: 3px; 661 - } 662 - 663 - code { 664 - font-size: var(--fs-xs); 665 661 } 666 662 } 667 663 ··· 737 733 display: flex; 738 734 flex: 1; 739 735 flex-wrap: nowrap; 736 + font-size: var(--fs-sm); 740 737 gap: var(--space-xs); 741 738 overflow: hidden; 742 739