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:
1 parent
0e8371d900
commit
6edfb5db06
2 files changed
+256
-29
No files matched your search
@@ -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
@@ -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');
|
||||
}}
|
||||
|
||||
Reference in new issue
Block a user