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.

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