From 917a79a8d6e30dddd2170323bb26692386794872 Mon Sep 17 00:00:00 2001 From: Caleb Allen Date: Sun, 1 Mar 2026 11:15:05 -0500 Subject: [PATCH] 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 --- .../playlist-picker/playlist-picker.ts | 38 ++++++++++++++++--- .../components/playlist-view/playlist-view.ts | 28 ++++++++++++++ 2 files changed, 60 insertions(+), 6 deletions(-) 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()} + > `; }