queue panel shares layer with main content, no longer an overlay

This commit is contained in:
2026-02-15 20:29:22 -05:00
parent 86204e1b7a
commit 3a696c57ac
4 changed files with 167 additions and 58 deletions
+8 -1
View File
@@ -112,8 +112,15 @@ body div.sidebar {
}
}
.main-panel {
.content-area {
grid-area: main-panel;
display: flex;
overflow: hidden;
}
.main-panel {
flex: 1;
min-width: 0;
padding: 0.25em;
background-color: #212529;
overflow: hidden;
+6 -4
View File
@@ -22,9 +22,12 @@
<div class="sidebar">
<app-sidebar></app-sidebar>
</div>
<main class="main-panel" id="main-content">
<track-list></track-list>
</main>
<div class="content-area">
<main class="main-panel" id="main-content">
<track-list></track-list>
</main>
<queue-panel id="queue-panel"></queue-panel>
</div>
<footer class="bottom-bar">
<now-playing></now-playing>
<audio-player></audio-player>
@@ -32,7 +35,6 @@
<wa-icon name="list"></wa-icon>
</button>
</footer>
<queue-panel id="queue-panel"></queue-panel>
</body>
</html>
@@ -1,8 +1,12 @@
import { LitElement, html, css } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { LitElement, html, css, unsafeCSS } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import '@awesome.me/webawesome/dist/components/icon/icon.js';
import { QueueController } from '@store/controllers/queue-controller';
const MIN_WIDTH = 200;
const MAX_WIDTH = 500;
const DEFAULT_WIDTH = 320;
@customElement('queue-panel')
export class QueuePanel extends LitElement {
private queue = new QueueController(this);
@@ -10,26 +14,50 @@ export class QueuePanel extends LitElement {
@property({ type: Boolean, reflect: true })
open = false;
@state()
private isDragging = false;
private panelWidth = DEFAULT_WIDTH;
static override styles = css`
:host {
display: block;
position: fixed;
top: 4em; /* below header */
right: 0;
bottom: 4em; /* above footer */
width: 320px;
background-color: #1a1a2e;
border-left: 1px solid #333;
transform: translateX(100%);
transition: transform 0.25s ease-in-out;
z-index: 100;
flex-shrink: 0;
width: 0;
overflow: hidden;
background-color: #1a1a2e;
transition: width 0.25s ease-in-out;
display: flex;
flex-direction: column;
flex-direction: row;
}
:host([open]) {
transform: translateX(0);
width: var(--queue-width, ${unsafeCSS(DEFAULT_WIDTH)}px);
border-left: 1px solid #333;
}
.resize-handle {
position: absolute;
top: 0;
left: 0;
width: 4px;
height: 100%;
cursor: col-resize;
background-color: transparent;
transition: background-color 0.15s ease;
z-index: 10;
}
.resize-handle:hover,
.resize-handle.dragging {
background-color: #6c757d;
}
.panel-content {
position: relative;
display: flex;
flex-direction: column;
min-width: ${unsafeCSS(MIN_WIDTH)}px;
flex: 1;
}
.header {
@@ -160,9 +188,27 @@ export class QueuePanel extends LitElement {
}
`;
override connectedCallback() {
super.connectedCallback();
this.style.setProperty('--queue-width', `${this.panelWidth}px`);
document.addEventListener('mousemove', this.handleMouseMove);
document.addEventListener('mouseup', this.handleMouseUp);
}
override disconnectedCallback() {
super.disconnectedCallback();
document.removeEventListener('mousemove', this.handleMouseMove);
document.removeEventListener('mouseup', this.handleMouseUp);
}
private handleClose() {
this.open = false;
this.dispatchEvent(new CustomEvent('queue-panel-close', { bubbles: true, composed: true }));
this.dispatchEvent(
new CustomEvent('queue-panel-close', {
bubbles: true,
composed: true,
}),
);
}
private handleRemoveTrack(e: Event, position: number) {
@@ -174,7 +220,9 @@ export class QueuePanel extends LitElement {
this.queue.playAtIndex(index);
}
private getDisplayTitle(track: { title: string; filePath: string }): string {
private getDisplayTitle(
track: { title: string; filePath: string },
): string {
if (track.title) return track.title;
// Fall back to filename without extension.
@@ -184,49 +232,97 @@ export class QueuePanel extends LitElement {
return filename.replace(/\.[^.]+$/, '');
}
private handleMouseDown = (e: MouseEvent) => {
e.preventDefault();
this.isDragging = true;
// Disable transition during drag for instant feedback.
this.style.transition = 'none';
};
private handleMouseMove = (e: MouseEvent) => {
if (!this.isDragging) return;
const rect = this.getBoundingClientRect();
const newWidth = rect.right - e.clientX;
const clampedWidth = Math.min(
Math.max(newWidth, MIN_WIDTH),
MAX_WIDTH,
);
this.panelWidth = clampedWidth;
this.style.setProperty('--queue-width', `${clampedWidth}px`);
};
private handleMouseUp = () => {
if (!this.isDragging) return;
this.isDragging = false;
// Re-enable transition after drag ends.
this.style.removeProperty('transition');
};
override render() {
const tracks = this.queue.tracks;
const currentIndex = this.queue.currentIndex;
return html`
<div class="header">
<h3>Queue</h3>
<button class="close-button" @click=${this.handleClose}>
<wa-icon name="xmark"></wa-icon>
</button>
</div>
<div class="panel-content">
<div
class="resize-handle ${this.isDragging ? 'dragging' : ''}"
@mousedown=${this.handleMouseDown}
></div>
<div class="header">
<h3>Queue</h3>
<button class="close-button" @click=${this.handleClose}>
<wa-icon name="xmark"></wa-icon>
</button>
</div>
${tracks.length === 0
? html`
<div class="empty-state">
<wa-icon name="list"></wa-icon>
<p>Queue is empty</p>
<p style="font-size: 12px;">Click a track to start playing</p>
</div>
`
: html`
<ul class="track-list">
${tracks.map(
(track, index) => html`
<li class="track-item ${index === currentIndex ? 'active' : ''}"
@click=${() => this.handleTrackClick(index)}>
<span class="track-position">${index + 1}</span>
<div class="track-details">
<span class="track-title">${this.getDisplayTitle(track)}</span>
<span class="track-artist">${track.artist || 'Unknown Artist'}</span>
</div>
<button
class="remove-button"
@click=${(e: Event) => this.handleRemoveTrack(e, index)}
title="Remove from queue"
${tracks.length === 0
? html`
<div class="empty-state">
<wa-icon name="list"></wa-icon>
<p>Queue is empty</p>
<p style="font-size: 12px;">
Click a track to start playing
</p>
</div>
`
: html`
<ul class="track-list">
${tracks.map(
(track, index) => html`
<li
class="track-item ${index === currentIndex
? 'active'
: ''}"
@click=${() => this.handleTrackClick(index)}
>
<wa-icon name="xmark"></wa-icon>
</button>
</li>
`
)}
</ul>
`}
<span class="track-position">${index + 1}</span>
<div class="track-details">
<span class="track-title">
${this.getDisplayTitle(track)}
</span>
<span class="track-artist">
${track.artist || 'Unknown Artist'}
</span>
</div>
<button
class="remove-button"
@click=${(e: Event) =>
this.handleRemoveTrack(e, index)}
title="Remove from queue"
>
<wa-icon name="xmark"></wa-icon>
</button>
</li>
`,
)}
</ul>
`}
</div>
`;
}
}
@@ -62,6 +62,10 @@ export class TrackList extends LitElement {
width: 100%;
}
.track-row > * {
min-width: 0;
}
.track-row:hover {
background-color: rgba(255, 255, 255, 0.05);
}