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

feat: scrobble feature facet + copy facet descriptions

+60 -13
+2
src/_components/facets/grid.vto
··· 18 <li 19 class="grid-item" 20 data-active-color="{{color}}" 21 data-name="{{item.title}}" 22 data-uri="{{ item.url |> facetOrThemeURI }}" 23 > 24 <div class="grid-item__contents">
··· 18 <li 19 class="grid-item" 20 data-active-color="{{color}}" 21 + data-description="{{item.desc.trim()}}" 22 data-name="{{item.title}}" 23 + data-kind="{{item.kind ?? `interface`}}" 24 data-uri="{{ item.url |> facetOrThemeURI }}" 25 > 26 <div class="grid-item__contents">
+7
src/_data/facets.yaml
··· 10 featured: true 11 desc: > 12 Automatically put tracks into the queue. 13 - url: "facets/scrobble/last.fm/index.html" 14 title: "Scrobble / Last.fm" 15 category: Misc
··· 10 featured: true 11 desc: > 12 Automatically put tracks into the queue. 13 + - url: "facets/scrobble/index.html" 14 + title: "Scrobble" 15 + kind: "prelude" 16 + category: Misc 17 + featured: true 18 + desc: > 19 + Enable scrobbling, keep track of what you're listening to. 20 - url: "facets/scrobble/last.fm/index.html" 21 title: "Scrobble / Last.fm" 22 category: Misc
+1 -3
src/common/facets/foundation.js
··· 82 } 83 84 async function playAudioFromQueue() { 85 - const [a, q, ms, qa, sca] = await Promise.all([ 86 // engine 87 audio(), 88 queue(), ··· 90 // orchestrator 91 mediaSession(), 92 queueAudio(), 93 - scrobbleAudio(), 94 ]); 95 96 return { ··· 101 orchestrator: { 102 mediaSession: ms, 103 queueAudio: qa, 104 - scrobbleAudio: sca, 105 }, 106 }; 107 }
··· 82 } 83 84 async function playAudioFromQueue() { 85 + const [a, q, ms, qa] = await Promise.all([ 86 // engine 87 audio(), 88 queue(), ··· 90 // orchestrator 91 mediaSession(), 92 queueAudio(), 93 ]); 94 95 return { ··· 100 orchestrator: { 101 mediaSession: ms, 102 queueAudio: qa, 103 }, 104 }; 105 }
+7 -2
src/common/facets/utils.js
··· 10 */ 11 12 /** 13 - * @param {{ name: string; uri: string }} _args 14 * @param {{ fetchHTML: boolean }} options 15 */ 16 - export async function facetFromURI({ name, uri }, { fetchHTML }) { 17 const html = fetchHTML ? await loadURI(uri) : undefined; 18 const cid = html 19 ? await CID.create(0x55, new TextEncoder().encode(html)) ··· 26 createdAt: timestamp, 27 id: TID.now(), 28 cid, 29 html, 30 name, 31 updatedAt: timestamp, 32 uri, 33 };
··· 10 */ 11 12 /** 13 + * @param {{ description?: string; kind: string | undefined; name: string; uri: string }} _args 14 * @param {{ fetchHTML: boolean }} options 15 */ 16 + export async function facetFromURI( 17 + { description, kind, name, uri }, 18 + { fetchHTML }, 19 + ) { 20 const html = fetchHTML ? await loadURI(uri) : undefined; 21 const cid = html 22 ? await CID.create(0x55, new TextEncoder().encode(html)) ··· 29 createdAt: timestamp, 30 id: TID.now(), 31 cid, 32 + description, 33 html, 34 name, 35 + kind: kind === "interactive" || kind === "prelude" ? kind : undefined, 36 updatedAt: timestamp, 37 uri, 38 };
+7 -2
src/facets/common/build.js
··· 105 const cid = await CID.create(0x55, new TextEncoder().encode(html)); 106 const name = nameEl?.value ?? "nameless"; 107 const description = descriptionEl?.value ?? ""; 108 - const kind = /** @type {"interactive" | "prelude"} */ (kindEl?.value ?? "interactive"); 109 110 /** @type {Facet} */ 111 const facet = $editingFacet.value ··· 231 const name = target.closest("li")?.getAttribute("data-name"); 232 if (!name) return; 233 234 switch (rel) { 235 case "edit": { 236 - const facet = await facetFromURI({ name, uri }, { fetchHTML: true }); 237 editFacet(facet); 238 document.querySelector("#build")?.scrollIntoView(); 239 break;
··· 105 const cid = await CID.create(0x55, new TextEncoder().encode(html)); 106 const name = nameEl?.value ?? "nameless"; 107 const description = descriptionEl?.value ?? ""; 108 + const kind = 109 + /** @type {"interactive" | "prelude"} */ (kindEl?.value ?? "interactive"); 110 111 /** @type {Facet} */ 112 const facet = $editingFacet.value ··· 232 const name = target.closest("li")?.getAttribute("data-name"); 233 if (!name) return; 234 235 + const kind = target.closest("li")?.getAttribute("data-kind") ?? undefined; 236 + 237 switch (rel) { 238 case "edit": { 239 + const facet = await facetFromURI({ kind, name, uri }, { 240 + fetchHTML: true, 241 + }); 242 editFacet(facet); 243 document.querySelector("#build")?.scrollIntoView(); 244 break;
+6 -1
src/facets/common/grid.js
··· 26 27 const uri = li.getAttribute("data-uri"); 28 const name = li.getAttribute("data-name"); 29 if (!uri || !name) return; 30 31 const out = await foundation.orchestrator.output(); ··· 35 if (isActive) { 36 out.facets.save(collection.filter((f) => f.uri !== uri)); 37 } else { 38 - const facet = await facetFromURI({ name, uri }, { fetchHTML: false }); 39 out.facets.save([...collection, facet]); 40 } 41 });
··· 26 27 const uri = li.getAttribute("data-uri"); 28 const name = li.getAttribute("data-name"); 29 + const kind = li.getAttribute("data-kind") ?? undefined; 30 + const description = li.getAttribute("data-description") ?? undefined; 31 + 32 if (!uri || !name) return; 33 34 const out = await foundation.orchestrator.output(); ··· 38 if (isActive) { 39 out.facets.save(collection.filter((f) => f.uri !== uri)); 40 } else { 41 + const facet = await facetFromURI({ description, kind, name, uri }, { 42 + fetchHTML: false, 43 + }); 44 out.facets.save([...collection, facet]); 45 } 46 });
+22 -1
src/facets/common/you.js
··· 87 /> 88 </div> 89 <div> 90 <label>URI</label> 91 <input 92 id="add-uri-uri" ··· 118 "submit", 119 async (e) => { 120 e.preventDefault(); 121 const nameEl = /** @type {HTMLInputElement} */ ( 122 dialog?.querySelector("#add-uri-name") 123 ); 124 const uriEl = /** @type {HTMLInputElement} */ ( 125 dialog?.querySelector("#add-uri-uri") 126 ); 127 const name = nameEl?.value.trim() ?? ""; 128 const uri = uriEl?.value.trim() ?? ""; 129 if (!name || !uri) return; 130 - const facet = await facetFromURI({ name, uri }, { fetchHTML: false }); 131 await saveFacet(facet); 132 /** @type {HTMLDialogElement} */ (dialog).close(); 133 }, 134 ); ··· 137 const nameEl = /** @type {HTMLInputElement} */ ( 138 dialog.querySelector("#add-uri-name") 139 ); 140 const uriEl = /** @type {HTMLInputElement} */ ( 141 dialog.querySelector("#add-uri-uri") 142 ); 143 if (nameEl) nameEl.value = ""; 144 if (uriEl) uriEl.value = ""; 145 146 dialog.showModal();
··· 87 /> 88 </div> 89 <div> 90 + <label>Kind</label> 91 + <select id="add-uri-kind"> 92 + <option value="interactive">interface</option> 93 + <option value="prelude">feature</option> 94 + </select> 95 + </div> 96 + <div> 97 <label>URI</label> 98 <input 99 id="add-uri-uri" ··· 125 "submit", 126 async (e) => { 127 e.preventDefault(); 128 + 129 const nameEl = /** @type {HTMLInputElement} */ ( 130 dialog?.querySelector("#add-uri-name") 131 ); 132 + 133 + const kindEl = /** @type {HTMLSelectElement} */ ( 134 + dialog?.querySelector("#add-uri-kind") 135 + ); 136 + 137 const uriEl = /** @type {HTMLInputElement} */ ( 138 dialog?.querySelector("#add-uri-uri") 139 ); 140 + 141 const name = nameEl?.value.trim() ?? ""; 142 + const kind = kindEl?.value ?? "interactive"; 143 const uri = uriEl?.value.trim() ?? ""; 144 if (!name || !uri) return; 145 + 146 + const facet = await facetFromURI({ kind, name, uri }, { fetchHTML: false }); 147 await saveFacet(facet); 148 + 149 /** @type {HTMLDialogElement} */ (dialog).close(); 150 }, 151 ); ··· 154 const nameEl = /** @type {HTMLInputElement} */ ( 155 dialog.querySelector("#add-uri-name") 156 ); 157 + const kindEl = /** @type {HTMLSelectElement} */ ( 158 + dialog.querySelector("#add-uri-kind") 159 + ); 160 const uriEl = /** @type {HTMLInputElement} */ ( 161 dialog.querySelector("#add-uri-uri") 162 ); 163 if (nameEl) nameEl.value = ""; 164 + if (kindEl) kindEl.value = "interactive"; 165 if (uriEl) uriEl.value = ""; 166 167 dialog.showModal();
+1
src/facets/scrobble/index.html
···
··· 1 + <script type="module" src="./index.inline.js"></script>
+2
src/facets/scrobble/index.inline.js
···
··· 1 + import foundation from "~/common/facets/foundation.js"; 2 + await foundation.orchestrator.scrobbleAudio();
+5 -4
src/styles/diffuse/page.css
··· 225 select { 226 appearance: none; 227 background: transparent; 228 - border: 2px solid var(--form-color); 229 - border-radius: var(--radius-md); 230 color: inherit; 231 font-family: inherit; 232 font-size: var(--fs-sm); ··· 304 border-radius: 9999px; 305 display: inline-block; 306 font-size: var(--fs-2xs); 307 - font-weight: 500; 308 - padding: 1px var(--space-3xs); 309 vertical-align: middle; 310 } 311
··· 225 select { 226 appearance: none; 227 background: transparent; 228 + border: 3px solid var(--form-color); 229 + border-radius: var(--radius-sm); 230 color: inherit; 231 font-family: inherit; 232 font-size: var(--fs-sm); ··· 304 border-radius: 9999px; 305 display: inline-block; 306 font-size: var(--fs-2xs); 307 + font-weight: 600; 308 + padding: var(--space-3xs); 309 + text-box: trim-both cap alphabetic; 310 vertical-align: middle; 311 } 312