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.

chore: save webamp ui state

+115 -6
+8 -3
src/themes/winamp/facet/index.inline.js
··· 3 3 4 4 import WindowManager from "~/themes/winamp/window-manager/element.js"; 5 5 import WebampElement from "~/themes/winamp/webamp/element.js"; 6 - import { setAudioEngine, setCurrentTrackIdResolver } from "~/themes/winamp/webamp/media.js"; 6 + import { 7 + setAudioEngine, 8 + setCurrentTrackIdResolver, 9 + } from "~/themes/winamp/webamp/media.js"; 7 10 8 11 // Set doc title 9 12 document.title = "Winamp | Diffuse"; ··· 17 20 * @import {Track} from "~/definitions/types.d.ts" 18 21 */ 19 22 20 - const input = await foundation.configurator.input(); 21 23 const audio = await foundation.engine.audio(); 22 24 const queue = await foundation.engine.queue(); 23 25 const repeatShuffle = await foundation.engine.repeatShuffle(); ··· 62 64 63 65 // Sync audio engine volume → webamp 64 66 effect(() => { 65 - amp.store.dispatch({ type: "SET_VOLUME", volume: Math.round(audio.volume() * 100) }); 67 + amp.store.dispatch({ 68 + type: "SET_VOLUME", 69 + volume: Math.round(audio.volume() * 100), 70 + }); 66 71 }); 67 72 68 73 // Sync Diffuse repeat → webamp
+107 -3
src/themes/winamp/webamp/element.js
··· 1 1 import Webamp from "webamp/lazy"; 2 + 3 + import { batch, effect, signal } from "~/common/signal.js"; 4 + import { diff, strictEquality } from "~/common/compare.js"; 2 5 import DiffuseMedia from "./media.js"; 3 6 4 7 /** 5 8 * @import {Track} from "~/definitions/types.d.ts" 6 9 */ 10 + 11 + const UI_STATE_KEY = "diffuse/themes/winamp/webamp/ui"; 12 + 13 + /** @returns {{ milkdropOpen: boolean, eqOpen: boolean, playlistOpen: boolean, eqOn: boolean, eqSliders: Record<string, number> | null }} */ 14 + function loadUiState() { 15 + try { 16 + return { 17 + milkdropOpen: true, 18 + eqOpen: true, 19 + playlistOpen: true, 20 + eqOn: false, 21 + eqSliders: null, 22 + ...JSON.parse(localStorage.getItem(UI_STATE_KEY) ?? "{}"), 23 + }; 24 + } catch { 25 + return { 26 + milkdropOpen: true, 27 + eqOpen: true, 28 + playlistOpen: true, 29 + eqOn: false, 30 + eqSliders: null, 31 + }; 32 + } 33 + } 34 + 35 + /** @param {Record<string, unknown>} partial */ 36 + function saveUiState(partial) { 37 + try { 38 + const current = JSON.parse(localStorage.getItem(UI_STATE_KEY) ?? "{}"); 39 + localStorage.setItem( 40 + UI_STATE_KEY, 41 + JSON.stringify({ ...current, ...partial }), 42 + ); 43 + } catch { /* ignore */ } 44 + } 45 + 7 46 class WebampElement extends HTMLElement { 8 47 constructor() { 9 48 super(); 10 49 11 50 // ⚡ 51 + 52 + const ui = loadUiState(); 12 53 13 54 /** @type {import("webamp/lazy").default} */ 14 55 this.amp = new /** @type {any} */ (Webamp)({ ··· 37 78 butterchurnPresetObject: preset, 38 79 })); 39 80 }, 40 - butterchurnOpen: true, 81 + butterchurnOpen: ui.milkdropOpen, 41 82 }, 42 83 windowLayout: { 43 84 main: { position: { top: 0, left: 0 } }, 44 - equalizer: { position: { top: 116, left: 0 } }, 85 + equalizer: { position: { top: 116, left: 0 }, closed: !ui.eqOpen }, 45 86 playlist: { 46 87 position: { top: 232, left: 0 }, 47 88 size: { extraHeight: 4, extraWidth: 0 }, 89 + closed: !ui.playlistOpen, 48 90 }, 49 91 milkdrop: { 50 92 position: { top: 0, left: 275 }, ··· 137 179 138 180 this.shadowRoot?.appendChild(ampNode); 139 181 140 - return await this.amp.renderWhenReady(ampNode); 182 + await this.amp.renderWhenReady(ampNode); 183 + 184 + // Restore EQ settings 185 + const ui = loadUiState(); 186 + if (ui.eqSliders != null) { 187 + for (const [band, value] of Object.entries(ui.eqSliders)) { 188 + // @ts-ignore 189 + this.amp.store.dispatch({ type: "SET_BAND_VALUE", band, value }); 190 + } 191 + } 192 + 193 + this.amp.store.dispatch({ type: ui.eqOn ? "SET_EQ_ON" : "SET_EQ_OFF" }); 194 + 195 + // Signals for each piece of persisted UI state 196 + const milkdropOpenSig = signal(ui.milkdropOpen, { 197 + compare: strictEquality, 198 + }); 199 + 200 + const playlistOpenSig = signal(ui.playlistOpen, { 201 + compare: strictEquality, 202 + }); 203 + 204 + const eqOnSig = signal(ui.eqOn, { compare: strictEquality }); 205 + const eqOpenSig = signal(ui.eqOpen, { compare: strictEquality }); 206 + const eqSlidersSig = signal(ui.eqSliders, { compare: diff }); 207 + 208 + // Update signals in batch on every store change 209 + this.amp.store.subscribe(() => { 210 + const state = this.amp.store.getState(); 211 + batch(() => { 212 + milkdropOpenSig.set(state.windows?.genWindows?.milkdrop?.open ?? true); 213 + eqOpenSig.set(state.windows?.genWindows?.equalizer?.open ?? true); 214 + playlistOpenSig.set(state.windows?.genWindows?.playlist?.open ?? true); 215 + eqOnSig.set(state.equalizer?.on ?? false); 216 + eqSlidersSig.set(state.equalizer?.sliders ?? null); 217 + }); 218 + }); 219 + 220 + // Save to localStorage when any signal changes (debounced) 221 + let saveTimer = /** @type {ReturnType<typeof setTimeout> | null} */ (null); 222 + let initialized = false; 223 + 224 + effect(() => { 225 + const snapshot = { 226 + milkdropOpen: milkdropOpenSig.value, 227 + eqOpen: eqOpenSig.value, 228 + playlistOpen: playlistOpenSig.value, 229 + eqOn: eqOnSig.value, 230 + eqSliders: eqSlidersSig.value, 231 + }; 232 + 233 + if (!initialized) { 234 + initialized = true; 235 + return; 236 + } 237 + 238 + if (saveTimer != null) clearTimeout(saveTimer); 239 + 240 + saveTimer = setTimeout(() => { 241 + saveTimer = null; 242 + saveUiState(snapshot); 243 + }, 5000); 244 + }); 141 245 } 142 246 } 143 247