From 2ffd9df13334b258d601cfd8f4f7f7563ddc570a Mon Sep 17 00:00:00 2001 From: vicrodh <103399283+vicrodh@users.noreply.github.com> Date: Sat, 31 Jan 2026 16:45:23 -0600 Subject: [PATCH] feat: add "Find available version" option for unavailable tracks - Add TrackReplacementModal to search for replacement tracks - Add onFindReplacement action to TrackMenu context menu - Show replacement option only for owned playlists with unavailable tracks - Replacement searches by track title + artist - Shows quality info (bit depth, sample rate) to help user choose - Supports preview playback before confirming - Replace old track with new one in playlist Also fix tooltip for unavailable tracks to show a complete message instead of "is no longer available" (now shows "Track no longer available in Qobuz library") --- src/lib/components/TrackMenu.svelte | 13 +- .../components/TrackReplacementModal.svelte | 588 ++++++++++++++++++ .../views/PlaylistDetailView.svelte | 112 +++- src/lib/i18n/locales/en.json | 13 +- src/lib/i18n/locales/es.json | 13 +- 5 files changed, 730 insertions(+), 9 deletions(-) create mode 100644 src/lib/components/TrackReplacementModal.svelte diff --git a/src/lib/components/TrackMenu.svelte b/src/lib/components/TrackMenu.svelte index 9e7412800..773232c4b 100644 --- a/src/lib/components/TrackMenu.svelte +++ b/src/lib/components/TrackMenu.svelte @@ -16,7 +16,8 @@ CloudDownload, RefreshCw, Radio, - Info + Info, + Search } from 'lucide-svelte'; import { shouldHidePlaylistFeatures } from '$lib/utils/offlineHelpers'; import { @@ -37,6 +38,7 @@ onAddFavorite?: () => void; onAddToPlaylist?: () => void; onRemoveFromPlaylist?: () => void; + onFindReplacement?: () => void; onShareQobuz?: () => void; onShareSonglink?: () => void; onGoToAlbum?: () => void; @@ -58,6 +60,7 @@ onAddFavorite, onAddToPlaylist, onRemoveFromPlaylist, + onFindReplacement, onShareQobuz, onShareSonglink, onGoToAlbum, @@ -100,7 +103,7 @@ } const hasPlayback = $derived(!!(onPlayNow || onPlayTrackOnly || onPlayFromHere || onPlayNext || onPlayLater || onCreateRadio)); - const hasLibrary = $derived(!!(onAddFavorite || onAddToPlaylist || onRemoveFromPlaylist)); + const hasLibrary = $derived(!!(onAddFavorite || onAddToPlaylist || onRemoveFromPlaylist || onFindReplacement)); const hasShare = $derived(!!(onShareQobuz || onShareSonglink)); const hasDownload = $derived(!!onDownload || isTrackDownloaded); const hasNav = $derived(!!(onGoToAlbum || onGoToArtist || onShowInfo)); @@ -414,6 +417,12 @@ Remove from playlist {/if} + {#if onFindReplacement} + + {/if} {/if} {#if hasLibrary && (hasShare || hasNav)} diff --git a/src/lib/components/TrackReplacementModal.svelte b/src/lib/components/TrackReplacementModal.svelte new file mode 100644 index 000000000..23446c39f --- /dev/null +++ b/src/lib/components/TrackReplacementModal.svelte @@ -0,0 +1,588 @@ + + +{#if isOpen} +
+{/if} + + diff --git a/src/lib/components/views/PlaylistDetailView.svelte b/src/lib/components/views/PlaylistDetailView.svelte index c5bd9cebb..4e14f9e01 100644 --- a/src/lib/components/views/PlaylistDetailView.svelte +++ b/src/lib/components/views/PlaylistDetailView.svelte @@ -3,6 +3,7 @@ import AlbumMenu from '../AlbumMenu.svelte'; import PlaylistCollage from '../PlaylistCollage.svelte'; import PlaylistModal from '../PlaylistModal.svelte'; + import TrackReplacementModal from '../TrackReplacementModal.svelte'; import ViewTransition from '../ViewTransition.svelte'; import { writeText } from '@tauri-apps/plugin-clipboard-manager'; import { invoke } from '@tauri-apps/api/core'; @@ -17,7 +18,8 @@ type OfflineStatus } from '$lib/stores/offlineStore'; import { consumeContextTrackFocus, setPlaybackContext } from '$lib/stores/playbackContextStore'; - import { isTrackUnavailable, subscribe as subscribeUnavailable } from '$lib/stores/unavailableTracksStore'; + import { isTrackUnavailable, clearTrackUnavailable, subscribe as subscribeUnavailable } from '$lib/stores/unavailableTracksStore'; + import { showToast } from '$lib/stores/toastStore'; import { t } from '$lib/i18n'; import { onMount, tick } from 'svelte'; @@ -250,6 +252,10 @@ let isCopying = $state(false); let isCopied = $state(false); + // Track replacement modal state + let replacementModalOpen = $state(false); + let trackToReplace = $state