feat(quick-8): wire duplicate detection into playlist-picker and playlist-view

- playlist-picker checks for duplicates before adding tracks
- playlist-view drag-drop handler checks for duplicates before adding
- Both show duplicate-tracks-dialog when duplicates found
- Non-duplicate tracks added directly when no duplicates exist
This commit is contained in:
2026-03-01 11:15:05 -05:00
parent 9f3ba2b9d4
commit 917a79a8d6
2 changed files with 60 additions and 6 deletions
@@ -1,5 +1,5 @@
import { LitElement, html, css, nothing } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import { customElement, property, state, query } from 'lit/decorators.js';
import { EventsOn } from '@runtime/runtime';
import '@awesome.me/webawesome/dist/components/icon/icon.js';
@@ -9,9 +9,12 @@ import {
GetAllPlaylists,
AddTracksToPlaylist,
CreatePlaylistWithTracks,
FindDuplicateTracksInPlaylist,
} from '@go/playlist/Service';
import { Events } from '../../events';
import type { playlist } from '@go/models';
import '@components/duplicate-tracks-dialog/duplicate-tracks-dialog.js';
import type { DuplicateTracksDialog } from '@components/duplicate-tracks-dialog/duplicate-tracks-dialog.js';
/**
* A reusable playlist picker that displays existing playlists
@@ -26,6 +29,9 @@ export class PlaylistPicker extends LitElement {
@property({ type: Array }) filePaths: string[] = [];
private cancelScanComplete?: () => void;
@query('duplicate-tracks-dialog')
private duplicateDialog!: DuplicateTracksDialog;
@state() private mode: 'list' | 'create' = 'list';
@state() private playlists: playlist.Summary[] = [];
@state() private newPlaylistName = '';
@@ -161,6 +167,23 @@ export class PlaylistPicker extends LitElement {
this.loading = true;
try {
const result = await FindDuplicateTracksInPlaylist(
playlistId,
this.filePaths,
);
const duplicates = result.Duplicates ?? [];
const unique = result.Unique ?? [];
if (duplicates.length > 0) {
// Show dialog — it handles adding tracks and dispatching completion.
this.loading = false;
await this.updateComplete;
this.duplicateDialog.show(playlistId, duplicates, unique);
return;
}
// No duplicates — add all directly.
await AddTracksToPlaylist(playlistId, this.filePaths);
this.dispatchComplete();
} catch (err) {
@@ -239,11 +262,14 @@ export class PlaylistPicker extends LitElement {
}
override render() {
if (this.mode === 'create') {
return this.renderCreateForm();
}
return this.renderPlaylistList();
return html`
${this.mode === 'create'
? this.renderCreateForm()
: this.renderPlaylistList()}
<duplicate-tracks-dialog
@playlist-action-complete=${this.dispatchComplete}
></duplicate-tracks-dialog>
`;
}
private renderPlaylistList() {
@@ -14,6 +14,7 @@ import {
RenamePlaylist,
ImportPlaylists,
RemovePhantomTracks,
FindDuplicateTracksInPlaylist,
} from '@go/playlist/Service';
import { PlaylistFilePicker } from '@go/frontendutil/FrontendUtil';
import type { playlist } from '@go/models';
@@ -48,6 +49,8 @@ import type { TrackDetails } from '@components/track-details/track-details.js';
import type { CoverArtUrls } from '@components/track-details/track-details.js';
import '@components/phantom-resolver/phantom-resolver.js';
import type { PhantomResolver } from '@components/phantom-resolver/phantom-resolver.js';
import '@components/duplicate-tracks-dialog/duplicate-tracks-dialog.js';
import type { DuplicateTracksDialog } from '@components/duplicate-tracks-dialog/duplicate-tracks-dialog.js';
const SCROLL_DEBOUNCE_MS = 100;
@@ -253,6 +256,9 @@ export class PlaylistView
@query('phantom-resolver')
private phantomResolver!: PhantomResolver;
@query('duplicate-tracks-dialog')
private duplicateDialog!: DuplicateTracksDialog;
private closePlaylistCtxMenuHandler =
() => this.closePlaylistContextMenu();
@@ -1821,6 +1827,24 @@ export class PlaylistView
}
try {
const result = await FindDuplicateTracksInPlaylist(
entry.summary.ID,
payload.filePaths,
);
const duplicates = result.Duplicates ?? [];
const unique = result.Unique ?? [];
if (duplicates.length > 0) {
await this.updateComplete;
this.duplicateDialog.show(
entry.summary.ID,
duplicates,
unique,
);
return;
}
await AddTracksToPlaylist(
entry.summary.ID,
payload.filePaths,
@@ -2785,6 +2809,10 @@ export class PlaylistView
@phantom-resolved=${() =>
this.refreshPlaylists()}
></phantom-resolver>
<duplicate-tracks-dialog
@playlist-action-complete=${() =>
this.refreshPlaylists()}
></duplicate-tracks-dialog>
`;
}