multi-select on track-list

This commit is contained in:
2026-02-16 01:20:36 -05:00
parent 4c09e7abee
commit 2a40be5f52
4 changed files with 79 additions and 40 deletions
@@ -23,10 +23,10 @@ export class TrackList extends LitElement {
private tracks: library.Track[] = []; private tracks: library.Track[] = [];
@state() @state()
private contextMenuOpen = false; private selectedTracks: Set<string> = new Set();
@state() @state()
private contextMenuTrack: library.Track | null = null; private contextMenuOpen = false;
@state() @state()
private playlistSubmenuOpen = false; private playlistSubmenuOpen = false;
@@ -68,6 +68,8 @@ export class TrackList extends LitElement {
border-bottom: 1px solid #333; border-bottom: 1px solid #333;
align-items: center; align-items: center;
width: 100%; width: 100%;
cursor: default;
user-select: none;
} }
.track-row > * { .track-row > * {
@@ -78,30 +80,28 @@ export class TrackList extends LitElement {
background-color: rgba(255, 255, 255, 0.05); background-color: rgba(255, 255, 255, 0.05);
} }
.track-row.selected {
background-color: rgba(100, 160, 255, 0.15);
}
.track-row.active { .track-row.active {
background-color: rgba(255, 212, 59, 0.1); background-color: rgba(255, 212, 59, 0.1);
} }
.track-row.active .track-name-button { .track-row.active .track-name {
color: #ffd43b; color: #ffd43b;
} }
.track-name-button { .track-row.selected.active {
background: none; background-color: rgba(100, 160, 255, 0.15);
border: none; }
color: inherit;
text-align: left; .track-name {
padding: 0;
cursor: pointer;
width: 100%;
font: inherit;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} cursor: default;
user-select: none;
.track-name-button:hover {
text-decoration: underline;
} }
.artist-name, .artist-name,
@@ -169,6 +169,7 @@ export class TrackList extends LitElement {
try { try {
const tracks = await GetAllTracks(); const tracks = await GetAllTracks();
this.tracks = tracks; this.tracks = tracks;
this.selectedTracks = new Set();
if (tracks[0]) { if (tracks[0]) {
LogPrint(tracks[0].TrackName); LogPrint(tracks[0].TrackName);
@@ -178,7 +179,32 @@ export class TrackList extends LitElement {
} }
} }
private onTrackClick(track: library.Track) { private getSelectedFilePaths(): string[] {
return this.tracks
.filter((t) => this.selectedTracks.has(t.FilePath))
.map((t) => t.FilePath);
}
private onTrackRowClick(e: MouseEvent, track: library.Track) {
const isCtrl = e.ctrlKey || e.metaKey;
if (isCtrl) {
const next = new Set(this.selectedTracks);
if (next.has(track.FilePath)) {
next.delete(track.FilePath);
} else {
next.add(track.FilePath);
}
this.selectedTracks = next;
} else {
this.selectedTracks = new Set([track.FilePath]);
}
}
private onTrackRowDblClick(track: library.Track) {
this.selectedTracks = new Set();
this.queue.setQueue([track.FilePath], 0); this.queue.setQueue([track.FilePath], 0);
} }
@@ -186,7 +212,10 @@ export class TrackList extends LitElement {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
this.contextMenuTrack = track; if (!this.selectedTracks.has(track.FilePath)) {
this.selectedTracks = new Set([track.FilePath]);
}
this.contextMenuOpen = true; this.contextMenuOpen = true;
// Position the popup at the mouse cursor using a virtual anchor. // Position the popup at the mouse cursor using a virtual anchor.
@@ -214,31 +243,34 @@ export class TrackList extends LitElement {
} }
private onContextMenuAction(action: string) { private onContextMenuAction(action: string) {
if (!this.contextMenuTrack) return; const filePaths = this.getSelectedFilePaths();
const filePath = this.contextMenuTrack.FilePath; if (filePaths.length === 0) return;
switch (action) { switch (action) {
case 'play': case 'play':
this.queue.setQueue([filePath], 0); this.queue.setQueue(filePaths, 0);
break; break;
case 'add-to-queue': case 'add-to-queue':
this.queue.addToQueue(filePath); this.queue.addTracksToQueue(filePaths);
break; break;
case 'play-next': case 'play-next':
this.queue.playNext(filePath); this.queue.playTracksNext(filePaths);
break; break;
} }
this.closeContextMenu(); this.closeContextMenu(true);
} }
private closeContextMenu() { private closeContextMenu(clearSelection = false) {
if (!this.contextMenuOpen) return; if (!this.contextMenuOpen) return;
this.closePlaylistSubmenu(); this.closePlaylistSubmenu();
this.contextMenuOpen = false; this.contextMenuOpen = false;
this.contextMenuTrack = null;
if (clearSelection) {
this.selectedTracks = new Set();
}
const popup = this.contextMenuPopup; const popup = this.contextMenuPopup;
@@ -282,7 +314,7 @@ export class TrackList extends LitElement {
} }
private onPlaylistActionComplete = () => { private onPlaylistActionComplete = () => {
this.closeContextMenu(); this.closeContextMenu(true);
}; };
private isActiveTrack(track: library.Track): boolean { private isActiveTrack(track: library.Track): boolean {
@@ -295,22 +327,29 @@ export class TrackList extends LitElement {
private renderTrackRow = (track: library.Track): unknown => { private renderTrackRow = (track: library.Track): unknown => {
const active = this.isActiveTrack(track); const active = this.isActiveTrack(track);
const selected = this.selectedTracks.has(track.FilePath);
const classes = [
'track-row',
active ? 'active' : '',
selected ? 'selected' : '',
]
.filter(Boolean)
.join(' ');
return html` return html`
<div <div
class="track-row ${active ? 'active' : ''}" class=${classes}
@contextmenu=${(e: MouseEvent) => this.onTrackContextMenu(e, track)} @click=${(e: MouseEvent) => this.onTrackRowClick(e, track)}
@dblclick=${() => this.onTrackRowDblClick(track)}
@contextmenu=${(e: MouseEvent) =>
this.onTrackContextMenu(e, track)}
> >
<div> <div class="track-name">${track.TrackName}</div>
<button
class="track-name-button"
@click=${() => this.onTrackClick(track)}
>
${track.TrackName}
</button>
</div>
<div class="artist-name">${track.ArtistName}</div> <div class="artist-name">${track.ArtistName}</div>
<div class="track-length">${formatMilliseconds(track.TrackLength)}</div> <div class="track-length">
${formatMilliseconds(track.TrackLength)}
</div>
</div> </div>
`; `;
}; };
@@ -381,10 +420,10 @@ export class TrackList extends LitElement {
placement="right-start" placement="right-start"
.active=${this.playlistSubmenuOpen} .active=${this.playlistSubmenuOpen}
> >
${this.playlistSubmenuOpen && this.contextMenuTrack ${this.playlistSubmenuOpen && this.selectedTracks.size > 0
? html` ? html`
<playlist-picker <playlist-picker
.filePaths=${[this.contextMenuTrack.FilePath]} .filePaths=${this.getSelectedFilePaths()}
@playlist-action-complete=${this.onPlaylistActionComplete} @playlist-action-complete=${this.onPlaylistActionComplete}
@click=${(e: Event) => e.stopPropagation()} @click=${(e: Event) => e.stopPropagation()}
></playlist-picker> ></playlist-picker>
View File
Vendored Executable → Regular
View File
View File