refactor(UIRenderer): convert to singleton

This commit is contained in:
Daniel 2026-03-21 18:14:37 -05:00
parent 2fdd169ba0
commit e32fbc3813
4 changed files with 85 additions and 68 deletions

109
js/app.js
View file

@ -264,23 +264,20 @@ function initializeKeyboardShortcuts(player, _audioPlayer) {
}, },
visualizerNext: () => { visualizerNext: () => {
trackKeyboardShortcut('VisualizerNext'); trackKeyboardShortcut('VisualizerNext');
const ui = window.monochromeUi; if (UIRenderer.instance.visualizer?.presets?.['butterchurn']) {
if (ui?.visualizer?.presets?.['butterchurn']) { UIRenderer.instance.visualizer.presets['butterchurn'].nextPreset();
ui.visualizer.presets['butterchurn'].nextPreset();
} }
}, },
visualizerPrev: () => { visualizerPrev: () => {
trackKeyboardShortcut('VisualizerPrev'); trackKeyboardShortcut('VisualizerPrev');
const ui = window.monochromeUi; if (UIRenderer.instance.visualizer?.presets?.['butterchurn']) {
if (ui?.visualizer?.presets?.['butterchurn']) { UIRenderer.instance.visualizer.presets['butterchurn'].prevPreset();
ui.visualizer.presets['butterchurn'].prevPreset();
} }
}, },
visualizerCycle: () => { visualizerCycle: () => {
trackKeyboardShortcut('VisualizerCycle'); trackKeyboardShortcut('VisualizerCycle');
const ui = window.monochromeUi; if (UIRenderer.instance.visualizer?.presets?.['butterchurn']) {
if (ui?.visualizer?.presets?.['butterchurn']) { UIRenderer.instance.visualizer.presets['butterchurn'].toggleCycle();
ui.visualizer.presets['butterchurn'].toggleCycle();
} }
}, },
}; };
@ -386,10 +383,11 @@ document.addEventListener('DOMContentLoaded', async () => {
if (import.meta.env.DEV) { if (import.meta.env.DEV) {
window.monochrome = { window.monochrome = {
MusicAPI,
LyricsManager,
Player,
HiFiClient, HiFiClient,
LyricsManager,
MusicAPI,
Player,
UIRenderer,
}; };
} }
@ -506,8 +504,7 @@ document.addEventListener('DOMContentLoaded', async () => {
const castBtn = document.getElementById('cast-btn'); const castBtn = document.getElementById('cast-btn');
initializeCasting(audioPlayer, castBtn); initializeCasting(audioPlayer, castBtn);
const ui = new UIRenderer(MusicAPI.instance, Player.instance); await UIRenderer.initialize(MusicAPI.instance, Player.instance);
window.monochromeUi = ui;
/** /**
* Scans the configured local media folder and refreshes `window.localFilesCache`. * Scans the configured local media folder and refreshes `window.localFilesCache`.
@ -559,7 +556,7 @@ document.addEventListener('DOMContentLoaded', async () => {
const metadata = await readTrackMetadata(file); const metadata = await readTrackMetadata(file);
metadata.id = `local-${idCounter++}-${entry.entry}`; metadata.id = `local-${idCounter++}-${entry.entry}`;
tracks.push(metadata); tracks.push(metadata);
window.monochromeUi?.renderLocalFiles( UIRenderer.instance.renderLocalFiles(
document.getElementById('library-local-container') document.getElementById('library-local-container')
); );
} catch (e) { } catch (e) {
@ -596,7 +593,7 @@ document.addEventListener('DOMContentLoaded', async () => {
const metadata = await readTrackMetadata(file); const metadata = await readTrackMetadata(file);
metadata.id = `local-${idCounter++}-${file.name}`; metadata.id = `local-${idCounter++}-${file.name}`;
tracks.push(metadata); tracks.push(metadata);
window.monochromeUi?.renderLocalFiles( UIRenderer.instance.renderLocalFiles(
document.getElementById('library-local-container') document.getElementById('library-local-container')
); );
} }
@ -610,7 +607,7 @@ document.addEventListener('DOMContentLoaded', async () => {
tracks.sort((a, b) => (a.artist.name || '').localeCompare(b.artist.name || '')); tracks.sort((a, b) => (a.artist.name || '').localeCompare(b.artist.name || ''));
// Update only the local-files section without navigating to the library page. // Update only the local-files section without navigating to the library page.
window.monochromeUi?.renderLocalFiles(document.getElementById('library-local-container')); UIRenderer.instance.renderLocalFiles(document.getElementById('library-local-container'));
} finally { } finally {
window.localFilesScanInProgress = false; window.localFilesScanInProgress = false;
} }
@ -643,7 +640,7 @@ document.addEventListener('DOMContentLoaded', async () => {
window.localFilesCache = [...existing, metadata].sort((a, b) => window.localFilesCache = [...existing, metadata].sort((a, b) =>
(a.artist.name || '').localeCompare(b.artist.name || '') (a.artist.name || '').localeCompare(b.artist.name || '')
); );
window.monochromeUi?.renderLocalFiles(document.getElementById('library-local-container')); UIRenderer.instance.renderLocalFiles(document.getElementById('library-local-container'));
} catch { } catch {
// Fall back to a full rescan if metadata extraction fails. // Fall back to a full rescan if metadata extraction fails.
await scanLocalMediaFolder(true); await scanLocalMediaFolder(true);
@ -665,7 +662,7 @@ document.addEventListener('DOMContentLoaded', async () => {
window.monochromeScrobbler = scrobbler; window.monochromeScrobbler = scrobbler;
const lyricsManager = await LyricsManager.initialize(MusicAPI.instance); const lyricsManager = await LyricsManager.initialize(MusicAPI.instance);
ui.lyricsManager = lyricsManager; UIRenderer.instance.lyricsManager = lyricsManager;
// Check browser support for local files // Check browser support for local files
const selectLocalBtn = document.getElementById('select-local-folder-btn'); const selectLocalBtn = document.getElementById('select-local-folder-btn');
@ -698,11 +695,11 @@ document.addEventListener('DOMContentLoaded', async () => {
sidebarSettings.restoreState(); sidebarSettings.restoreState();
// Render pinned items // Render pinned items
await ui.renderPinnedItems(); await UIRenderer.instance.renderPinnedItems();
// Load settings module and initialize // Load settings module and initialize
const { initializeSettings } = await loadSettingsModule(); const { initializeSettings } = await loadSettingsModule();
await initializeSettings(scrobbler, Player.instance, MusicAPI.instance, ui); await initializeSettings(scrobbler, Player.instance, MusicAPI.instance, UIRenderer.instance);
// Track sidebar navigation clicks // Track sidebar navigation clicks
document.querySelectorAll('.sidebar-nav a').forEach((link) => { document.querySelectorAll('.sidebar-nav a').forEach((link) => {
@ -715,22 +712,22 @@ document.addEventListener('DOMContentLoaded', async () => {
}); });
}); });
initializePlayerEvents(Player.instance, audioPlayer, scrobbler, ui); initializePlayerEvents(Player.instance, audioPlayer, scrobbler, UIRenderer.instance);
initializeTrackInteractions( initializeTrackInteractions(
Player.instance, Player.instance,
MusicAPI.instance, MusicAPI.instance,
document.querySelector('.main-content'), document.querySelector('.main-content'),
document.getElementById('context-menu'), document.getElementById('context-menu'),
lyricsManager, lyricsManager,
ui, UIRenderer.instance,
scrobbler scrobbler
); );
initializeUIInteractions(Player.instance, MusicAPI.instance, ui); initializeUIInteractions(Player.instance, MusicAPI.instance, UIRenderer.instance);
initializeKeyboardShortcuts(Player.instance, audioPlayer); initializeKeyboardShortcuts(Player.instance, audioPlayer);
// Restore UI state for the current track (like button, theme) // Restore UI state for the current track (like button, theme)
if (Player.instance.currentTrack) { if (Player.instance.currentTrack) {
ui.setCurrentTrack(Player.instance.currentTrack); UIRenderer.instance.setCurrentTrack(Player.instance.currentTrack);
} }
document.querySelector('.now-playing-bar').addEventListener('click', async (e) => { document.querySelector('.now-playing-bar').addEventListener('click', async (e) => {
@ -775,11 +772,11 @@ document.addEventListener('DOMContentLoaded', async () => {
if (window.location.hash === '#fullscreen') { if (window.location.hash === '#fullscreen') {
window.history.back(); window.history.back();
} else { } else {
ui.closeFullscreenCover(); UIRenderer.instance.closeFullscreenCover();
} }
} else { } else {
const nextTrack = Player.instance.getNextTrack(); const nextTrack = Player.instance.getNextTrack();
ui.showFullscreenCover( UIRenderer.instance.showFullscreenCover(
Player.instance.currentTrack, Player.instance.currentTrack,
nextTrack, nextTrack,
lyricsManager, lyricsManager,
@ -805,7 +802,7 @@ document.addEventListener('DOMContentLoaded', async () => {
if (window.location.hash === '#fullscreen') { if (window.location.hash === '#fullscreen') {
window.history.back(); window.history.back();
} else { } else {
ui.closeFullscreenCover(); UIRenderer.instance.closeFullscreenCover();
} }
}); });
@ -824,7 +821,7 @@ document.addEventListener('DOMContentLoaded', async () => {
if (window.location.hash === '#fullscreen') { if (window.location.hash === '#fullscreen') {
window.history.back(); window.history.back();
} else { } else {
ui.closeFullscreenCover(); UIRenderer.instance.closeFullscreenCover();
} }
break; break;
case 'hide-ui': case 'hide-ui':
@ -847,11 +844,11 @@ document.addEventListener('DOMContentLoaded', async () => {
toggleBtn.title = 'Show UI'; toggleBtn.title = 'Show UI';
} }
} }
if (ui && typeof ui.setupUIToggleButton === 'function') { if (UIRenderer.instance && typeof UIRenderer.instance.setupUIToggleButton === 'function') {
if (ui.uiToggleCleanup) { if (UIRenderer.instance.uiToggleCleanup) {
ui.uiToggleCleanup(); UIRenderer.instance.uiToggleCleanup();
} }
ui.setupUIToggleButton(overlay); UIRenderer.instance.setupUIToggleButton(overlay);
} }
} }
break; break;
@ -870,7 +867,7 @@ document.addEventListener('DOMContentLoaded', async () => {
if (window.location.hash === '#fullscreen') { if (window.location.hash === '#fullscreen') {
window.history.back(); window.history.back();
} else { } else {
ui.closeFullscreenCover(); UIRenderer.instance.closeFullscreenCover();
} }
} }
}); });
@ -1085,7 +1082,7 @@ document.addEventListener('DOMContentLoaded', async () => {
MusicAPI.instance, MusicAPI.instance,
lyricsManager, lyricsManager,
'track', 'track',
ui UIRenderer.instance
); );
} }
}); });
@ -1096,7 +1093,7 @@ document.addEventListener('DOMContentLoaded', async () => {
if (!Player.instance.currentTrack) return; if (!Player.instance.currentTrack) return;
// Update UI with current track info for theme // Update UI with current track info for theme
ui.setCurrentTrack(Player.instance.currentTrack); UIRenderer.instance.setCurrentTrack(Player.instance.currentTrack);
// Update Media Session with new track // Update Media Session with new track
Player.instance.updateMediaSession(Player.instance.currentTrack); Player.instance.updateMediaSession(Player.instance.currentTrack);
@ -1115,7 +1112,7 @@ document.addEventListener('DOMContentLoaded', async () => {
const fullscreenOverlay = document.getElementById('fullscreen-cover-overlay'); const fullscreenOverlay = document.getElementById('fullscreen-cover-overlay');
if (fullscreenOverlay && getComputedStyle(fullscreenOverlay).display !== 'none') { if (fullscreenOverlay && getComputedStyle(fullscreenOverlay).display !== 'none') {
const nextTrack = Player.instance.getNextTrack(); const nextTrack = Player.instance.getNextTrack();
ui.showFullscreenCover( UIRenderer.instance.showFullscreenCover(
Player.instance.currentTrack, Player.instance.currentTrack,
nextTrack, nextTrack,
lyricsManager, lyricsManager,
@ -1131,7 +1128,7 @@ document.addEventListener('DOMContentLoaded', async () => {
getComputedStyle(fullscreenOverlay).display === 'none' getComputedStyle(fullscreenOverlay).display === 'none'
) { ) {
const nextTrack = Player.instance.getNextTrack(); const nextTrack = Player.instance.getNextTrack();
ui.showFullscreenCover( UIRenderer.instance.showFullscreenCover(
Player.instance.currentTrack, Player.instance.currentTrack,
nextTrack, nextTrack,
lyricsManager, lyricsManager,
@ -1434,7 +1431,7 @@ document.addEventListener('DOMContentLoaded', async () => {
const folder = await db.createFolder(name, cover); const folder = await db.createFolder(name, cover);
trackCreateFolder(folder); trackCreateFolder(folder);
await syncManager.syncUserFolder(folder, 'create'); await syncManager.syncUserFolder(folder, 'create');
ui.renderLibraryPage(); UIRenderer.instance.renderLibraryPage();
document.getElementById('folder-modal').classList.remove('active'); document.getElementById('folder-modal').classList.remove('active');
trackCloseModal('Create Folder'); trackCloseModal('Create Folder');
} else { } else {
@ -1496,10 +1493,10 @@ document.addEventListener('DOMContentLoaded', async () => {
await handlePublicStatus(playlist); await handlePublicStatus(playlist);
await db.performTransaction('user_playlists', 'readwrite', (store) => store.put(playlist)); await db.performTransaction('user_playlists', 'readwrite', (store) => store.put(playlist));
syncManager.syncUserPlaylist(playlist, 'update'); syncManager.syncUserPlaylist(playlist, 'update');
ui.renderLibraryPage(); UIRenderer.instance.renderLibraryPage();
// Also update current page if we are on it // Also update current page if we are on it
if (window.location.pathname === `/userplaylist/${editingId}`) { if (window.location.pathname === `/userplaylist/${editingId}`) {
ui.renderPlaylistPage(editingId, 'user'); UIRenderer.instance.renderPlaylistPage(editingId, 'user');
} }
modal.classList.remove('active'); modal.classList.remove('active');
delete modal.dataset.editingId; delete modal.dataset.editingId;
@ -2022,7 +2019,7 @@ document.addEventListener('DOMContentLoaded', async () => {
await db.performTransaction('user_playlists', 'readwrite', (store) => store.put(playlist)); await db.performTransaction('user_playlists', 'readwrite', (store) => store.put(playlist));
await syncManager.syncUserPlaylist(playlist, 'create'); await syncManager.syncUserPlaylist(playlist, 'create');
trackCreatePlaylist(playlist, importSource); trackCreatePlaylist(playlist, importSource);
ui.renderLibraryPage(); UIRenderer.instance.renderLibraryPage();
modal.classList.remove('active'); modal.classList.remove('active');
trackCloseModal('Create Playlist'); trackCloseModal('Create Playlist');
}); });
@ -2100,7 +2097,7 @@ document.addEventListener('DOMContentLoaded', async () => {
if (confirm('Are you sure you want to delete this playlist?')) { if (confirm('Are you sure you want to delete this playlist?')) {
db.deletePlaylist(playlistId).then(() => { db.deletePlaylist(playlistId).then(() => {
syncManager.syncUserPlaylist({ id: playlistId }, 'delete'); syncManager.syncUserPlaylist({ id: playlistId }, 'delete');
ui.renderLibraryPage(); UIRenderer.instance.renderLibraryPage();
}); });
} }
} }
@ -2194,7 +2191,7 @@ document.addEventListener('DOMContentLoaded', async () => {
const updatedPlaylist = await db.removeTrackFromPlaylist(playlistId, trackId, trackType); const updatedPlaylist = await db.removeTrackFromPlaylist(playlistId, trackId, trackType);
syncManager.syncUserPlaylist(updatedPlaylist, 'update'); syncManager.syncUserPlaylist(updatedPlaylist, 'update');
const scrollTop = document.querySelector('.main-content').scrollTop; const scrollTop = document.querySelector('.main-content').scrollTop;
await ui.renderPlaylistPage(playlistId, 'user'); await UIRenderer.instance.renderPlaylistPage(playlistId, 'user');
document.querySelector('.main-content').scrollTop = scrollTop; document.querySelector('.main-content').scrollTop = scrollTop;
} }
}); });
@ -2545,7 +2542,7 @@ document.addEventListener('DOMContentLoaded', async () => {
const tracks = scanLocalMediaFolder(true); const tracks = scanLocalMediaFolder(true);
trackSelectLocalFolder(tracks?.length ?? 0); trackSelectLocalFolder(tracks?.length ?? 0);
ui.renderLibraryPage(); UIRenderer.instance.renderLibraryPage();
} catch (err) { } catch (err) {
if (err.name !== 'AbortError') { if (err.name !== 'AbortError') {
console.error('Error selecting folder:', err); console.error('Error selecting folder:', err);
@ -2565,7 +2562,7 @@ document.addEventListener('DOMContentLoaded', async () => {
const searchForm = document.getElementById('search-form'); const searchForm = document.getElementById('search-form');
const searchInput = document.getElementById('search-input'); const searchInput = document.getElementById('search-input');
ui.setupSearchClearButton(searchInput); UIRenderer.instance.setupSearchClearButton(searchInput);
const performSearch = (query) => { const performSearch = (query) => {
if (query) { if (query) {
@ -2618,16 +2615,16 @@ document.addEventListener('DOMContentLoaded', async () => {
searchInput.addEventListener('change', (e) => { searchInput.addEventListener('change', (e) => {
const query = e.target.value.trim(); const query = e.target.value.trim();
if (query) { if (query) {
ui.addToSearchHistory(query); UIRenderer.instance.addToSearchHistory(query);
} }
}); });
searchInput.addEventListener('focus', () => { searchInput.addEventListener('focus', () => {
ui.renderSearchHistory(); UIRenderer.instance.renderSearchHistory();
}); });
searchInput.addEventListener('click', () => { searchInput.addEventListener('click', () => {
ui.renderSearchHistory(); UIRenderer.instance.renderSearchHistory();
}); });
document.addEventListener('click', (e) => { document.addEventListener('click', (e) => {
@ -2643,7 +2640,7 @@ document.addEventListener('DOMContentLoaded', async () => {
if (!query) return; if (!query) return;
if (!handleExternalLink(query)) { if (!handleExternalLink(query)) {
ui.addToSearchHistory(query); UIRenderer.instance.addToSearchHistory(query);
performSearch(query); performSearch(query);
const historyEl = document.getElementById('search-history'); const historyEl = document.getElementById('search-history');
if (historyEl) historyEl.style.display = 'none'; if (historyEl) historyEl.style.display = 'none';
@ -2662,14 +2659,14 @@ document.addEventListener('DOMContentLoaded', async () => {
document.querySelector('.now-playing-bar .play-pause-btn').innerHTML = SVG_PLAY(20); document.querySelector('.now-playing-bar .play-pause-btn').innerHTML = SVG_PLAY(20);
const router = createRouter(ui); const router = createRouter(UIRenderer.instance);
const handleRouteChange = async (event) => { const handleRouteChange = async (event) => {
const overlay = document.getElementById('fullscreen-cover-overlay'); const overlay = document.getElementById('fullscreen-cover-overlay');
const isFullscreenOpen = overlay && getComputedStyle(overlay).display === 'flex'; const isFullscreenOpen = overlay && getComputedStyle(overlay).display === 'flex';
if (isFullscreenOpen && window.location.hash !== '#fullscreen') { if (isFullscreenOpen && window.location.hash !== '#fullscreen') {
ui.closeFullscreenCover(); UIRenderer.instance.closeFullscreenCover();
} }
if (event && event.state && event.state.exitTrap) { if (event && event.state && event.state.exitTrap) {
@ -2773,14 +2770,14 @@ document.addEventListener('DOMContentLoaded', async () => {
window.addEventListener('library-changed', () => { window.addEventListener('library-changed', () => {
const path = window.location.pathname; const path = window.location.pathname;
if (path === '/library') { if (path === '/library') {
ui.renderLibraryPage(); UIRenderer.instance.renderLibraryPage();
} else if (path === '/' || path === '/home') { } else if (path === '/' || path === '/home') {
ui.renderHomePage(); UIRenderer.instance.renderHomePage();
} else if (path.startsWith('/userplaylist/')) { } else if (path.startsWith('/userplaylist/')) {
const playlistId = path.split('/')[2]; const playlistId = path.split('/')[2];
const content = document.querySelector('.main-content'); const content = document.querySelector('.main-content');
const scroll = content ? content.scrollTop : 0; const scroll = content ? content.scrollTop : 0;
ui.renderPlaylistPage(playlistId, 'user').then(() => { UIRenderer.instance.renderPlaylistPage(playlistId, 'user').then(() => {
if (content) content.scrollTop = scroll; if (content) content.scrollTop = scroll;
}); });
} }
@ -2788,7 +2785,7 @@ document.addEventListener('DOMContentLoaded', async () => {
window.addEventListener('history-changed', () => { window.addEventListener('history-changed', () => {
const path = window.location.pathname; const path = window.location.pathname;
if (path === '/recent') { if (path === '/recent') {
ui.renderRecentPage(); UIRenderer.instance.renderRecentPage();
} }
}); });

View file

@ -45,6 +45,7 @@ import {
SVG_RADIO, SVG_RADIO,
} from './icons.js'; } from './icons.js';
import { Player } from './player.js'; import { Player } from './player.js';
import { UIRenderer } from './ui.js';
const ICON_SIZE = 16; const ICON_SIZE = 16;
@ -828,7 +829,7 @@ class CommandPalette {
async searchMusic(query) { async searchMusic(query) {
if (!query || query.length < 2) return; if (!query || query.length < 2) return;
const api = window.monochromeUi?.api; const api = UIRenderer.instance.api;
if (!api) return; if (!api) return;
this.cancelMusicSearch(); this.cancelMusicSearch();
@ -1174,15 +1175,15 @@ class CommandPalette {
const overlay = document.getElementById('fullscreen-cover-overlay'); const overlay = document.getElementById('fullscreen-cover-overlay');
if (overlay && getComputedStyle(overlay).display !== 'none') { if (overlay && getComputedStyle(overlay).display !== 'none') {
window.monochromeUi?.closeFullscreenCover(); UIRenderer.instance.closeFullscreenCover();
} }
} }
async setVisualizerPreset(preset) { async setVisualizerPreset(preset) {
const { visualizerSettings } = await import('./storage.js'); const { visualizerSettings } = await import('./storage.js');
visualizerSettings.setPreset(preset); visualizerSettings.setPreset(preset);
if (window.monochromeUi?.visualizer) { if (UIRenderer.instance.visualizer) {
window.monochromeUi.visualizer.setPreset(preset); UIRenderer.instance.visualizer.setPreset(preset);
} }
this.notify(`Visualizer preset: ${preset}`); this.notify(`Visualizer preset: ${preset}`);
} }
@ -1214,7 +1215,7 @@ class CommandPalette {
async likeAllInQueue() { async likeAllInQueue() {
const player = Player.instance; const player = Player.instance;
const ui = window.monochromeUi; const ui = UIRenderer.instance;
if (!player || !ui) return; if (!player || !ui) return;
const queue = player.getCurrentQueue(); const queue = player.getCurrentQueue();
@ -1240,7 +1241,7 @@ class CommandPalette {
async downloadQueue() { async downloadQueue() {
const player = Player.instance; const player = Player.instance;
const ui = window.monochromeUi; const ui = UIRenderer.instance;
if (!player || !ui) return; if (!player || !ui) return;
const queue = player.getCurrentQueue(); const queue = player.getCurrentQueue();
@ -1269,7 +1270,7 @@ class CommandPalette {
} }
async clearCache() { async clearCache() {
const api = window.monochromeUi?.api; const api = UIRenderer.instance.api;
if (api) { if (api) {
await api.clearCache(); await api.clearCache();
this.notify('Cache cleared'); this.notify('Cache cleared');

View file

@ -23,6 +23,8 @@ import { db } from './db.js';
import('./dash-media-player.js'); import('./dash-media-player.js');
import { SVG_CLOCK } from './icons.js'; import { SVG_CLOCK } from './icons.js';
import { UIRenderer } from './ui.js';
export class Player { export class Player {
static #instance = null; static #instance = null;
@ -811,12 +813,12 @@ export class Player {
const played = await this.safePlay(activeElement); const played = await this.safePlay(activeElement);
if (!played) return; if (!played) return;
} else if (track.type === 'video') { } else if (track.type === 'video') {
if (window.monochromeUi) { if (UIRenderer.instance) {
const isInFullscreen = const isInFullscreen =
document.getElementById('fullscreen-cover-overlay')?.style.display === 'flex'; document.getElementById('fullscreen-cover-overlay')?.style.display === 'flex';
if (!isInFullscreen) { if (!isInFullscreen) {
const lyricsManager = window.monochromeUi.lyricsManager; const lyricsManager = UIRenderer.instance.lyricsManager;
window.monochromeUi.showFullscreenCover( UIRenderer.instance.showFullscreenCover(
track, track,
this.getNextTrack(), this.getNextTrack(),
lyricsManager, lyricsManager,
@ -1392,8 +1394,8 @@ export class Player {
this.originalQueueBeforeShuffle = []; this.originalQueueBeforeShuffle = [];
this.currentQueueIndex = -1; this.currentQueueIndex = -1;
this.saveQueueState(); this.saveQueueState();
if (window.monochromeUi) { if (UIRenderer.instance) {
window.monochromeUi.setCurrentTrack(null); UIRenderer.instance.setCurrentTrack(null);
} }
if (window.renderQueueFunction) { if (window.renderQueueFunction) {
window.renderQueueFunction(); window.renderQueueFunction();

View file

@ -117,6 +117,16 @@ function sortTracks(tracks, sortType) {
} }
export class UIRenderer { export class UIRenderer {
static #instance = null;
static get instance() {
if (!UIRenderer.#instance) {
throw new Error('UIRenderer is not initialized. Call UIRenderer.initialize(api, player) first.');
}
return UIRenderer.#instance;
}
/** @private */
constructor(api, player) { constructor(api, player) {
this.api = api; this.api = api;
this.player = player; this.player = player;
@ -144,6 +154,13 @@ export class UIRenderer {
}); });
} }
static async initialize(api, player) {
if (UIRenderer.#instance) {
throw new Error('UIRenderer is already initialized');
}
return (UIRenderer.#instance = new UIRenderer(api, player));
}
// Helper for Heart Icon // Helper for Heart Icon
createHeartIcon(filled = false) { createHeartIcon(filled = false) {
if (filled) { if (filled) {