frontend fixes
album artist gets populated with artist as fallback (frontend also uses this as display name fallback for albums). added scroll position persistence when switching main views. also added frontend cache for faster switching.
This commit is contained in:
@@ -1,9 +1,14 @@
|
||||
import { LitElement, html, css, nothing } from 'lit';
|
||||
import { customElement, state, query } from 'lit/decorators.js';
|
||||
import { GetAllAlbums, GetAlbumTracks } from '@go/library/Library';
|
||||
import { GetAlbumTracks } from '@go/library/Library';
|
||||
import { library } from '@go/models';
|
||||
import { QueueController } from '@store/controllers/queue-controller';
|
||||
import { LibraryController } from '@store/controllers/library-controller';
|
||||
import '@lit-labs/virtualizer';
|
||||
import type {
|
||||
LitVirtualizer,
|
||||
VisibilityChangedEvent,
|
||||
} from '@lit-labs/virtualizer';
|
||||
import { grid } from '@lit-labs/virtualizer/layouts/grid.js';
|
||||
import '@awesome.me/webawesome/dist/components/popup/popup.js';
|
||||
import '@awesome.me/webawesome/dist/components/dropdown-item/dropdown-item.js';
|
||||
@@ -14,8 +19,16 @@ import type { PlaylistPicker } from '@components/playlist-picker/playlist-picker
|
||||
@customElement('cover-grid')
|
||||
export class CoverGrid extends LitElement {
|
||||
private queue = new QueueController(this);
|
||||
private libraryCtrl = new LibraryController(this);
|
||||
|
||||
// Grid layout constants — must match the grid() config in render().
|
||||
private static readonly GRID_ITEM_WIDTH = 176;
|
||||
private static readonly GRID_ITEM_HEIGHT = 230;
|
||||
private static readonly GRID_GAP = 16;
|
||||
private static readonly GRID_PADDING = 16;
|
||||
|
||||
private lastSelectedIndex: number | null = null;
|
||||
private hasRestoredScroll = false;
|
||||
|
||||
private closeHandler = () => this.closeContextMenu();
|
||||
|
||||
@@ -31,6 +44,10 @@ export class CoverGrid extends LitElement {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
lit-virtualizer.restoring {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.album-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -185,12 +202,18 @@ export class CoverGrid extends LitElement {
|
||||
@state()
|
||||
private playlistFilePaths: string[] = [];
|
||||
|
||||
@state()
|
||||
private hiddenForRestore = false;
|
||||
|
||||
@query('#context-menu')
|
||||
private contextMenuPopup!: HTMLElement;
|
||||
|
||||
@query('#playlist-submenu')
|
||||
private playlistSubmenuPopup!: HTMLElement;
|
||||
|
||||
@query('lit-virtualizer')
|
||||
private virtualizer!: LitVirtualizer;
|
||||
|
||||
override connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.loadAlbums();
|
||||
@@ -199,6 +222,12 @@ export class CoverGrid extends LitElement {
|
||||
}
|
||||
|
||||
override disconnectedCallback() {
|
||||
this.virtualizer?.removeEventListener(
|
||||
'visibilityChanged',
|
||||
this.onVisibilityChanged,
|
||||
);
|
||||
this.hasRestoredScroll = false;
|
||||
this.hiddenForRestore = false;
|
||||
super.disconnectedCallback();
|
||||
document.removeEventListener('click', this.closeHandler);
|
||||
document.removeEventListener('contextmenu', this.closeHandler);
|
||||
@@ -207,7 +236,7 @@ export class CoverGrid extends LitElement {
|
||||
private async loadAlbums() {
|
||||
try {
|
||||
this.loading = true;
|
||||
const albums = await GetAllAlbums();
|
||||
const albums = await this.libraryCtrl.getAlbums();
|
||||
this.albums = albums ?? [];
|
||||
this.selectedAlbums = new Set();
|
||||
this.lastSelectedIndex = null;
|
||||
@@ -217,6 +246,89 @@ export class CoverGrid extends LitElement {
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
const savedIndex =
|
||||
this.libraryCtrl.getScrollPosition('albums');
|
||||
|
||||
if (savedIndex > 0) {
|
||||
this.hiddenForRestore = true;
|
||||
}
|
||||
|
||||
await this.updateComplete;
|
||||
|
||||
if (this.isConnected && this.virtualizer) {
|
||||
this.virtualizer.addEventListener(
|
||||
'visibilityChanged',
|
||||
this.onVisibilityChanged,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private onVisibilityChanged = (e: Event) => {
|
||||
const { first, last } = e as VisibilityChangedEvent;
|
||||
|
||||
if (!this.hasRestoredScroll) {
|
||||
// The grid layout fires a premature visibilityChanged
|
||||
// before the viewport width is measured. Skip until
|
||||
// real items are visible.
|
||||
if (last <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.hasRestoredScroll = true;
|
||||
|
||||
const savedIndex =
|
||||
this.libraryCtrl.getScrollPosition('albums');
|
||||
|
||||
if (savedIndex > 0) {
|
||||
void this.restoreScrollPosition(savedIndex);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.hiddenForRestore = false;
|
||||
}
|
||||
|
||||
this.libraryCtrl.setScrollPosition('albums', first);
|
||||
};
|
||||
|
||||
private async restoreScrollPosition(
|
||||
savedIndex: number,
|
||||
) {
|
||||
// Wait for the virtualizer layout to settle. layoutComplete
|
||||
// resolves after ResizeObserver + double-rAF, so the sizer
|
||||
// transform has been painted and scrollHeight is correct.
|
||||
await this.virtualizer?.layoutComplete;
|
||||
|
||||
// Compute pixel offset matching the grid layout internals:
|
||||
// offset = padding + row * (itemHeight + gap).
|
||||
const vw = this.virtualizer?.clientWidth ?? 0;
|
||||
const {
|
||||
GRID_ITEM_WIDTH,
|
||||
GRID_ITEM_HEIGHT,
|
||||
GRID_GAP,
|
||||
GRID_PADDING,
|
||||
} = CoverGrid;
|
||||
|
||||
const availableWidth = vw - GRID_PADDING * 2;
|
||||
const columns = Math.max(
|
||||
1,
|
||||
Math.floor(
|
||||
(availableWidth + GRID_GAP) /
|
||||
(GRID_ITEM_WIDTH + GRID_GAP),
|
||||
),
|
||||
);
|
||||
const row = Math.floor(savedIndex / columns);
|
||||
const pixelOffset =
|
||||
GRID_PADDING +
|
||||
row * (GRID_ITEM_HEIGHT + GRID_GAP);
|
||||
|
||||
if (this.virtualizer) {
|
||||
this.virtualizer.scrollTop = pixelOffset;
|
||||
}
|
||||
|
||||
// Reveal now that the virtualizer is at the correct position.
|
||||
this.hiddenForRestore = false;
|
||||
}
|
||||
|
||||
private selectRange(
|
||||
@@ -528,6 +640,7 @@ export class CoverGrid extends LitElement {
|
||||
|
||||
return html`
|
||||
<lit-virtualizer
|
||||
class=${this.hiddenForRestore ? 'restoring' : ''}
|
||||
scroller
|
||||
.items=${this.albums}
|
||||
.renderItem=${this.renderAlbumCard}
|
||||
|
||||
@@ -23,13 +23,13 @@ export class QueuePanel extends LitElement {
|
||||
@state()
|
||||
private playlistPickerOpen = false;
|
||||
|
||||
@query('#save-playlist-popup')
|
||||
private savePlaylistPopup!: HTMLElement;
|
||||
@query('#add-to-playlist-popup')
|
||||
private addToPlaylistPopup!: HTMLElement;
|
||||
|
||||
private closePickerHandler = (e: MouseEvent) => {
|
||||
const path = e.composedPath();
|
||||
const popup = this.savePlaylistPopup;
|
||||
const btn = this.shadowRoot?.querySelector('.save-playlist-button');
|
||||
const popup = this.addToPlaylistPopup;
|
||||
const btn = this.shadowRoot?.querySelector('.add-to-playlist-button');
|
||||
|
||||
if (popup && !path.includes(popup) && (!btn || !path.includes(btn))) {
|
||||
this.closePlaylistPicker();
|
||||
@@ -94,7 +94,7 @@ export class QueuePanel extends LitElement {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.save-playlist-button {
|
||||
.add-to-playlist-button {
|
||||
background: none;
|
||||
border: none;
|
||||
color: inherit;
|
||||
@@ -104,16 +104,16 @@ export class QueuePanel extends LitElement {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.save-playlist-button:hover {
|
||||
.add-to-playlist-button:hover {
|
||||
color: #ffd43b;
|
||||
}
|
||||
|
||||
.save-playlist-button:disabled {
|
||||
.add-to-playlist-button:disabled {
|
||||
color: #555;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
#save-playlist-popup {
|
||||
#add-to-playlist-popup {
|
||||
z-index: 210;
|
||||
}
|
||||
|
||||
@@ -231,15 +231,15 @@ export class QueuePanel extends LitElement {
|
||||
document.removeEventListener('click', this.closePickerHandler);
|
||||
}
|
||||
|
||||
private async handleSaveAsPlaylist() {
|
||||
private async handleAddToPlaylist() {
|
||||
if (this.queue.tracks.length === 0) return;
|
||||
|
||||
this.playlistPickerOpen = !this.playlistPickerOpen;
|
||||
|
||||
await this.updateComplete;
|
||||
|
||||
const popup = this.savePlaylistPopup;
|
||||
const btn = this.shadowRoot?.querySelector('.save-playlist-button');
|
||||
const popup = this.addToPlaylistPopup;
|
||||
const btn = this.shadowRoot?.querySelector('.add-to-playlist-button');
|
||||
|
||||
if (popup && btn) {
|
||||
(popup as any).anchor = btn;
|
||||
@@ -260,7 +260,7 @@ export class QueuePanel extends LitElement {
|
||||
|
||||
this.playlistPickerOpen = false;
|
||||
|
||||
const popup = this.savePlaylistPopup;
|
||||
const popup = this.addToPlaylistPopup;
|
||||
|
||||
if (popup) {
|
||||
(popup as any).active = false;
|
||||
@@ -336,17 +336,17 @@ export class QueuePanel extends LitElement {
|
||||
<div class="header">
|
||||
<h3>Queue</h3>
|
||||
<button
|
||||
class="save-playlist-button"
|
||||
@click=${this.handleSaveAsPlaylist}
|
||||
class="add-to-playlist-button"
|
||||
@click=${this.handleAddToPlaylist}
|
||||
?disabled=${tracks.length === 0}
|
||||
title="Save queue as playlist"
|
||||
title="Add queue to playlist"
|
||||
>
|
||||
<wa-icon name="plus"></wa-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<wa-popup
|
||||
id="save-playlist-popup"
|
||||
id="add-to-playlist-popup"
|
||||
placement="bottom-end"
|
||||
.active=${this.playlistPickerOpen}
|
||||
>
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { GetAllTracks } from '@go/library/Library';
|
||||
import { library } from '@go/models';
|
||||
import { LogPrint } from '@runtime/runtime';
|
||||
import { LitElement, html, css, nothing } from 'lit';
|
||||
import { customElement, state, query } from 'lit/decorators.js';
|
||||
import { formatMilliseconds } from '@utils/time';
|
||||
import { PlayerController } from '@store/controllers/player-controller';
|
||||
import { QueueController } from '@store/controllers/queue-controller';
|
||||
import { LibraryController } from '@store/controllers/library-controller';
|
||||
import '@lit-labs/virtualizer';
|
||||
import type {
|
||||
LitVirtualizer,
|
||||
VisibilityChangedEvent,
|
||||
} from '@lit-labs/virtualizer';
|
||||
import { flow } from '@lit-labs/virtualizer/layouts/flow.js';
|
||||
import '@awesome.me/webawesome/dist/components/popup/popup.js';
|
||||
import '@awesome.me/webawesome/dist/components/dropdown-item/dropdown-item.js';
|
||||
@@ -14,10 +17,16 @@ import '@awesome.me/webawesome/dist/components/icon/icon.js';
|
||||
import '@components/playlist-picker/playlist-picker.js';
|
||||
import type { PlaylistPicker } from '@components/playlist-picker/playlist-picker.js';
|
||||
|
||||
const COLUMN_STORAGE_KEY = 'track-list-column-widths';
|
||||
const MIN_COLUMN_WIDTH = 50;
|
||||
const DEFAULT_DURATION_WIDTH = 80;
|
||||
const COLUMN_COUNT = 3;
|
||||
|
||||
@customElement('track-list')
|
||||
export class TrackList extends LitElement {
|
||||
private player = new PlayerController(this);
|
||||
private queue = new QueueController(this);
|
||||
private libraryCtrl = new LibraryController(this);
|
||||
|
||||
@state()
|
||||
private tracks: library.Track[] = [];
|
||||
@@ -37,12 +46,160 @@ export class TrackList extends LitElement {
|
||||
@query('#playlist-submenu')
|
||||
private playlistSubmenuPopup!: HTMLElement;
|
||||
|
||||
@query('lit-virtualizer')
|
||||
private virtualizer!: LitVirtualizer;
|
||||
|
||||
private lastSelectedIndex: number | null = null;
|
||||
|
||||
private closeHandler = () => this.closeContextMenu();
|
||||
|
||||
@state()
|
||||
private columnWidths: number[] = [];
|
||||
|
||||
private resizingColumn: number | null = null;
|
||||
private resizeStartX = 0;
|
||||
private resizeStartWidths: number[] = [];
|
||||
private resizeObserver: ResizeObserver | null = null;
|
||||
private flowLayout = flow();
|
||||
private hasRestoredScroll = false;
|
||||
|
||||
private get gridTemplateColumns(): string {
|
||||
if (this.columnWidths.length === 0) {
|
||||
return '1fr 1fr 80px';
|
||||
}
|
||||
|
||||
return this.columnWidths
|
||||
.map((w) => `${w}px`)
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
private get colBoundaryPositions(): number[] {
|
||||
if (this.columnWidths.length === 0) return [];
|
||||
|
||||
const padding = 8;
|
||||
const positions: number[] = [];
|
||||
let cumulative = padding;
|
||||
|
||||
for (let i = 0; i < this.columnWidths.length - 1; i++) {
|
||||
cumulative += this.columnWidths[i] ?? 0;
|
||||
positions.push(cumulative);
|
||||
}
|
||||
|
||||
return positions;
|
||||
}
|
||||
|
||||
private initColumnWidths() {
|
||||
const saved = this.loadColumnWidths();
|
||||
|
||||
if (saved) {
|
||||
this.columnWidths = saved;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.computeDefaultWidths();
|
||||
}
|
||||
|
||||
private computeDefaultWidths() {
|
||||
const totalWidth = this.clientWidth;
|
||||
|
||||
if (totalWidth <= 0) return;
|
||||
|
||||
const remaining = totalWidth - DEFAULT_DURATION_WIDTH;
|
||||
const half = Math.floor(remaining / 2);
|
||||
|
||||
this.columnWidths = [
|
||||
half,
|
||||
remaining - half,
|
||||
DEFAULT_DURATION_WIDTH,
|
||||
];
|
||||
}
|
||||
|
||||
private loadColumnWidths(): number[] | null {
|
||||
try {
|
||||
const raw = localStorage.getItem(COLUMN_STORAGE_KEY);
|
||||
|
||||
if (!raw) return null;
|
||||
|
||||
const parsed: unknown = JSON.parse(raw);
|
||||
|
||||
if (
|
||||
!Array.isArray(parsed) ||
|
||||
parsed.length !== COLUMN_COUNT ||
|
||||
!parsed.every(
|
||||
(v: unknown) =>
|
||||
typeof v === 'number' && v >= MIN_COLUMN_WIDTH,
|
||||
)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return parsed as number[];
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private saveColumnWidths() {
|
||||
try {
|
||||
localStorage.setItem(
|
||||
COLUMN_STORAGE_KEY,
|
||||
JSON.stringify(this.columnWidths),
|
||||
);
|
||||
} catch {
|
||||
// Ignore storage errors.
|
||||
}
|
||||
}
|
||||
|
||||
private onColResizeStart = (e: MouseEvent, columnIndex: number) => {
|
||||
e.preventDefault();
|
||||
this.resizingColumn = columnIndex;
|
||||
this.resizeStartX = e.clientX;
|
||||
this.resizeStartWidths = [...this.columnWidths];
|
||||
this.requestUpdate();
|
||||
};
|
||||
|
||||
private onColResizeMove = (e: MouseEvent) => {
|
||||
if (this.resizingColumn === null) return;
|
||||
|
||||
const delta = e.clientX - this.resizeStartX;
|
||||
const col = this.resizingColumn;
|
||||
const nextCol = col + 1;
|
||||
const startLeft = this.resizeStartWidths[col] ?? 0;
|
||||
const startRight = this.resizeStartWidths[nextCol] ?? 0;
|
||||
const total = startLeft + startRight;
|
||||
|
||||
let newLeft = startLeft + delta;
|
||||
let newRight = startRight - delta;
|
||||
|
||||
if (newLeft < MIN_COLUMN_WIDTH) {
|
||||
newLeft = MIN_COLUMN_WIDTH;
|
||||
newRight = total - MIN_COLUMN_WIDTH;
|
||||
}
|
||||
|
||||
if (newRight < MIN_COLUMN_WIDTH) {
|
||||
newRight = MIN_COLUMN_WIDTH;
|
||||
newLeft = total - MIN_COLUMN_WIDTH;
|
||||
}
|
||||
|
||||
const updated = [...this.resizeStartWidths];
|
||||
|
||||
updated[col] = newLeft;
|
||||
updated[nextCol] = newRight;
|
||||
this.columnWidths = updated;
|
||||
};
|
||||
|
||||
private onColResizeEnd = () => {
|
||||
if (this.resizingColumn === null) return;
|
||||
|
||||
this.resizingColumn = null;
|
||||
this.saveColumnWidths();
|
||||
this.requestUpdate();
|
||||
};
|
||||
|
||||
static override styles = css`
|
||||
:host {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
@@ -50,7 +207,6 @@ export class TrackList extends LitElement {
|
||||
|
||||
.header-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 80px;
|
||||
padding: 8px;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
@@ -58,6 +214,44 @@ export class TrackList extends LitElement {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.header-cell {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.resize-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.col-resize-handle {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 1px;
|
||||
cursor: col-resize;
|
||||
pointer-events: auto;
|
||||
background-color: #444;
|
||||
transition: background-color 0.15s ease;
|
||||
}
|
||||
|
||||
.col-resize-handle::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -3px;
|
||||
width: 7px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.col-resize-handle:hover,
|
||||
.col-resize-handle.active {
|
||||
background-color: #6c757d;
|
||||
}
|
||||
|
||||
lit-virtualizer {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
@@ -65,7 +259,6 @@ export class TrackList extends LitElement {
|
||||
|
||||
.track-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 80px;
|
||||
font-size: 12px;
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid #333;
|
||||
@@ -79,6 +272,11 @@ export class TrackList extends LitElement {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.header-cell + .header-cell,
|
||||
.track-row > :not(:first-child) {
|
||||
padding-left: 6px;
|
||||
}
|
||||
|
||||
.track-row:hover {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
@@ -160,29 +358,124 @@ export class TrackList extends LitElement {
|
||||
this.loadTracks();
|
||||
document.addEventListener('click', this.closeHandler);
|
||||
document.addEventListener('contextmenu', this.closeHandler);
|
||||
document.addEventListener('mousemove', this.onColResizeMove);
|
||||
document.addEventListener('mouseup', this.onColResizeEnd);
|
||||
|
||||
this.resizeObserver = new ResizeObserver(() => {
|
||||
this.onHostResize();
|
||||
});
|
||||
|
||||
this.resizeObserver.observe(this);
|
||||
}
|
||||
|
||||
override disconnectedCallback() {
|
||||
this.virtualizer?.removeEventListener(
|
||||
'visibilityChanged',
|
||||
this.onVisibilityChanged,
|
||||
);
|
||||
this.hasRestoredScroll = false;
|
||||
super.disconnectedCallback();
|
||||
document.removeEventListener('click', this.closeHandler);
|
||||
document.removeEventListener('contextmenu', this.closeHandler);
|
||||
document.removeEventListener('mousemove', this.onColResizeMove);
|
||||
document.removeEventListener('mouseup', this.onColResizeEnd);
|
||||
|
||||
this.resizeObserver?.disconnect();
|
||||
this.resizeObserver = null;
|
||||
}
|
||||
|
||||
override firstUpdated() {
|
||||
this.initColumnWidths();
|
||||
}
|
||||
|
||||
override updated(changed: Map<string, unknown>) {
|
||||
if (changed.has('columnWidths')) {
|
||||
this.virtualizer?.requestUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
private previousHostWidth = 0;
|
||||
|
||||
private onHostResize() {
|
||||
const newWidth = this.clientWidth;
|
||||
|
||||
if (
|
||||
newWidth <= 0 ||
|
||||
this.columnWidths.length === 0 ||
|
||||
this.resizingColumn !== null
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.previousHostWidth === 0) {
|
||||
this.previousHostWidth = newWidth;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const oldTotal = this.columnWidths.reduce(
|
||||
(sum, w) => sum + w,
|
||||
0,
|
||||
);
|
||||
|
||||
if (oldTotal <= 0) return;
|
||||
|
||||
const scale = newWidth / oldTotal;
|
||||
|
||||
this.columnWidths = this.columnWidths.map((w) =>
|
||||
Math.max(
|
||||
MIN_COLUMN_WIDTH,
|
||||
Math.round(w * scale),
|
||||
),
|
||||
);
|
||||
|
||||
this.previousHostWidth = newWidth;
|
||||
this.saveColumnWidths();
|
||||
}
|
||||
|
||||
async loadTracks() {
|
||||
try {
|
||||
const tracks = await GetAllTracks();
|
||||
const tracks = await this.libraryCtrl.getTracks();
|
||||
this.tracks = tracks;
|
||||
this.selectedTracks = new Set();
|
||||
this.lastSelectedIndex = null;
|
||||
await this.updateComplete;
|
||||
|
||||
if (tracks[0]) {
|
||||
LogPrint(tracks[0].TrackName);
|
||||
if (this.isConnected && this.virtualizer) {
|
||||
this.virtualizer.addEventListener(
|
||||
'visibilityChanged',
|
||||
this.onVisibilityChanged,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading tracks:', error);
|
||||
}
|
||||
}
|
||||
|
||||
private onVisibilityChanged = (e: Event) => {
|
||||
const { first } = e as VisibilityChangedEvent;
|
||||
|
||||
if (!this.hasRestoredScroll) {
|
||||
this.hasRestoredScroll = true;
|
||||
|
||||
const savedIndex =
|
||||
this.libraryCtrl.getScrollPosition('tracks');
|
||||
|
||||
if (savedIndex > 0) {
|
||||
requestAnimationFrame(() => {
|
||||
this.virtualizer?.scrollToIndex(
|
||||
savedIndex,
|
||||
'start',
|
||||
);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.libraryCtrl.setScrollPosition('tracks', first);
|
||||
};
|
||||
|
||||
private getSelectedFilePaths(): string[] {
|
||||
return this.tracks
|
||||
.filter((t) => this.selectedTracks.has(t.FilePath))
|
||||
@@ -395,10 +688,15 @@ export class TrackList extends LitElement {
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
|
||||
const colStyle =
|
||||
`grid-template-columns: ${this.gridTemplateColumns}`;
|
||||
|
||||
return html`
|
||||
<div
|
||||
class=${classes}
|
||||
@click=${(e: MouseEvent) => this.onTrackRowClick(e, track, index)}
|
||||
style=${colStyle}
|
||||
@click=${(e: MouseEvent) =>
|
||||
this.onTrackRowClick(e, track, index)}
|
||||
@dblclick=${() => this.onTrackRowDblClick(track)}
|
||||
@contextmenu=${(e: MouseEvent) =>
|
||||
this.onTrackContextMenu(e, track)}
|
||||
@@ -417,19 +715,35 @@ export class TrackList extends LitElement {
|
||||
${this.tracks.length === 0
|
||||
? html`<p>Loading tracks...</p>`
|
||||
: html`
|
||||
<div class="header-row">
|
||||
<span>Track Name</span>
|
||||
<span>Artist</span>
|
||||
<span>Track Length</span>
|
||||
<div
|
||||
class="header-row"
|
||||
style="grid-template-columns: ${this.gridTemplateColumns}"
|
||||
>
|
||||
<div class="header-cell">Track Name</div>
|
||||
<div class="header-cell">Artist</div>
|
||||
<div class="header-cell">Track Length</div>
|
||||
</div>
|
||||
<lit-virtualizer
|
||||
scroller
|
||||
.items=${this.tracks}
|
||||
.renderItem=${this.renderTrackRow}
|
||||
.layout=${flow()}
|
||||
.layout=${this.flowLayout}
|
||||
></lit-virtualizer>
|
||||
`}
|
||||
|
||||
<div class="resize-overlay">
|
||||
${this.colBoundaryPositions.map(
|
||||
(pos, i) => html`
|
||||
<div
|
||||
class="col-resize-handle ${this.resizingColumn === i ? 'active' : ''}"
|
||||
style="left: ${pos}px"
|
||||
@mousedown=${(e: MouseEvent) =>
|
||||
this.onColResizeStart(e, i)}
|
||||
></div>
|
||||
`,
|
||||
)}
|
||||
</div>
|
||||
|
||||
<wa-popup
|
||||
id="context-menu"
|
||||
placement="bottom-start"
|
||||
|
||||
Reference in New Issue
Block a user