From 2004a59660932e71fec70db964f66d1e6547f9f5 Mon Sep 17 00:00:00 2001 From: vicrodh <103399283+vicrodh@users.noreply.github.com> Date: Sun, 1 Feb 2026 08:08:51 -0600 Subject: [PATCH] feat(immersive): unify tab navigation with view mode toggle Consolidates navigation into single bar with mode toggle: - Split mode tabs: Lyrics, Track Info, Suggestions, Queue - Focus mode tabs: Coverflow, Lyrics, Queue - Mode toggle button (lotus/split-view icons) - V key toggles view mode, 1-3 for focus tabs --- .../immersive/ImmersiveHeader.svelte | 129 +++++++++--------- .../immersive/ImmersivePlayer.svelte | 115 +++++++++------- 2 files changed, 129 insertions(+), 115 deletions(-) diff --git a/src/lib/components/immersive/ImmersiveHeader.svelte b/src/lib/components/immersive/ImmersiveHeader.svelte index a8224b685..8c50a8e13 100644 --- a/src/lib/components/immersive/ImmersiveHeader.svelte +++ b/src/lib/components/immersive/ImmersiveHeader.svelte @@ -1,16 +1,19 @@
- +
-
- {#each displayModes as mode (mode.id)} - - {/each} -
+
- - {#if showTabs} -
@@ -211,46 +224,38 @@ } .header-left { - flex: 1; - min-width: 100px; + flex: 0 0 auto; + min-width: 50px; } - .mode-switcher { - display: inline-flex; - align-items: center; - gap: 2px; - padding: 4px; - background: rgba(0, 0, 0, 0.5); - border: 1px solid rgba(255, 255, 255, 0.15); - border-radius: 10px; - } - - .mode-btn { + .mode-toggle { display: flex; align-items: center; justify-content: center; - width: 34px; - height: 34px; - background: none; - border: none; - border-radius: 8px; - color: rgba(255, 255, 255, 0.65); + width: 42px; + height: 42px; + padding: 8px; + background: rgba(0, 0, 0, 0.5); + border: 1px solid rgba(255, 255, 255, 0.15); + border-radius: 12px; cursor: pointer; transition: all 150ms ease; } - .mode-btn:hover { - color: rgba(255, 255, 255, 0.9); - background: rgba(255, 255, 255, 0.12); + .mode-toggle:hover { + background: rgba(0, 0, 0, 0.6); + border-color: rgba(255, 255, 255, 0.25); } - .mode-btn.active { - color: var(--text-primary, white); - background: rgba(255, 255, 255, 0.2); + .mode-icon { + width: 22px; + height: 22px; + filter: invert(1) opacity(0.85); + transition: filter 150ms ease; } - .tabs-placeholder { - flex: 1; + .mode-toggle:hover .mode-icon { + filter: invert(1) opacity(1); } .tabs { diff --git a/src/lib/components/immersive/ImmersivePlayer.svelte b/src/lib/components/immersive/ImmersivePlayer.svelte index b0b7220d9..cbf786045 100644 --- a/src/lib/components/immersive/ImmersivePlayer.svelte +++ b/src/lib/components/immersive/ImmersivePlayer.svelte @@ -3,7 +3,7 @@ import { getCurrentWindow } from '@tauri-apps/api/window'; import ImmersiveBackground from './ImmersiveBackground.svelte'; import ImmersiveArtwork from './ImmersiveArtwork.svelte'; - import ImmersiveHeader, { type ImmersiveTab, type DisplayMode } from './ImmersiveHeader.svelte'; + import ImmersiveHeader, { type ImmersiveTab, type FocusTab, type ViewMode } from './ImmersiveHeader.svelte'; import PlayerControlsCompact from './PlayerControlsCompact.svelte'; import LyricsPanel from './panels/LyricsPanel.svelte'; import TrackInfoPanel from './panels/TrackInfoPanel.svelte'; @@ -115,8 +115,9 @@ }: Props = $props(); // UI State - let displayMode: DisplayMode = $state('split'); + let viewMode: ViewMode = $state('split'); let activeTab: ImmersiveTab = $state('lyrics'); + let activeFocusTab: FocusTab = $state('coverflow'); let showUI = $state(true); let hideTimeout: ReturnType | null = null; let isFullscreen = $state(false); @@ -210,35 +211,38 @@ onSeek(Math.min(duration, currentTime + 5)); } break; - // Display mode shortcuts - case '1': - displayMode = 'coverflow'; + // View mode toggle + case 'v': + case 'V': + viewMode = viewMode === 'split' ? 'focus' : 'split'; break; - case '2': - displayMode = 'split'; - break; - case '3': - displayMode = 'lyrics-focus'; - break; - case '4': - displayMode = 'queue-focus'; - break; - // Tab shortcuts (only in split mode) + // Tab shortcuts (split mode) case 'l': case 'L': - if (displayMode === 'split') activeTab = 'lyrics'; + if (viewMode === 'split') activeTab = 'lyrics'; break; case 't': case 'T': - if (displayMode === 'split' && enableCredits) activeTab = 'trackInfo'; + if (viewMode === 'split' && enableCredits) activeTab = 'trackInfo'; break; case 's': case 'S': - if (displayMode === 'split' && enableSuggestions) activeTab = 'suggestions'; + if (viewMode === 'split' && enableSuggestions) activeTab = 'suggestions'; break; case 'q': case 'Q': - if (displayMode === 'split') activeTab = 'queue'; + if (viewMode === 'split') activeTab = 'queue'; + else if (viewMode === 'focus') activeFocusTab = 'queue-focus'; + break; + // Focus mode tabs + case '1': + if (viewMode === 'focus') activeFocusTab = 'coverflow'; + break; + case '2': + if (viewMode === 'focus') activeFocusTab = 'lyrics-focus'; + break; + case '3': + if (viewMode === 'focus') activeFocusTab = 'queue-focus'; break; } resetHideTimer(); @@ -280,10 +284,12 @@ viewMode = mode} onTabChange={(tab) => activeTab = tab} - onDisplayModeChange={(mode) => displayMode = mode} + onFocusTabChange={(tab) => activeFocusTab = tab} onClose={handleExitImmersive} visible={showUI} hasLyrics={true} @@ -296,39 +302,42 @@ onMinimize={minimizeWindow} /> - - {#if displayMode === 'coverflow'} - - - {:else if displayMode === 'lyrics-focus'} - - - {:else if displayMode === 'queue-focus'} - -
-
- onQueuePlayTrack?.(index)} - onClear={onQueueClear} - /> + + {#if viewMode === 'focus'} + + {#if activeFocusTab === 'coverflow'} + + + {:else if activeFocusTab === 'lyrics-focus'} + + + {:else if activeFocusTab === 'queue-focus'} + +
+
+ onQueuePlayTrack?.(index)} + onClear={onQueueClear} + /> +
-
+ {/if} {:else}