refactor: extract UI/overlay states into uiStore

- Create uiStore.ts for overlay and modal visibility states
- Centralize queue panel, fullscreen, focus mode, cast picker states
- Add playlist modal state management with mode and track IDs
- Implement handleEscapeKey for prioritized overlay closing
- Update +page.svelte to use store subscription and functions
This commit is contained in:
vicrodh committed 2026-01-10 04:28:34 -06:00
1 parent 0e8371d900
commit 6edfb5db06
2 files changed
+256 -29

No files matched your search

+199
View File
@@ -0,0 +1,199 @@
/**
* UI State Store
*
* Manages overlay and modal visibility states across the app.
*/
// Overlay states
let isQueueOpen = false;
let isFullScreenOpen = false;
let isFocusModeOpen = false;
let isCastPickerOpen = false;
// Playlist modal states
let isPlaylistModalOpen = false;
let playlistModalMode: 'create' | 'edit' | 'addTrack' = 'create';
let playlistModalTrackIds: number[] = [];
// Listeners
const listeners = new Set<() => void>();
function notifyListeners(): void {
for (const listener of listeners) {
listener();
}
}
/**
* Subscribe to UI state changes
*/
export function subscribe(listener: () => void): () => void {
listeners.add(listener);
listener(); // Immediately notify with current state
return () => listeners.delete(listener);
}
// ============ Queue Panel ============
export function getQueueOpen(): boolean {
return isQueueOpen;
}
export function openQueue(): void {
isQueueOpen = true;
notifyListeners();
}
export function closeQueue(): void {
isQueueOpen = false;
notifyListeners();
}
export function toggleQueue(): void {
isQueueOpen = !isQueueOpen;
notifyListeners();
}
// ============ Full Screen Now Playing ============
export function getFullScreenOpen(): boolean {
return isFullScreenOpen;
}
export function openFullScreen(): void {
isFullScreenOpen = true;
notifyListeners();
}
export function closeFullScreen(): void {
isFullScreenOpen = false;
notifyListeners();
}
export function toggleFullScreen(): void {
isFullScreenOpen = !isFullScreenOpen;
notifyListeners();
}
// ============ Focus Mode ============
export function getFocusModeOpen(): boolean {
return isFocusModeOpen;
}
export function openFocusMode(): void {
isFocusModeOpen = true;
notifyListeners();
}
export function closeFocusMode(): void {
isFocusModeOpen = false;
notifyListeners();
}
export function toggleFocusMode(): void {
isFocusModeOpen = !isFocusModeOpen;
notifyListeners();
}
// ============ Cast Picker ============
export function getCastPickerOpen(): boolean {
return isCastPickerOpen;
}
export function openCastPicker(): void {
isCastPickerOpen = true;
notifyListeners();
}
export function closeCastPicker(): void {
isCastPickerOpen = false;
notifyListeners();
}
export function toggleCastPicker(): void {
isCastPickerOpen = !isCastPickerOpen;
notifyListeners();
}
// ============ Playlist Modal ============
export function getPlaylistModalOpen(): boolean {
return isPlaylistModalOpen;
}
export function getPlaylistModalMode(): 'create' | 'edit' | 'addTrack' {
return playlistModalMode;
}
export function getPlaylistModalTrackIds(): number[] {
return playlistModalTrackIds;
}
export function openPlaylistModal(mode: 'create' | 'edit' | 'addTrack', trackIds: number[] = []): void {
isPlaylistModalOpen = true;
playlistModalMode = mode;
playlistModalTrackIds = trackIds;
notifyListeners();
}
export function closePlaylistModal(): void {
isPlaylistModalOpen = false;
playlistModalTrackIds = [];
notifyListeners();
}
// ============ Escape Key Handler ============
/**
* Handle escape key - closes overlays in priority order
* Returns true if an overlay was closed
*/
export function handleEscapeKey(): boolean {
if (isFocusModeOpen) {
closeFocusMode();
return true;
}
if (isFullScreenOpen) {
closeFullScreen();
return true;
}
if (isQueueOpen) {
closeQueue();
return true;
}
if (isCastPickerOpen) {
closeCastPicker();
return true;
}
if (isPlaylistModalOpen) {
closePlaylistModal();
return true;
}
return false;
}
// ============ Bulk State Getter ============
export interface UIState {
isQueueOpen: boolean;
isFullScreenOpen: boolean;
isFocusModeOpen: boolean;
isCastPickerOpen: boolean;
isPlaylistModalOpen: boolean;
playlistModalMode: 'create' | 'edit' | 'addTrack';
playlistModalTrackIds: number[];
}
export function getUIState(): UIState {
return {
isQueueOpen,
isFullScreenOpen,
isFocusModeOpen,
isCastPickerOpen,
isPlaylistModalOpen,
playlistModalMode,
playlistModalTrackIds
};
}
+57 -29
View File
@@ -23,6 +23,27 @@
type Toast as ToastData
} from '$lib/stores/toastStore';
// UI state management
import {
subscribe as subscribeUI,
openQueue,
closeQueue,
toggleQueue,
openFullScreen,
closeFullScreen,
toggleFullScreen,
openFocusMode,
closeFocusMode,
toggleFocusMode,
openCastPicker,
closeCastPicker,
openPlaylistModal,
closePlaylistModal,
handleEscapeKey as handleUIEscape,
getUIState,
type UIState
} from '$lib/stores/uiStore';
// Components
import Sidebar from '$lib/components/Sidebar.svelte';
import NowPlayingBar from '$lib/components/NowPlayingBar.svelte';
@@ -211,13 +232,13 @@
let selectedArtist = $state<ArtistDetail | null>(null);
let selectedPlaylistId = $state<number | null>(null);
// Overlay States
// Overlay States (from uiStore subscription)
let isQueueOpen = $state(false);
let isFullScreenOpen = $state(false);
let isFocusModeOpen = $state(false);
let isCastPickerOpen = $state(false);
// Playlist Modal State
// Playlist Modal State (from uiStore subscription)
let isPlaylistModalOpen = $state(false);
let playlistModalMode = $state<'create' | 'edit' | 'addTrack'>('create');
let playlistModalTrackIds = $state<number[]>([]);
@@ -1301,16 +1322,12 @@
// Playlist Modal Functions
function openCreatePlaylist() {
userPlaylists = sidebarRef?.getPlaylists() ?? [];
playlistModalMode = 'create';
playlistModalTrackIds = [];
isPlaylistModalOpen = true;
openPlaylistModal('create', []);
}
function openAddToPlaylist(trackIds: number[]) {
userPlaylists = sidebarRef?.getPlaylists() ?? [];
playlistModalMode = 'addTrack';
playlistModalTrackIds = trackIds;
isPlaylistModalOpen = true;
openPlaylistModal('addTrack', trackIds);
}
function handlePlaylistCreated() {
@@ -1383,16 +1400,14 @@
break;
case 'f':
if (!e.ctrlKey && !e.metaKey) {
isFocusModeOpen = !isFocusModeOpen;
toggleFocusMode();
}
break;
case 'q':
isQueueOpen = !isQueueOpen;
toggleQueue();
break;
case 'Escape':
if (isFocusModeOpen) isFocusModeOpen = false;
else if (isFullScreenOpen) isFullScreenOpen = false;
else if (isQueueOpen) isQueueOpen = false;
handleUIEscape();
break;
}
}
@@ -1503,6 +1518,18 @@
toast = newToast;
});
// Subscribe to UI state changes
const unsubscribeUI = subscribeUI(() => {
const uiState = getUIState();
isQueueOpen = uiState.isQueueOpen;
isFullScreenOpen = uiState.isFullScreenOpen;
isFocusModeOpen = uiState.isFocusModeOpen;
isCastPickerOpen = uiState.isCastPickerOpen;
isPlaylistModalOpen = uiState.isPlaylistModalOpen;
playlistModalMode = uiState.playlistModalMode;
playlistModalTrackIds = uiState.playlistModalTrackIds;
});
// Restore Last.fm session on app startup
(async () => {
try {
@@ -1534,6 +1561,7 @@
stopDownloadEventListeners();
unsubscribeDownloads();
unsubscribeToast();
unsubscribeUI();
};
});
@@ -1710,22 +1738,22 @@
onToggleRepeat={toggleRepeat}
{isFavorite}
onToggleFavorite={toggleFavorite}
onOpenQueue={() => (isQueueOpen = true)}
onOpenFullScreen={() => (isFullScreenOpen = true)}
onCast={() => (isCastPickerOpen = true)}
onOpenQueue={openQueue}
onOpenFullScreen={openFullScreen}
onCast={openCastPicker}
/>
{:else}
<NowPlayingBar
onOpenQueue={() => (isQueueOpen = true)}
onOpenFullScreen={() => (isFullScreenOpen = true)}
onCast={() => (isCastPickerOpen = true)}
onOpenQueue={openQueue}
onOpenFullScreen={openFullScreen}
onCast={openCastPicker}
/>
{/if}
<!-- Queue Panel -->
<QueuePanel
isOpen={isQueueOpen}
onClose={() => (isQueueOpen = false)}
onClose={closeQueue}
currentTrack={currentQueueTrack ?? undefined}
upcomingTracks={queue}
onPlayTrack={handleQueueTrackPlay}
@@ -1737,7 +1765,7 @@
{#if currentTrack}
<FullScreenNowPlaying
isOpen={isFullScreenOpen}
onClose={() => (isFullScreenOpen = false)}
onClose={closeFullScreen}
artwork={currentTrack.artwork}
trackTitle={currentTrack.title}
artist={currentTrack.artist}
@@ -1762,14 +1790,14 @@
{isFavorite}
onToggleFavorite={toggleFavorite}
onOpenQueue={() => {
isFullScreenOpen = false;
isQueueOpen = true;
closeFullScreen();
openQueue();
}}
onOpenFocusMode={() => {
isFullScreenOpen = false;
isFocusModeOpen = true;
closeFullScreen();
openFocusMode();
}}
onCast={() => (isCastPickerOpen = true)}
onCast={openCastPicker}
/>
{/if}
@@ -1777,7 +1805,7 @@
{#if currentTrack}
<FocusMode
isOpen={isFocusModeOpen}
onClose={() => (isFocusModeOpen = false)}
onClose={closeFocusMode}
artwork={currentTrack.artwork}
trackTitle={currentTrack.title}
artist={currentTrack.artist}
@@ -1804,14 +1832,14 @@
mode={playlistModalMode}
trackIds={playlistModalTrackIds}
{userPlaylists}
onClose={() => (isPlaylistModalOpen = false)}
onClose={closePlaylistModal}
onSuccess={handlePlaylistCreated}
/>
<!-- Cast Picker -->
<CastPicker
isOpen={isCastPickerOpen}
onClose={() => (isCastPickerOpen = false)}
onClose={closeCastPicker}
onConnect={(deviceId) => {
showToast(`Connected to device`, 'success');
}}