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: output fallback configurator

+259 -18
+224
src/components/configurator/output-fallback/element.js
··· 1 + import { DiffuseElement } from "@common/element.js"; 2 + import { batch, computed, signal } from "@common/signal.js"; 3 + 4 + /** 5 + * @import {Facet, Playlist, Theme, Track} from "@definitions/types.d.ts" 6 + * @import {OutputManagerDeputy, OutputElement} from "@components/output/types.d.ts" 7 + */ 8 + 9 + /** 10 + * @typedef {OutputElement} Output 11 + */ 12 + 13 + //////////////////////////////////////////// 14 + // ELEMENT 15 + //////////////////////////////////////////// 16 + 17 + /** 18 + * Output fallback configurator. 19 + * 20 + * Checks child output elements in order and delegates 21 + * to the first one whose `.ready()` signal returns `true`. 22 + * If none are ready, falls back to in-memory storage. 23 + * 24 + * @implements {OutputManagerDeputy} 25 + */ 26 + class OutputFallbackConfigurator extends DiffuseElement { 27 + static NAME = "diffuse/configurator/output-fallback"; 28 + 29 + constructor() { 30 + super(); 31 + 32 + /** @type {OutputManagerDeputy} */ 33 + const manager = { 34 + facets: { 35 + collection: computed(() => { 36 + const out = this.activeOutput(); 37 + if (out) return out.facets.collection(); 38 + return this.#memory.facets.value; 39 + }), 40 + reload: () => { 41 + const out = this.activeOutput(); 42 + if (out) return out.facets.reload(); 43 + return Promise.resolve(); 44 + }, 45 + save: async (newFacets) => { 46 + const out = this.activeOutput(); 47 + if (out) return await out.facets.save(newFacets); 48 + this.#memory.facets.value = newFacets; 49 + }, 50 + state: computed(() => { 51 + const out = this.activeOutput(); 52 + if (out) return out.facets.state(); 53 + return this.#setupFinished.value ? "loaded" : "sleeping"; 54 + }), 55 + }, 56 + playlists: { 57 + collection: computed(() => { 58 + const out = this.activeOutput(); 59 + if (out) return out.playlists.collection(); 60 + return this.#memory.playlists.value; 61 + }), 62 + reload: () => { 63 + const out = this.activeOutput(); 64 + if (out) return out.playlists.reload(); 65 + return Promise.resolve(); 66 + }, 67 + save: async (newPlaylists) => { 68 + const out = this.activeOutput(); 69 + if (out) return await out.playlists.save(newPlaylists); 70 + this.#memory.playlists.value = newPlaylists; 71 + }, 72 + state: computed(() => { 73 + const out = this.activeOutput(); 74 + if (out) return out.playlists.state(); 75 + return this.#setupFinished.value ? "loaded" : "sleeping"; 76 + }), 77 + }, 78 + themes: { 79 + collection: computed(() => { 80 + const out = this.activeOutput(); 81 + if (out) return out.themes.collection(); 82 + return this.#memory.themes.value; 83 + }), 84 + reload: () => { 85 + const out = this.activeOutput(); 86 + if (out) return out.themes.reload(); 87 + return Promise.resolve(); 88 + }, 89 + save: async (newThemes) => { 90 + const out = this.activeOutput(); 91 + if (out) return await out.themes.save(newThemes); 92 + this.#memory.themes.value = newThemes; 93 + }, 94 + state: computed(() => { 95 + const out = this.activeOutput(); 96 + if (out) return out.themes.state(); 97 + return this.#setupFinished.value ? "loaded" : "sleeping"; 98 + }), 99 + }, 100 + tracks: { 101 + collection: computed(() => { 102 + const out = this.activeOutput(); 103 + if (out) return out.tracks.collection(); 104 + return this.#memory.tracks.value; 105 + }), 106 + reload: () => { 107 + const out = this.activeOutput(); 108 + if (out) return out.tracks.reload(); 109 + return Promise.resolve(); 110 + }, 111 + save: async (newTracks) => { 112 + const out = this.activeOutput(); 113 + if (out) return await out.tracks.save(newTracks); 114 + this.#memory.tracks.value = newTracks; 115 + }, 116 + state: computed(() => { 117 + const out = this.activeOutput(); 118 + if (out) return out.tracks.state(); 119 + return this.#setupFinished.value ? "loaded" : "sleeping"; 120 + }), 121 + }, 122 + 123 + // Other 124 + ready: this.ready, 125 + }; 126 + 127 + this.facets = manager.facets; 128 + this.playlists = manager.playlists; 129 + this.themes = manager.themes; 130 + this.tracks = manager.tracks; 131 + this.ready = manager.ready; 132 + } 133 + 134 + // SIGNALS 135 + 136 + #memory = { 137 + facets: signal(/** @type {Facet[]} */ ([])), 138 + playlists: signal(/** @type {Playlist[]} */ ([])), 139 + themes: signal(/** @type {Theme[]} */ ([])), 140 + tracks: signal(/** @type {Track[]} */ ([])), 141 + }; 142 + 143 + #outputs = signal(/** @type {Output[]} */ ([])); 144 + #setupFinished = signal(false); 145 + 146 + // STATE 147 + 148 + /** 149 + * The first child output element whose `.ready()` returns `true`. 150 + */ 151 + activeOutput = computed(() => { 152 + const outputs = this.#outputs.value; 153 + // TODO: Not sure if this will cause a signal change too often. 154 + for (const output of outputs) { 155 + if (output.ready()) return output; 156 + } 157 + return null; 158 + }); 159 + 160 + ready = computed(() => { 161 + if (this.activeOutput()) return true; 162 + return this.#setupFinished.value; 163 + }); 164 + 165 + // LIFECYCLE 166 + 167 + /** 168 + * @override 169 + */ 170 + async connectedCallback() { 171 + super.connectedCallback(); 172 + 173 + const children = Array.from(this.root().children); 174 + 175 + /** @type {Output[]} */ 176 + const outputs = []; 177 + 178 + for (const el of children) { 179 + await customElements.whenDefined(el.localName); 180 + 181 + if ("nameWithGroup" in el && "tracks" in el) { 182 + outputs.push(/** @type {Output} */ (/** @type {unknown} */ (el))); 183 + } 184 + } 185 + 186 + batch(() => { 187 + this.#outputs.value = outputs; 188 + this.#setupFinished.value = true; 189 + }); 190 + } 191 + 192 + // MISC 193 + 194 + /** 195 + * @override 196 + */ 197 + dependencies = () => { 198 + return Object.fromEntries( 199 + Array.from(this.root().children).flatMap((element) => { 200 + if (element.hasAttribute("id") === false) { 201 + console.warn( 202 + "Missing `id` for output-fallback configurator child element with `localName` '" + 203 + element.localName + "'", 204 + ); 205 + return []; 206 + } 207 + 208 + const d = /** @type {DiffuseElement} */ (element); 209 + return [[d.id, d]]; 210 + }), 211 + ); 212 + }; 213 + } 214 + 215 + export default OutputFallbackConfigurator; 216 + 217 + //////////////////////////////////////////// 218 + // REGISTER 219 + //////////////////////////////////////////// 220 + 221 + export const CLASS = OutputFallbackConfigurator; 222 + export const NAME = "dc-output-fallback"; 223 + 224 + customElements.define(NAME, CLASS);
+3
src/components/configurator/output-fallback/types.d.ts
··· 1 + import type { OutputManagerDeputy } from "@components/output/types.d.ts"; 2 + 3 + export type OutputFallbackConfiguratorElement = OutputManagerDeputy;
+12 -10
src/components/configurator/output/element.js
··· 181 181 return this.#setupFinished.value ? "loaded" : "sleeping"; 182 182 }), 183 183 }, 184 + 185 + // Other 186 + ready: computed(() => { 187 + const out = this.#selectedOutput.value; 188 + if (out) return out.ready(); 189 + 190 + const def = this.#defaultOutput.value; 191 + if (def) return def.ready(); 192 + 193 + return this.#setupFinished.value; 194 + }), 184 195 }; 185 196 186 197 // Assign manager properties to class ··· 188 199 this.playlists = manager.playlists; 189 200 this.themes = manager.themes; 190 201 this.tracks = manager.tracks; 202 + this.ready = manager.ready 191 203 } 192 204 193 205 // SIGNALS ··· 212 224 // STATE 213 225 214 226 selectedOutput = computed(() => this.#selectedOutput.value ?? null); 215 - 216 - ready = computed(() => { 217 - const out = this.#selectedOutput.value; 218 - if (out) return out.ready(); 219 - 220 - const def = this.#defaultOutput.value; 221 - if (def) return def.ready(); 222 - 223 - return this.#setupFinished.value; 224 - }); 225 227 226 228 // LIFECYCLE 227 229
+4 -4
src/components/output/raw/atproto/oauth.js
··· 40 40 ? `http://localhost/?redirect_uri=${ 41 41 encodeURIComponent(redirect_uri) 42 42 }&scope=${encodeURIComponent("atproto transition:generic")}` 43 - : import.meta.env?.ATPROTO_CLIENT_ID ?? 43 + : /** @type {any} */ (import.meta).env?.ATPROTO_CLIENT_ID ?? 44 44 "https://elements.diffuse.sh/oauth-client-metadata.json", 45 45 redirect_uri, 46 46 }, ··· 67 67 * @param {string} handle 68 68 */ 69 69 export async function login(handle) { 70 - const location = globalThis.location 70 + const location = globalThis.location; 71 71 72 72 if (location.origin.startsWith("http://localhost")) { 73 73 location.assign( 74 74 location.href.replace("http://localhost:", "http://127.0.0.1:"), 75 - ) 75 + ); 76 76 } 77 77 78 78 const authUrl = await createAuthorizationUrl({ ··· 125 125 { allowStale: true }, 126 126 ); 127 127 } catch (err) { 128 - console.warn(err) 128 + console.warn(err); 129 129 localStorage.removeItem(STORAGE_KEY); 130 130 return null; 131 131 }
+4 -1
src/components/transformer/output/bytes/automerge/element.js
··· 148 148 await base.tracks.save(bytes); 149 149 }, 150 150 }, 151 + 152 + // Other 153 + ready: base.ready, 151 154 }; 152 155 153 156 // Assign manager properties to class ··· 155 158 this.playlists = manager.playlists; 156 159 this.themes = manager.themes; 157 160 this.tracks = manager.tracks; 158 - this.ready = base.ready; 161 + this.ready = manager.ready; 159 162 } 160 163 } 161 164
+4 -1
src/components/transformer/output/bytes/json/element.js
··· 77 77 await base.tracks.save(bytes); 78 78 }, 79 79 }, 80 + 81 + // Other 82 + ready: base.ready, 80 83 }; 81 84 82 85 // Assign manager properties to class ··· 84 87 this.playlists = manager.playlists; 85 88 this.themes = manager.themes; 86 89 this.tracks = manager.tracks; 87 - this.ready = base.ready; 90 + this.ready = manager.ready; 88 91 } 89 92 } 90 93
+4 -1
src/components/transformer/output/refiner/default/element.js
··· 44 44 await base.tracks.save(filtered); 45 45 }, 46 46 }, 47 + 48 + // Other 49 + ready: base.ready, 47 50 }; 48 51 49 52 // Assign manager properties to class ··· 51 54 this.playlists = manager.playlists; 52 55 this.themes = manager.themes; 53 56 this.tracks = manager.tracks; 54 - this.ready = base.ready; 57 + this.ready = manager.ready; 55 58 } 56 59 } 57 60
+4 -1
src/components/transformer/output/string/json/element.js
··· 60 60 await base.tracks.save(json); 61 61 }, 62 62 }, 63 + 64 + // Other 65 + ready: base.ready, 63 66 }; 64 67 65 68 // Assign manager properties to class ··· 67 70 this.playlists = manager.playlists; 68 71 this.themes = manager.themes; 69 72 this.tracks = manager.tracks; 70 - this.ready = base.ready; 73 + this.ready = manager.ready; 71 74 } 72 75 } 73 76