queue panel shares layer with main content, no longer an overlay
This commit is contained in:
+8
-1
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user