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:
1 parent
5b76530a4d
commit
13d9ec3724
3 files changed
+55
-114
No files matched your search
@@ -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
@@ -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
|
||||
|
||||
Reference in new issue
Block a user