diff --git a/frontend/src/components/playlist-picker/playlist-picker.ts b/frontend/src/components/playlist-picker/playlist-picker.ts
index 0cbe708..78f07f3 100644
--- a/frontend/src/components/playlist-picker/playlist-picker.ts
+++ b/frontend/src/components/playlist-picker/playlist-picker.ts
@@ -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()}
+
+ `;
}
private renderPlaylistList() {
diff --git a/frontend/src/components/playlist-view/playlist-view.ts b/frontend/src/components/playlist-view/playlist-view.ts
index fc0430e..6d2031d 100644
--- a/frontend/src/components/playlist-view/playlist-view.ts
+++ b/frontend/src/components/playlist-view/playlist-view.ts
@@ -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()}
>
+
+ this.refreshPlaylists()}
+ >
`;
}