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

Configure Feed

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

feat: webamp browser constituent

+134 -31
+15
_config.ts
··· 51 51 site.add("/favicons", "/"); 52 52 site.add("/fonts"); 53 53 site.add("/images"); 54 + site.add([".woff2"]); 55 + 56 + site.remoteFile( 57 + "styles/vendor/ms_sans_serif.woff2", 58 + import.meta.resolve( 59 + "./node_modules/98.css/fonts/converted/ms_sans_serif.woff2", 60 + ), 61 + ); 62 + 63 + site.remoteFile( 64 + "styles/vendor/ms_sans_serif_bold.woff2", 65 + import.meta.resolve( 66 + "./node_modules/98.css/fonts/converted/ms_sans_serif_bold.woff2", 67 + ), 68 + ); 54 69 55 70 site.remoteFile( 56 71 "fonts/ms_sans_serif.woff2",
+20 -1
src/common/constituents/default.js
··· 2 2 import InputOrchestrator from "@components/orchestrator/input/element.js"; 3 3 import OutputOrchestrator from "@components/orchestrator/output/element.js"; 4 4 import QueueTracksOrchestrator from "@components/orchestrator/queue-tracks/element.js"; 5 + import SearchProcessor from "@components/processor/search/element.js"; 6 + import SearchTracksOrchestrator from "@components/orchestrator/search-tracks/element.js"; 7 + 5 8 import { effect } from "../signal.js"; 9 + import QueueAudioOrchestrator from "@components/orchestrator/queue-audio/element.js"; 6 10 7 11 export const GROUP = "constituents"; 8 12 ··· 28 32 29 33 document.body.append(output); 30 34 35 + // Processors 36 + const search = new SearchProcessor(); 37 + search.setAttribute("group", GROUP); 38 + 39 + document.body.append(search); 40 + 31 41 // Orchestrators 32 42 const oqt = new QueueTracksOrchestrator(); 33 43 oqt.setAttribute("group", GROUP); ··· 35 45 oqt.setAttribute("output-selector", "#output"); 36 46 oqt.setAttribute("queue-engine-selector", queue.localName); 37 47 38 - document.body.append(oqt); 48 + const ost = new SearchTracksOrchestrator(); 49 + ost.setAttribute("group", GROUP); 50 + ost.setAttribute("input-selector", "#input"); 51 + ost.setAttribute("output-selector", "#output"); 52 + ost.setAttribute("search-processor-selector", search.localName); 53 + 54 + document.body.append(oqt, ost); 39 55 40 56 // Signals & effects 41 57 effect(() => { ··· 64 80 input, 65 81 output, 66 82 queueTracks: oqt, 83 + }, 84 + processor: { 85 + search, 67 86 }, 68 87 }; 69 88 }
+9 -2
src/components/orchestrator/process-tracks/element.js
··· 1 - import { DiffuseElement, query } from "@common/element.js"; 1 + import { BroadcastableDiffuseElement, query } from "@common/element.js"; 2 2 import { signal, untracked } from "@common/signal.js"; 3 3 4 4 /** ··· 19 19 * the already existing tracks are loaded 20 20 * from the assigned output element. 21 21 */ 22 - class ProcessTracksOrchestrator extends DiffuseElement { 22 + class ProcessTracksOrchestrator extends BroadcastableDiffuseElement { 23 23 static NAME = "diffuse/orchestrator/process-tracks"; 24 24 static WORKER_URL = "components/orchestrator/process-tracks/worker.js"; 25 25 ··· 49 49 * @override 50 50 */ 51 51 async connectedCallback() { 52 + // Broadcast if needed 53 + if (this.hasAttribute("group")) { 54 + this.broadcast(this.nameWithGroup, {}); 55 + } 56 + 57 + // Super 52 58 super.connectedCallback(); 53 59 54 60 /** @type {InputElement} */ ··· 98 104 99 105 async process() { 100 106 if (!this.output) return; 107 + if (!(await this.isLeader())) return; 101 108 102 109 // Start 103 110 this.#isProcessing.value = true;
+11 -3
src/components/orchestrator/queue-audio/element.js
··· 1 - import { DiffuseElement, query } from "@common/element.js"; 1 + import { BroadcastableDiffuseElement, query } from "@common/element.js"; 2 2 import { untracked } from "@common/signal.js"; 3 3 4 4 /** ··· 16 16 * Vice versa, when the audio ends, 17 17 * shift the queue if needed. 18 18 */ 19 - class QueueAudioOrchestrator extends DiffuseElement { 19 + class QueueAudioOrchestrator extends BroadcastableDiffuseElement { 20 20 /** 21 21 * @override 22 22 */ 23 23 async connectedCallback() { 24 + // Broadcast if needed 25 + if (this.hasAttribute("group")) { 26 + this.broadcast(this.nameWithGroup, {}); 27 + } 28 + 29 + // Super 24 30 super.connectedCallback(); 25 31 26 32 /** @type {InputElement} */ ··· 50 56 if (!this.queue) return; 51 57 52 58 const activeTrack = this.queue.now(); 59 + if ((await this.isLeader()) === false) return; 60 + 53 61 const isPlaying = untracked(this.audio.isPlaying); 54 62 55 63 // Resolve URIs ··· 89 97 const now = this.queue.now(); 90 98 const aud = now ? this.audio.state(now.id) : undefined; 91 99 92 - if (aud?.hasEnded()) await this.queue.shift(); 100 + if (aud?.hasEnded() && (await this.isLeader())) await this.queue.shift(); 93 101 } 94 102 } 95 103
+13 -6
src/components/orchestrator/search-tracks/element.js
··· 1 - import { DiffuseElement, query } from "@common/element.js"; 1 + import { BroadcastableDiffuseElement, query } from "@common/element.js"; 2 2 3 3 /** 4 4 * @import {Track} from "@definitions/types.d.ts" ··· 18 18 * tracks whenever they have been loaded, 19 19 * or the tracks collection changes. 20 20 */ 21 - class SearchTracksOrchestrator extends DiffuseElement { 21 + class SearchTracksOrchestrator extends BroadcastableDiffuseElement { 22 22 static NAME = "diffuse/orchestrator/search-tracks"; 23 23 static WORKER_URL = "components/orchestrator/search-tracks/worker.js"; 24 24 ··· 42 42 * @override 43 43 */ 44 44 async connectedCallback() { 45 + // Broadcast if needed 46 + if (this.hasAttribute("group")) { 47 + this.broadcast(this.nameWithGroup, {}); 48 + } 49 + 50 + // Super 45 51 super.connectedCallback(); 46 52 47 53 /** @type {InputElement} */ ··· 62 68 await customElements.whenDefined(this.output.localName); 63 69 64 70 // Watch tracks collection 65 - this.effect(() => { 66 - const tracks = output.tracks.collection().filter((t) => 67 - t.kind !== "placeholder" 68 - ); 71 + this.effect(async () => { 72 + const collection = output.tracks.collection(); 73 + 74 + if ((await this.isLeader()) === false) return; 69 75 76 + const tracks = collection.filter((t) => t.kind !== "placeholder"); 70 77 this.#proxy.supplyAvailable(tracks); 71 78 }); 72 79 }
+2 -3
src/themes/blur/artwork-controller/index.vto
··· 36 36 import("./themes/blur/artwork-controller/element.js") 37 37 38 38 // Orchestrators 39 - 40 39 const oqa = new QueueAudioOrchestrator(); 41 40 oqa.setAttribute("group", defaults.GROUP); 42 - oqa.setAttribute("input-selector", defaults.configurator.input.localName); 41 + oqa.setAttribute("input-selector", "#" + defaults.orchestrator.input.id); 43 42 oqa.setAttribute("audio-engine-selector", "de-audio"); 44 43 oqa.setAttribute("queue-engine-selector", defaults.engine.queue.localName); 45 44 46 - document.body.append(oqa); 45 + document.body.append(oqa) 47 46 </script>
+15 -3
src/themes/webamp/browser/element.js
··· 92 92 inFront: true, 93 93 tracks: [track], 94 94 }); 95 + 96 + this.queue?.shift(); 95 97 } 96 98 97 99 async performSearch() { ··· 116 118 <style> 117 119 @import "./themes/webamp/98-vars.css"; 118 120 121 + :host { 122 + display: flex; 123 + flex-direction: column; 124 + height: 100%; 125 + } 126 + 119 127 /*********************************** 120 128 * SEARCH 121 129 ***********************************/ ··· 133 141 ***********************************/ 134 142 135 143 .sunken-panel { 136 - height: 30dvh; 144 + flex: 1; 137 145 min-height: 80px; 146 + } 147 + 148 + :host([resizable]) .sunken-panel { 138 149 resize: both; 139 150 } 140 151 ··· 165 176 166 177 <search class="field-row"> 167 178 <label for="search-input">Search</label> 168 - <input id="search-input" type="search" @change="${this.performSearch}" /> 179 + <input id="search-input" type="search" @change="${this 180 + .performSearch}" /> 169 181 </search> 170 182 171 - <div class="sunken-panel" style="width: 480px"> 183 + <div class="sunken-panel"> 172 184 <table> 173 185 <thead> 174 186 <tr>
+28 -10
src/themes/webamp/browser/index.vto
··· 4 4 5 5 styles: 6 6 - styles/vendor/98.css 7 + - themes/webamp/fonts.css 8 + - themes/webamp/constituent.css 7 9 --- 8 10 9 11 <!-- ELEMENTS --> 10 12 11 - <dtw-window id="browser-window" open> 12 - <span slot="title-icon"><img src="images/icons/windows_98/directory_explorer-4.png" height="14" /></span> 13 - <span slot="title">Browse collection</span> 14 - <dtw-browser 15 - input-selector="#input" 16 - output-selector="#output" 17 - queue-engine-selector="de-queue" 18 - search-processor-selector="dp-search" 19 - ></dtw-browser> 20 - </dtw-window> 13 + <div class="window"> 14 + <div 15 + class="title-bar" 16 + > 17 + <div class="title-bar-icon"> 18 + <img src="images/icons/windows_98/directory_explorer-4.png" height="14" /> 19 + </div> 20 + <div class="title-bar-text" draggable="false"> 21 + Browse collection 22 + </div> 23 + <div class="title-bar-controls"> 24 + <button aria-label="Close" onclick="window.close()"></button> 25 + </div> 26 + </div> 27 + <div class="window-body"> 28 + <dtw-browser 29 + input-selector="#input" 30 + output-selector="#output" 31 + queue-engine-selector="de-queue" 32 + search-processor-selector="dp-search" 33 + ></dtw-browser> 34 + </div> 35 + </div> 21 36 22 37 <!-- SCRIPTS --> 23 38 ··· 26 41 27 42 // Prepare default constituents setup 28 43 const defaults = config() 44 + 45 + // Only then initialize the other elements 46 + import("./themes/webamp/browser/element.js") 29 47 </script>
+15
src/themes/webamp/constituent.css
··· 1 + body { 2 + margin: 0; 3 + } 4 + 5 + .window { 6 + box-sizing: border-box; 7 + display: flex; 8 + flex-direction: column; 9 + height: 100dvh; 10 + } 11 + 12 + .window-body { 13 + flex: 1; 14 + overflow: hidden; 15 + }
+1 -1
src/themes/webamp/index.vto
··· 89 89 </dtw-window> 90 90 91 91 <!-- BROWSER --> 92 - <dtw-window id="browser-window" open> 92 + <dtw-window id="browser-window" window-style="height: 380px; width: 560px;"> 93 93 <span slot="title-icon"><img src="images/icons/windows_98/directory_explorer-4.png" height="14" /></span> 94 94 <span slot="title">Browse collection</span> 95 95 <dtw-browser
+5 -2
src/themes/webamp/window/element.js
··· 44 44 padding: 0; 45 45 } 46 46 47 - .window { 47 + .window-body { 48 48 min-width: 240px; 49 + overflow: hidden; 50 + resize: both; 49 51 } 50 52 51 53 .title-bar { ··· 81 83 this.removeAttribute("open")}"></button> 82 84 </div> 83 85 </div> 84 - <div class="window-body"> 86 + <div class="window-body" style="${this.getAttribute(`window-style`) ?? 87 + ``}"> 85 88 <slot></slot> 86 89 </div> 87 90 </div>