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;
|
grid-area: main-panel;
|
||||||
|
display: flex;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-panel {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
padding: 0.25em;
|
padding: 0.25em;
|
||||||
background-color: #212529;
|
background-color: #212529;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
+6
-4
@@ -22,9 +22,12 @@
|
|||||||
<div class="sidebar">
|
<div class="sidebar">
|
||||||
<app-sidebar></app-sidebar>
|
<app-sidebar></app-sidebar>
|
||||||
</div>
|
</div>
|
||||||
<main class="main-panel" id="main-content">
|
<div class="content-area">
|
||||||
<track-list></track-list>
|
<main class="main-panel" id="main-content">
|
||||||
</main>
|
<track-list></track-list>
|
||||||
|
</main>
|
||||||
|
<queue-panel id="queue-panel"></queue-panel>
|
||||||
|
</div>
|
||||||
<footer class="bottom-bar">
|
<footer class="bottom-bar">
|
||||||
<now-playing></now-playing>
|
<now-playing></now-playing>
|
||||||
<audio-player></audio-player>
|
<audio-player></audio-player>
|
||||||
@@ -32,7 +35,6 @@
|
|||||||
<wa-icon name="list"></wa-icon>
|
<wa-icon name="list"></wa-icon>
|
||||||
</button>
|
</button>
|
||||||
</footer>
|
</footer>
|
||||||
<queue-panel id="queue-panel"></queue-panel>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
import { LitElement, html, css } from 'lit';
|
import { LitElement, html, css, unsafeCSS } from 'lit';
|
||||||
import { customElement, property } from 'lit/decorators.js';
|
import { customElement, property, state } from 'lit/decorators.js';
|
||||||
import '@awesome.me/webawesome/dist/components/icon/icon.js';
|
import '@awesome.me/webawesome/dist/components/icon/icon.js';
|
||||||
import { QueueController } from '@store/controllers/queue-controller';
|
import { QueueController } from '@store/controllers/queue-controller';
|
||||||
|
|
||||||
|
const MIN_WIDTH = 200;
|
||||||
|
const MAX_WIDTH = 500;
|
||||||
|
const DEFAULT_WIDTH = 320;
|
||||||
|
|
||||||
@customElement('queue-panel')
|
@customElement('queue-panel')
|
||||||
export class QueuePanel extends LitElement {
|
export class QueuePanel extends LitElement {
|
||||||
private queue = new QueueController(this);
|
private queue = new QueueController(this);
|
||||||
@@ -10,26 +14,50 @@ export class QueuePanel extends LitElement {
|
|||||||
@property({ type: Boolean, reflect: true })
|
@property({ type: Boolean, reflect: true })
|
||||||
open = false;
|
open = false;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
private isDragging = false;
|
||||||
|
|
||||||
|
private panelWidth = DEFAULT_WIDTH;
|
||||||
|
|
||||||
static override styles = css`
|
static override styles = css`
|
||||||
:host {
|
:host {
|
||||||
display: block;
|
flex-shrink: 0;
|
||||||
position: fixed;
|
width: 0;
|
||||||
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;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
background-color: #1a1a2e;
|
||||||
|
transition: width 0.25s ease-in-out;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([open]) {
|
: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 {
|
.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() {
|
private handleClose() {
|
||||||
this.open = false;
|
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) {
|
private handleRemoveTrack(e: Event, position: number) {
|
||||||
@@ -174,7 +220,9 @@ export class QueuePanel extends LitElement {
|
|||||||
this.queue.playAtIndex(index);
|
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;
|
if (track.title) return track.title;
|
||||||
|
|
||||||
// Fall back to filename without extension.
|
// Fall back to filename without extension.
|
||||||
@@ -184,49 +232,97 @@ export class QueuePanel extends LitElement {
|
|||||||
return filename.replace(/\.[^.]+$/, '');
|
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() {
|
override render() {
|
||||||
const tracks = this.queue.tracks;
|
const tracks = this.queue.tracks;
|
||||||
const currentIndex = this.queue.currentIndex;
|
const currentIndex = this.queue.currentIndex;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="header">
|
<div class="panel-content">
|
||||||
<h3>Queue</h3>
|
<div
|
||||||
<button class="close-button" @click=${this.handleClose}>
|
class="resize-handle ${this.isDragging ? 'dragging' : ''}"
|
||||||
<wa-icon name="xmark"></wa-icon>
|
@mousedown=${this.handleMouseDown}
|
||||||
</button>
|
></div>
|
||||||
</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
|
${tracks.length === 0
|
||||||
? html`
|
? html`
|
||||||
<div class="empty-state">
|
<div class="empty-state">
|
||||||
<wa-icon name="list"></wa-icon>
|
<wa-icon name="list"></wa-icon>
|
||||||
<p>Queue is empty</p>
|
<p>Queue is empty</p>
|
||||||
<p style="font-size: 12px;">Click a track to start playing</p>
|
<p style="font-size: 12px;">
|
||||||
</div>
|
Click a track to start playing
|
||||||
`
|
</p>
|
||||||
: html`
|
</div>
|
||||||
<ul class="track-list">
|
`
|
||||||
${tracks.map(
|
: html`
|
||||||
(track, index) => html`
|
<ul class="track-list">
|
||||||
<li class="track-item ${index === currentIndex ? 'active' : ''}"
|
${tracks.map(
|
||||||
@click=${() => this.handleTrackClick(index)}>
|
(track, index) => html`
|
||||||
<span class="track-position">${index + 1}</span>
|
<li
|
||||||
<div class="track-details">
|
class="track-item ${index === currentIndex
|
||||||
<span class="track-title">${this.getDisplayTitle(track)}</span>
|
? 'active'
|
||||||
<span class="track-artist">${track.artist || 'Unknown Artist'}</span>
|
: ''}"
|
||||||
</div>
|
@click=${() => this.handleTrackClick(index)}
|
||||||
<button
|
|
||||||
class="remove-button"
|
|
||||||
@click=${(e: Event) => this.handleRemoveTrack(e, index)}
|
|
||||||
title="Remove from queue"
|
|
||||||
>
|
>
|
||||||
<wa-icon name="xmark"></wa-icon>
|
<span class="track-position">${index + 1}</span>
|
||||||
</button>
|
<div class="track-details">
|
||||||
</li>
|
<span class="track-title">
|
||||||
`
|
${this.getDisplayTitle(track)}
|
||||||
)}
|
</span>
|
||||||
</ul>
|
<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%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.track-row > * {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.track-row:hover {
|
.track-row:hover {
|
||||||
background-color: rgba(255, 255, 255, 0.05);
|
background-color: rgba(255, 255, 255, 0.05);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user