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.

feat: processing progress

+102 -30
+3 -3
src/components/orchestrator/favourites/element.js
··· 60 60 // Broadcast if needed 61 61 if (this.hasAttribute("group")) { 62 62 const actions = this.broadcast(this.nameWithGroup, { 63 - include: { strategy: "replicate", fn: this.include }, 64 - expel: { strategy: "replicate", fn: this.expel }, 65 - toggle: { strategy: "replicate", fn: this.toggle }, 63 + include: { strategy: "leaderOnly", fn: this.include }, 64 + expel: { strategy: "leaderOnly", fn: this.expel }, 65 + toggle: { strategy: "leaderOnly", fn: this.toggle }, 66 66 }); 67 67 68 68 if (actions) {
+11 -1
src/components/orchestrator/process-tracks/element.js
··· 1 1 import { BroadcastableDiffuseElement, query } from "@common/element.js"; 2 2 import { signal, untracked } from "@common/signal.js"; 3 + import { listen } from "@common/worker.js"; 3 4 4 5 /** 5 6 * @import {ProxiedActions} from "@common/worker.d.ts" 6 7 * @import {InputElement} from "@components/input/types.d.ts" 7 8 * @import {OutputElement} from "@components/output/types.d.ts" 8 9 * 9 - * @import {Actions} from "./types.d.ts" 10 + * @import {Actions, Progress} from "./types.d.ts" 10 11 */ 11 12 12 13 //////////////////////////////////////////// ··· 37 38 // SIGNALS 38 39 39 40 #isProcessing = signal(false); 41 + #progress = signal(/** @type {Progress} */ ({ processed: 0, total: 0 })); 40 42 41 43 // STATE 42 44 43 45 isProcessing = this.#isProcessing.get; 46 + progress = this.#progress.get; 44 47 45 48 // LIFECYCLE 46 49 ··· 75 78 this.metadataProcessor = metadataProcessor; 76 79 77 80 // Wait until defined 81 + await customElements.whenDefined(input.localName); 78 82 await customElements.whenDefined(output.localName); 83 + await customElements.whenDefined(metadataProcessor.localName); 84 + 85 + // Sync progress with worker 86 + const link = this.workerLink(); 87 + listen("progress", this.#progress.set, link); 88 + this.#proxy.progress().then(this.#progress.set); 79 89 80 90 // Process whenever tracks are initially loaded 81 91 if (this.hasAttribute("process-when-ready")) {
+6
src/components/orchestrator/process-tracks/types.d.ts
··· 1 1 import type { Track } from "@definitions/types.d.ts"; 2 2 3 + export type Progress = { 4 + processed: number; 5 + total: number; 6 + }; 7 + 3 8 export type Actions = { 4 9 process: (tracks: Track[]) => Promise<Track[] | null>; 10 + progress: () => Progress; 5 11 };
+36 -4
src/components/orchestrator/process-tracks/worker.js
··· 1 1 import deepDiff from "@fry69/deep-diff"; 2 2 3 - import { ostiary, rpc, workerProxy } from "@common/worker.js"; 3 + import { effect, signal } from "@common/signal.js"; 4 + import { announce, ostiary, rpc, workerProxy } from "@common/worker.js"; 4 5 5 6 /** 6 7 * @import {Track} from "@definitions/types.d.ts" 7 8 * @import {ActionsWithTunnel, ProxiedActions} from "@common/worker.d.ts" 8 9 * @import {InputActions} from "@components/input/types.d.ts" 9 10 * @import {Actions as MetadataProcessorActions} from "@components/processor/metadata/types.d.ts" 11 + * 10 12 * @import {Actions} from "./types.d.ts" 11 13 */ 12 14 13 15 //////////////////////////////////////////// 16 + // STATE 17 + //////////////////////////////////////////// 18 + 19 + /** @type {import("@common/signal.d.ts").Signal<{processed: number, total: number}>} */ 20 + const $progress = signal({ processed: 0, total: 0 }); 21 + 22 + //////////////////////////////////////////// 14 23 // ACTIONS 15 24 //////////////////////////////////////////// 16 25 ··· 19 28 */ 20 29 export async function process({ data, ports }) { 21 30 const cachedTracks = data; 31 + 32 + // Reset progress 33 + $progress.value = { processed: 0, total: 0 }; 22 34 23 35 /** @type {ProxiedActions<InputActions>} */ 24 36 const input = workerProxy(() => ports.input); ··· 32 44 // List 33 45 const tracks = await input.list(cachedTracks); 34 46 47 + // Reset progress 48 + $progress.value = { processed: 0, total: tracks.length }; 49 + 35 50 // Fetch metadata if needed 51 + let processed = 0; 52 + 36 53 const tracksWithMetadata = await tracks.reduce( 37 54 /** 38 55 * @param {Promise<Track[]>} promise ··· 40 57 */ 41 58 async (promise, track) => { 42 59 const acc = await promise; 43 - if (track.tags && track.stats) return [...acc, track]; 60 + 61 + if (track.tags && track.stats) { 62 + processed++; 63 + $progress.value = { processed, total: tracks.length }; 64 + return [...acc, track]; 65 + } 44 66 45 67 const resGet = await input.resolve({ 46 68 method: "GET", 47 69 uri: track.uri, 48 70 }); 49 71 50 - if (!resGet) return [...acc, track]; 72 + if (!resGet) { 73 + processed++; 74 + $progress.value = { processed, total: tracks.length }; 75 + return [...acc, track]; 76 + } 51 77 52 78 const resHead = "stream" in resGet ? undefined : await input.resolve({ 53 79 method: "HEAD", ··· 63 89 } 64 90 : undefined, 65 91 }); 92 + 93 + processed++; 94 + $progress.value = { processed, total: tracks.length }; 66 95 67 96 return [...acc, { ...track, stats, tags }]; 68 97 }, ··· 83 112 //////////////////////////////////////////// 84 113 85 114 ostiary((context) => { 86 - rpc(context, { process }); 115 + rpc(context, { process, progress: $progress.get }); 116 + 117 + // Communicate state 118 + effect(() => announce("progress", $progress.value, context)); 87 119 });
+45 -21
src/themes/webamp/configurators/input/element.js
··· 1 - import { DiffuseElement, query, queryOptional } from "@common/element.js"; 1 + import { 2 + DiffuseElement, 3 + nothing, 4 + query, 5 + queryOptional, 6 + } from "@common/element.js"; 2 7 import { signal } from "@common/signal.js"; 3 8 4 9 import { buildURI as buildOpenSubsonicURI } from "@components/input/opensubsonic/common.js"; ··· 240 245 await this.$output.value?.tracks.save( 241 246 [...(this.$output.value?.tracks.collection() ?? []), track], 242 247 ); 248 + 249 + /** @type {HTMLInputElement | null} */ 250 + const overviewTab = this.root().querySelector("#overview-tab"); 251 + if (overviewTab) overviewTab.checked = true; 243 252 } 244 253 245 254 /** ··· 382 391 </span> 383 392 </fieldset> 384 393 385 - ${this.$processTracksOrchestrator.value?.isProcessing() 386 - ? html` 387 - <fieldset> 388 - <legend>Processing tracks</legend> 389 - <div class="with-icon with-icon--large"> 390 - <img 391 - src="images/icons/windows_98/gears-0.png" 392 - width="24" 393 - /> 394 - <span>Gathering metadata from your sources ...</span> 395 - </div> 396 - <div 397 - class="progress-indicator segmented" 398 - style="margin-top: var(--grouped-element-spacing);" 399 - > 400 - <div class="progress-indicator-bar" style="width: 100%"></div> 401 - </div> 402 - </fieldset> 403 - ` 404 - : ""} 394 + ${this.#renderProcessingProgress(html)} 405 395 </div> 406 396 407 397 <!-- HTTPS --> ··· 576 566 </div> 577 567 </div> 578 568 </div> 569 + `; 570 + } 571 + 572 + /** 573 + * @param {RenderArg["html"]} html 574 + */ 575 + #renderProcessingProgress(html) { 576 + const orchestrator = this.$processTracksOrchestrator.value; 577 + if (!orchestrator?.isProcessing()) return nothing; 578 + 579 + const { processed, total } = orchestrator.progress(); 580 + const percentage = total > 0 ? Math.round((processed / total) * 100) : 0; 581 + 582 + return html` 583 + <fieldset> 584 + <legend>Processing tracks</legend> 585 + <div class="with-icon with-icon--large"> 586 + <img 587 + src="images/icons/windows_98/gears-0.png" 588 + width="24" 589 + /> 590 + <span> 591 + ${total === 0 592 + ? `Going through all the inputs and gathering the tracks ...` 593 + : `Making sure each track has metadata & statistics (${processed} / ${total}) ...`} 594 + </span> 595 + </div> 596 + <div 597 + class="progress-indicator" 598 + style="margin-top: var(--grouped-element-spacing);" 599 + > 600 + <div class="progress-indicator-bar" style="width: ${percentage}%"></div> 601 + </div> 602 + </fieldset> 579 603 `; 580 604 } 581 605
+1 -1
src/themes/webamp/configurators/input/facet.html.txt
··· 27 27 28 28 const inp = foundation.orchestrator.input(); 29 29 const out = foundation.orchestrator.output(); 30 - const pro = foundation.orchestrator.processTracks({ disableWhenReady: false }); 30 + const pro = foundation.orchestrator.processTracks({ disableWhenReady: true }); 31 31 const sou = foundation.orchestrator.sources(); 32 32 33 33 const el = new InputConfigElement();