refactor(ui): convert queue panel from overlay to inline sidebar

Queue now pushes main content instead of overlaying it, fixing the
search header z-index conflict. Lyrics sidebar height fixed to 100%
to fill parent container correctly. Queue and lyrics are now mutually
exclusive - opening one closes the other.
This commit is contained in:
vicrodh committed 2026-02-19 08:55:09 -06:00
1 parent 5b76530a4d
commit 13d9ec3724
3 files changed
+55 -114

No files matched your search

+24 -85
View File
@@ -20,8 +20,6 @@
}
interface Props {
isOpen: boolean;
onClose: () => void;
currentTrack?: QueueTrack;
upcomingTracks: QueueTrack[];
queueTotalTracks?: number; // Total tracks in the entire queue
@@ -42,8 +40,6 @@
}
let {
isOpen,
onClose,
currentTrack,
upcomingTracks,
queueTotalTracks = 0,
@@ -113,17 +109,6 @@
}
}
// Reset state when panel closes
$effect(() => {
if (!isOpen) {
activeTab = 'queue';
searchOpen = false;
searchQuery = '';
displayCount = DISPLAY_LIMIT;
historyDisplayCount = DISPLAY_LIMIT;
}
});
// Check favorite status when current track changes
$effect(() => {
if (currentTrack?.trackId) {
@@ -231,10 +216,6 @@
onPlayHistoryTrack?.(track.id);
}
function handleClose() {
onClose();
}
// Context menu handlers for queue tracks
function toggleTrackMenu(e: MouseEvent, index: number) {
e.stopPropagation();
@@ -280,35 +261,28 @@
}
</script>
{#if isOpen}
<!-- Backdrop -->
<div class="backdrop" onclick={handleClose} role="presentation"></div>
<!-- Queue Panel -->
<div class="queue-panel">
<!-- Header with Tabs -->
<div class="header">
<div class="tabs">
<button
class="tab"
class:active={activeTab === 'queue'}
onclick={() => activeTab = 'queue'}
>
{$t('player.queue')}
</button>
<span class="tab-separator">|</span>
<button
class="tab"
class:active={activeTab === 'history'}
onclick={() => activeTab = 'history'}
>
{$t('player.history')}
</button>
</div>
<button class="close-btn" onclick={handleClose}>
<X size={20} />
<!-- Queue Panel -->
<aside class="queue-panel" onclick={handlePanelClick}>
<!-- Header with Tabs -->
<div class="header">
<div class="tabs">
<button
class="tab"
class:active={activeTab === 'queue'}
onclick={() => activeTab = 'queue'}
>
{$t('player.queue')}
</button>
<span class="tab-separator">|</span>
<button
class="tab"
class:active={activeTab === 'history'}
onclick={() => activeTab = 'history'}
>
{$t('player.history')}
</button>
</div>
</div>
<!-- Content -->
<div class="content">
@@ -551,44 +525,26 @@
</div>
</div>
{/if}
</div>
{/if}
</aside>
<style>
.backdrop {
position: fixed;
inset: 0;
background-color: rgba(0, 0, 0, 0.4);
z-index: 40;
}
.queue-panel {
position: fixed;
top: 32px;
right: 0;
bottom: 104px;
width: 340px;
z-index: 50;
min-width: 340px;
height: 100%;
display: flex;
flex-direction: column;
animation: slideIn 200ms ease-out;
background-color: var(--bg-secondary);
border-left: 1px solid var(--bg-tertiary);
box-shadow: -4px 0 24px rgba(0, 0, 0, 0.3);
}
@keyframes slideIn {
from { transform: translateX(100%); }
to { transform: translateX(0); }
}
/* Header */
.header {
padding: 12px 16px;
border-bottom: 1px solid var(--bg-tertiary);
background: var(--bg-primary);
display: flex;
align-items: center;
justify-content: space-between;
flex-shrink: 0;
}
@@ -622,23 +578,6 @@
font-size: 14px;
}
.close-btn {
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
background: none;
border: none;
color: var(--text-muted);
cursor: pointer;
transition: color 150ms ease;
}
.close-btn:hover {
color: var(--text-primary);
}
/* Content */
.content {
flex: 1;
@@ -68,7 +68,7 @@
.lyrics-sidebar {
width: 340px;
min-width: 340px;
height: calc(100vh - 104px);
height: 100%;
display: flex;
flex-direction: column;
border-left: 1px solid var(--bg-tertiary);
+30 -28
View File
@@ -2803,9 +2803,10 @@
// Subscribe to UI state changes
const unsubscribeUI = subscribeUI(() => {
const uiState = getUIState();
// Close network sidebar when queue opens
// Close network sidebar and lyrics when queue opens
if (uiState.isQueueOpen && !isQueueOpen) {
closeContentSidebar('network');
hideLyricsSidebar();
}
isQueueOpen = uiState.isQueueOpen;
isFullScreenOpen = uiState.isFullScreenOpen;
@@ -2908,11 +2909,12 @@
lyricsIsSynced = state.isSynced;
lyricsActiveIndex = state.activeIndex;
lyricsActiveProgress = state.activeProgress;
lyricsSidebarVisible = state.sidebarVisible;
// Close network sidebar when lyrics opens
if (state.sidebarVisible) {
// Close network sidebar and queue when lyrics opens
if (state.sidebarVisible && !lyricsSidebarVisible) {
closeContentSidebar('network');
closeQueue();
}
lyricsSidebarVisible = state.sidebarVisible;
});
// Subscribe to content sidebar for mutual exclusion (network closes lyrics/queue)
@@ -3811,7 +3813,7 @@
{/if}
<!-- Lyrics Sidebar -->
{#if lyricsSidebarVisible}
{#if lyricsSidebarVisible && !isQueueOpen}
<LyricsSidebar
title={currentTrack?.title}
artist={currentTrack?.artist}
@@ -3823,6 +3825,29 @@
error={lyricsStatus === 'error' ? lyricsError : (lyricsStatus === 'not_found' ? $t('player.noLyrics') : null)}
/>
{/if}
<!-- Queue Sidebar -->
{#if isQueueOpen}
<QueuePanel
currentTrack={currentQueueTrack ?? undefined}
upcomingTracks={queue}
{queueTotalTracks}
{queueRemainingTracks}
{historyTracks}
isRadioMode={getCurrentContext()?.type === 'radio'}
onPlayTrack={handleQueueTrackPlay}
onPlayHistoryTrack={handlePlayHistoryTrack}
onClearQueue={handleClearQueue}
onSaveAsPlaylist={handleSaveQueueAsPlaylist}
onReorderTrack={handleQueueReorder}
onToggleInfinitePlay={handleToggleInfinitePlay}
{infinitePlayEnabled}
{isPlaying}
onRemoveFromQueue={handleRemoveFromQueue}
onAddToPlaylist={handleQueueTrackAddToPlaylist}
onShowTrackInfo={handleQueueTrackInfo}
/>
{/if}
</div>
</div><!-- end app-body -->
@@ -3900,29 +3925,6 @@
/>
{/if}
<!-- Queue Panel -->
<QueuePanel
isOpen={isQueueOpen}
onClose={closeQueue}
currentTrack={currentQueueTrack ?? undefined}
upcomingTracks={queue}
{queueTotalTracks}
{queueRemainingTracks}
{historyTracks}
isRadioMode={getCurrentContext()?.type === 'radio'}
onPlayTrack={handleQueueTrackPlay}
onPlayHistoryTrack={handlePlayHistoryTrack}
onClearQueue={handleClearQueue}
onSaveAsPlaylist={handleSaveQueueAsPlaylist}
onReorderTrack={handleQueueReorder}
onToggleInfinitePlay={handleToggleInfinitePlay}
{infinitePlayEnabled}
{isPlaying}
onRemoveFromQueue={handleRemoveFromQueue}
onAddToPlaylist={handleQueueTrackAddToPlaylist}
onShowTrackInfo={handleQueueTrackInfo}
/>
<!-- Immersive Player (replaces ExpandedPlayer + FocusMode) -->
{#if currentTrack}
<ImmersivePlayer