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

wip: output configurator

Changed files
+183 -122
src
components
configurator
output
output
polymorphic
transformer
output
refiner
default
string
json
themes
webamp
+67
src/components/configurator/output/element.js
··· 1 + import { DiffuseElement } from "@common/element.js"; 2 + import { computed, signal } from "@common/signal.js"; 3 + 4 + /** 5 + * @import {ProxiedActions} from "@common/worker.d.ts" 6 + * @import {Track} from "@definitions/types.d.ts" 7 + * @import {OutputManager, OutputElement} from "@components/output/types.d.ts" 8 + */ 9 + 10 + //////////////////////////////////////////// 11 + // ELEMENT 12 + //////////////////////////////////////////// 13 + 14 + /** 15 + * @implements {OutputManager<Track[]>} 16 + */ 17 + class OutputConfigurator extends DiffuseElement { 18 + static NAME = "diffuse/configurator/output"; 19 + static WORKER_URL = "components/configurator/output/worker.js"; 20 + 21 + constructor() { 22 + super(); 23 + 24 + /** @type {OutputManager<Track[]>} */ 25 + const manager = { 26 + tracks: { 27 + collection: computed(() => { 28 + return this.#memory.tracks.value; 29 + }), 30 + reload: async () => {}, 31 + save: async (newTracks) => { 32 + this.#memory.tracks.value = newTracks; 33 + }, 34 + state: () => "loaded", 35 + }, 36 + }; 37 + 38 + // Assign manager properties to class 39 + this.tracks = manager.tracks; 40 + } 41 + 42 + // SIGNALS 43 + 44 + #memory = { 45 + tracks: signal(/** @type {Track[]} */ ([])), 46 + }; 47 + 48 + // LIFECYCLE 49 + 50 + /** 51 + * @override 52 + */ 53 + async connectedCallback() { 54 + super.connectedCallback(); 55 + } 56 + } 57 + 58 + export default OutputConfigurator; 59 + 60 + //////////////////////////////////////////// 61 + // REGISTER 62 + //////////////////////////////////////////// 63 + 64 + export const CLASS = OutputConfigurator; 65 + export const NAME = "dc-output"; 66 + 67 + customElements.define(NAME, CLASS);
+12 -5
src/components/output/polymorphic/indexed-db/element.js
··· 4 4 /** 5 5 * @import {ProxiedActions} from "@common/worker.d.ts" 6 6 * @import {OutputManager, OutputWorkerActions} from "../../types.d.ts" 7 + * @import {SupportedDataTypes} from "./types.d.ts" 7 8 */ 8 9 9 10 //////////////////////////////////////////// ··· 20 21 constructor() { 21 22 super(); 22 23 23 - /** @type {ProxiedActions<OutputWorkerActions>} */ 24 + /** @type {ProxiedActions<OutputWorkerActions<SupportedDataTypes>>} */ 24 25 const p = this.workerProxy(); 25 26 26 - // Manager 27 + /** @type {OutputManager<SupportedDataTypes>} */ 27 28 const manager = outputManager({ 28 29 tracks: { 29 - empty: () => [], 30 - get: p.getTracks, 31 - put: p.putTracks, 30 + empty: () => undefined, 31 + get: () => p.get({ name: this.#cat("tracks") }), 32 + put: (data) => p.put({ name: this.#cat("tracks"), data }), 32 33 }, 33 34 }); 34 35 35 36 this.tracks = manager.tracks; 37 + } 38 + 39 + /** @param {string} name */ 40 + #cat(name) { 41 + const key = this.hasAttribute("key") ? this.getAttribute("key") + "/" : ""; 42 + return `${key}${name}`; 36 43 } 37 44 } 38 45
+1
src/components/output/polymorphic/indexed-db/types.d.ts
··· 1 + export type SupportedDataTypes = any;
+10 -30
src/components/output/polymorphic/indexed-db/worker.js
··· 4 4 import { ostiary, rpc } from "@common/worker.js"; 5 5 6 6 /** 7 - * @import {Track} from "@definitions/types.d.ts"; 7 + * @import {OutputWorkerActions} from "@components/output/types.d.ts"; 8 + * @import {SupportedDataTypes} from "./types.d.ts" 8 9 */ 9 10 10 11 //////////////////////////////////////////// ··· 12 13 //////////////////////////////////////////// 13 14 14 15 /** 15 - * @returns {Promise<Track[]>} 16 + * @type {OutputWorkerActions<SupportedDataTypes>["get"]} 16 17 */ 17 - export async function getTracks() { 18 - /** @type {Track[] | null} */ 19 - const tracks = await get({ name: "tracks.json" }); 20 - return tracks ?? []; 18 + export async function get({ name }) { 19 + return await IDB.get(`${IDB_PREFIX}/${name}`); 21 20 } 22 21 23 22 /** 24 - * @param {Track[]} tracks 23 + * @type {OutputWorkerActions<SupportedDataTypes>["put"]} 25 24 */ 26 - export async function putTracks(tracks) { 27 - await put({ name: "tracks.json", data: tracks }); 25 + export async function put({ data, name }) { 26 + return await IDB.set(`${IDB_PREFIX}/${name}`, data); 28 27 } 29 - 30 28 //////////////////////////////////////////// 31 29 // ⚡️ 32 30 //////////////////////////////////////////// 33 31 34 32 ostiary((context) => { 35 33 rpc(context, { 36 - getTracks, 37 - putTracks, 34 + get, 35 + put, 38 36 }); 39 37 }); 40 - 41 - //////////////////////////////////////////// 42 - // ⛔️ 43 - //////////////////////////////////////////// 44 - 45 - /** 46 - * @param {{ name: string }} _ 47 - */ 48 - async function get({ name }) { 49 - return await IDB.get(`${IDB_PREFIX}/${name}`); 50 - } 51 - 52 - /** 53 - * @param {{ data: any; name: string }} _ 54 - */ 55 - async function put({ data, name }) { 56 - return await IDB.set(`${IDB_PREFIX}/${name}`, data); 57 - }
+3 -4
src/components/output/types.d.ts
··· 1 1 import type { SignalReader } from "@common/signal.d.ts"; 2 - import type { Track } from "@definitions/types.d.ts"; 3 2 import type { DiffuseElement } from "@common/element.js"; 4 3 5 4 export type OutputElement<Tracks> = DiffuseElement & OutputManager<Tracks>; ··· 22 21 }; 23 22 }; 24 23 25 - export type OutputWorkerActions = { 26 - getTracks(): Promise<Track[]>; 27 - putTracks(tracks: Track[]): Promise<void>; 24 + export type OutputWorkerActions<DataType> = { 25 + get(args: { name: string }): Promise<DataType>; 26 + put(args: { data: DataType; name: string }): Promise<void>; 28 27 };
+65
src/components/transformer/output/base.js
··· 1 + import { DiffuseElement, query } from "@common/element.js"; 2 + import { computed, signal } from "@common/signal.js"; 3 + 4 + /** 5 + * @import { OutputElement, OutputManager } from "../../output/types.d.ts" 6 + */ 7 + 8 + /** 9 + * @template T 10 + */ 11 + export class OutputTransformer extends DiffuseElement { 12 + // SIGNALS 13 + 14 + #output = signal(/** @type {OutputElement<T> | undefined} */ (undefined)); 15 + #outputWhenDefined = Promise.withResolvers(); 16 + 17 + output = { 18 + whenDefined: this.#outputWhenDefined.promise, 19 + signal: this.#output.get, 20 + }; 21 + 22 + // LIFECYCLE 23 + 24 + /** 25 + * @override 26 + */ 27 + connectedCallback() { 28 + super.connectedCallback(); 29 + 30 + /** @type {OutputElement<T>} */ 31 + const output = query(this, "output-selector"); 32 + 33 + // When defined 34 + customElements.whenDefined(output.localName).then(() => { 35 + this.#output.value = output; 36 + this.#outputWhenDefined.resolve(null); 37 + }); 38 + } 39 + 40 + // MANAGER 41 + 42 + base() { 43 + /** @type {OutputManager<T | undefined>} */ 44 + const m = { 45 + tracks: { 46 + collection: computed(() => { 47 + return this.output.signal()?.tracks?.collection(); 48 + }), 49 + reload: () => { 50 + return this.output.signal()?.tracks?.reload() ?? Promise.resolve(); 51 + }, 52 + save: async (newTracks) => { 53 + if (newTracks === undefined) return; 54 + await this.output.whenDefined; 55 + await this.output.signal()?.tracks.save(newTracks); 56 + }, 57 + state: computed(() => 58 + this.output.signal()?.tracks.state() ?? "loading" 59 + ), 60 + }, 61 + }; 62 + 63 + return m; 64 + } 65 + }
+12 -39
src/components/transformer/output/refiner/default/element.js
··· 1 - import { DiffuseElement, query } from "@common/element.js"; 2 - import { computed, signal } from "@common/signal.js"; 1 + import { computed } from "@common/signal.js"; 2 + import { OutputTransformer } from "../../base.js"; 3 3 4 4 /** 5 - * @import { OutputElement, OutputManager } from "../../../../output/types.d.ts" 5 + * @import { OutputManager } from "../../../../output/types.d.ts" 6 6 * @import { Track } from "@definitions/types.d.ts" 7 7 */ 8 8 9 - class DefaultOutputRefinerTransformer extends DiffuseElement { 9 + /** 10 + * @extends {OutputTransformer<Track[]>} 11 + */ 12 + class DefaultOutputRefinerTransformer extends OutputTransformer { 10 13 constructor() { 11 14 super(); 15 + 16 + const base = this.base(); 12 17 13 18 /** @type {OutputManager<Track[]>} */ 14 19 const manager = { 15 20 tracks: { 21 + ...base.tracks, 16 22 collection: computed(() => { 17 - return this.#defined.value 18 - ? this.output?.tracks?.collection() ?? [] 19 - : []; 23 + return base.tracks.collection() ?? []; 20 24 }), 21 - reload: () => this.output?.tracks?.reload() ?? Promise.resolve(), 22 25 save: async (newTracks) => { 23 26 const filtered = newTracks.filter((t) => !t.ephemeral); 24 - 25 - if (!this.output) return; 26 - 27 - await customElements.whenDefined(this.output.localName); 28 - await this.output.tracks.save(filtered); 27 + await base.tracks.save(filtered); 29 28 }, 30 - state: computed(() => this.output?.tracks.state() ?? "loading"), 31 29 }, 32 30 }; 33 31 34 32 // Assign manager properties to class 35 33 this.tracks = manager.tracks; 36 - } 37 - 38 - /** @type {OutputElement<Track[]> | undefined} */ 39 - output = undefined; 40 - 41 - // SIGNALS 42 - 43 - #defined = signal(false); 44 - 45 - // LIFECYCLE 46 - 47 - /** 48 - * @override 49 - */ 50 - connectedCallback() { 51 - super.connectedCallback(); 52 - 53 - /** @type {OutputElement<Track[]>} */ 54 - const output = query(this, "output-selector"); 55 - this.output = output; 56 - 57 - // When defined 58 - customElements.whenDefined(this.output.localName).then( 59 - () => this.#defined.value = true, 60 - ); 61 34 } 62 35 } 63 36
+12 -43
src/components/transformer/output/string/json/element.js
··· 1 - import { DiffuseElement, query } from "@common/element.js"; 2 - import { computed, signal } from "@common/signal.js"; 1 + import { computed } from "@common/signal.js"; 2 + import { OutputTransformer } from "../../base.js"; 3 3 4 4 /** 5 - * @import { OutputElement, OutputManager } from "../../../../output/types.d.ts" 5 + * @import { OutputManager } from "../../../../output/types.d.ts" 6 6 * @import { Track } from "@definitions/types.d.ts" 7 7 */ 8 8 9 - class JsonStringOutputTransformer extends DiffuseElement { 9 + /** 10 + * @extends {OutputTransformer<string>} 11 + */ 12 + class JsonStringOutputTransformer extends OutputTransformer { 10 13 constructor() { 11 14 super(); 12 15 16 + const base = this.base(); 17 + 13 18 /** @type {OutputManager<Track[]>} */ 14 19 const manager = { 15 20 tracks: { 21 + ...base.tracks, 16 22 collection: computed(() => { 17 - const json = this.#defined.value 18 - ? this.output?.tracks?.collection() ?? [] 19 - : []; 20 - 21 - // In addition to the above, Some polymorphic outputs 22 - // use an empty array as the default return value. 23 - if (Array.isArray(json)) return json; 23 + const json = base.tracks.collection() ?? "[]"; 24 24 25 25 // Try parsing JSON 26 26 try { ··· 32 32 return []; 33 33 } 34 34 }), 35 - reload: () => this.output?.tracks?.reload() ?? Promise.resolve(), 36 35 save: async (newTracks) => { 37 36 const json = JSON.stringify(newTracks); 38 - 39 - if (!this.output) return; 40 - 41 - await customElements.whenDefined(this.output.localName); 42 - await this.output.tracks.save(json); 37 + await base.tracks.save(json); 43 38 }, 44 - state: computed(() => this.output?.tracks?.state() ?? "loading"), 45 39 }, 46 40 }; 47 41 48 42 // Assign manager properties to class 49 43 this.tracks = manager.tracks; 50 - } 51 - 52 - /** @type {OutputElement<string> | undefined} */ 53 - output = undefined; 54 - 55 - // SIGNALS 56 - 57 - #defined = signal(false); 58 - 59 - // LIFECYCLE 60 - 61 - /** 62 - * @override 63 - */ 64 - connectedCallback() { 65 - super.connectedCallback(); 66 - 67 - /** @type {OutputElement<string>} */ 68 - const output = query(this, "output-selector"); 69 - this.output = output; 70 - 71 - // When defined 72 - customElements.whenDefined(this.output.localName).then( 73 - () => this.#defined.value = true, 74 - ); 75 44 } 76 45 } 77 46
+1 -1
src/themes/webamp/index.js
··· 1 - // import "@components/orchestrator/process-tracks/element.js"; 1 + import "@components/orchestrator/process-tracks/element.js"; 2 2 import "@components/orchestrator/queue-tracks/element.js"; 3 3 import "@components/input/opensubsonic/element.js"; 4 4 import "@components/input/s3/element.js";