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.

at v4 170 lines 4.4 kB view raw
1import deepDiff from "@fry69/deep-diff"; 2 3import * as Output from "~/common/output.js"; 4import { BroadcastableDiffuseElement, defineElement, query } from "~/common/element.js"; 5import { groupTracksPerScheme } from "~/common/utils.js"; 6import { signal } from "~/common/signal.js"; 7 8import { DISABLED_KEY } from "./constants.js"; 9import { parseDisabledUris, uriKey } from "./common.js"; 10 11/** 12 * @import {InputElement, Source} from "~/components/input/types.d.ts" 13 * @import {OutputElement} from "~/components/output/types.d.ts" 14 */ 15 16//////////////////////////////////////////// 17// ELEMENT 18//////////////////////////////////////////// 19 20class Sources extends BroadcastableDiffuseElement { 21 static NAME = "diffuse/orchestrator/sources"; 22 23 // SIGNALS 24 25 #sources = signal(/** @type {{ [scheme: string]: Source[] }} */ ({})); 26 #disabled = signal(/** @type {string[]} */ ([])); 27 28 // STATE 29 30 sources = this.#sources.get; 31 disabled = this.#disabled.get; 32 33 #output = signal(/** @type {OutputElement | null} */ (null)); 34 35 // METHODS 36 37 /** 38 * Returns whether the given source URI is disabled. 39 * Strips query params before comparing, matching how {@link toggle} stores keys. 40 * 41 * @param {string} uri 42 * @returns {boolean} 43 */ 44 isDisabled(uri) { 45 return this.#disabled.get().includes(uriKey(uri)); 46 } 47 48 /** 49 * @param {string} uri 50 */ 51 async toggle(uri) { 52 const key = uriKey(uri); 53 54 const output = this.#output.value; 55 if (!output) { 56 console.warn("Output element is not available yet."); 57 return; 58 } 59 60 const settings = await Output.data(output.settings); 61 const existing = settings.find((s) => s.key === DISABLED_KEY); 62 63 let disabled = parseDisabledUris(settings); 64 65 if (disabled.includes(key)) { 66 disabled = disabled.filter((u) => u !== key); 67 } else { 68 disabled = [...disabled, key]; 69 } 70 71 const value = JSON.stringify(disabled); 72 const updated = existing 73 ? settings.map((s) => 74 s.key === DISABLED_KEY ? { ...s, value } : s 75 ) 76 : [ 77 ...settings, 78 { 79 $type: /** @type {"sh.diffuse.output.setting"} */ ( 80 "sh.diffuse.output.setting" 81 ), 82 id: crypto.randomUUID(), 83 key: DISABLED_KEY, 84 value, 85 }, 86 ]; 87 88 await output.settings.save(updated); 89 } 90 91 // LIFECYCLE 92 93 /** 94 * @override 95 */ 96 async connectedCallback() { 97 super.connectedCallback(); 98 99 /** @type {InputElement} */ 100 const input = query(this, "input-selector"); 101 102 /** @type {OutputElement} */ 103 const output = query(this, "output-selector"); 104 105 // Wait until defined 106 await customElements.whenDefined(input.localName); 107 await customElements.whenDefined(output.localName); 108 109 // Signals 110 this.#output.value = output; 111 112 // Effects 113 this.effect(() => { 114 const col = output.settings.collection(); 115 if (col.state !== "loaded") { this.#disabled.value = []; return; } 116 this.#disabled.value = parseDisabledUris(col.data); 117 }); 118 119 // Single input mode + dependencies 120 const singleInputMode = !!input.SCHEME; 121 const deps = 122 /** @type {{ [k: string]: InputElement }} */ (singleInputMode 123 ? {} 124 : input.dependencies()); 125 126 // Effects 127 this.effect(() => { 128 const col = output.tracks.collection(); 129 const tracks = col.state === "loaded" ? col.data : []; 130 const groups = groupTracksPerScheme(tracks); 131 132 /** @type {{ [scheme: string]: Source[] }} */ 133 const record = {}; 134 135 Object.entries(groups).map(([scheme, tracks]) => { 136 /** @type {Source[]} */ 137 let sources; 138 139 if (singleInputMode) { 140 if (input.SCHEME === scheme) { 141 sources = input.sources(tracks); 142 } else { 143 sources = []; 144 } 145 } else { 146 const dep = deps[scheme]; 147 if (!dep) sources = tracks.map((t) => ({ label: t.uri, uri: t.uri })); 148 else sources = dep.sources(tracks); 149 } 150 151 record[scheme] = sources; 152 }); 153 154 if (deepDiff(this.#sources.value, record)) { 155 this.#sources.value = record; 156 } 157 }); 158 } 159} 160 161export default Sources; 162 163//////////////////////////////////////////// 164// REGISTER 165//////////////////////////////////////////// 166 167export const CLASS = Sources; 168export const NAME = "do-sources"; 169 170defineElement(NAME, CLASS);