feat: format-aware download status in album detail view
Download checkmarks now only show for the currently selected format. In-memory store tracks formatId per album download, and the album detail view checks track.downloaded_format_ids from backend against the selected format dropdown. Switching quality hides checkmarks from a different format's downloads.
This commit is contained in:
4 files changed
+31
-8
No files matched your search
@@ -8,7 +8,7 @@
|
||||
import Dropdown from '../Dropdown.svelte';
|
||||
import ViewTransition from '../ViewTransition.svelte';
|
||||
import { getAlbumDetail, getFormats } from '$lib/services/purchases';
|
||||
import { purchaseDownloads, startAlbumDownload, startTrackDownload } from '$lib/stores/purchaseDownloadStore';
|
||||
import { purchaseDownloads, startAlbumDownload, startTrackDownload, getAlbumDownloadFormatId } from '$lib/stores/purchaseDownloadStore';
|
||||
import type { TrackDownloadStatus } from '$lib/stores/purchaseDownloadStore';
|
||||
import { formatDuration, getQobuzImage } from '$lib/adapters/qobuzAdapters';
|
||||
import { showToast } from '$lib/stores/toastStore';
|
||||
@@ -45,7 +45,11 @@
|
||||
const albumDlState = $derived($purchaseDownloads[albumId] ?? null);
|
||||
const downloadStatuses = $derived(albumDlState?.trackStatuses ?? {});
|
||||
const isDownloadingAll = $derived(albumDlState?.isDownloadingAll ?? false);
|
||||
const allComplete = $derived(albumDlState?.allComplete ?? false);
|
||||
// Only show "all complete" if the download was for the currently selected format
|
||||
const allComplete = $derived(
|
||||
(albumDlState?.allComplete ?? false) &&
|
||||
(albumDlState?.formatId === undefined || albumDlState?.formatId === selectedFormatId)
|
||||
);
|
||||
const downloadDestination = $derived(albumDlState?.destination ?? null);
|
||||
|
||||
let addingToLibrary = $state(false);
|
||||
@@ -92,7 +96,14 @@
|
||||
}
|
||||
|
||||
function getTrackStatus(trackId: number): TrackDownloadStatus | null {
|
||||
return downloadStatuses[trackId] || null;
|
||||
const status = downloadStatuses[trackId] || null;
|
||||
if (!status) return null;
|
||||
// Only show 'complete' if the in-memory download was for the currently selected format
|
||||
if (status === 'complete') {
|
||||
const dlFormatId = getAlbumDownloadFormatId(albumId);
|
||||
if (dlFormatId !== undefined && dlFormatId !== selectedFormatId) return null;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
function groupByDisc(trackList: PurchasedTrack[]): Map<number, PurchasedTrack[]> {
|
||||
@@ -358,7 +369,8 @@
|
||||
{#each discTracks as track (track.id)}
|
||||
{@const status = getTrackStatus(track.id)}
|
||||
{@const isActive = activeTrackId === track.id}
|
||||
{@const isDownloaded = track.downloaded || status === 'complete'}
|
||||
{@const isDownloadedForFormat = selectedFormatId !== null && (track.downloaded_format_ids ?? []).includes(selectedFormatId)}
|
||||
{@const isDownloaded = isDownloadedForFormat || status === 'complete'}
|
||||
<div
|
||||
class="track-row"
|
||||
class:downloading={status === 'downloading'}
|
||||
|
||||
@@ -139,12 +139,14 @@ export async function getDownloadedTrackIds(): Promise<Set<number>> {
|
||||
export async function markTrackDownloaded(
|
||||
trackId: number,
|
||||
albumId: string | undefined,
|
||||
filePath: string
|
||||
filePath: string,
|
||||
formatId: number = 0
|
||||
): Promise<void> {
|
||||
return invoke<void>('v2_purchases_mark_downloaded', {
|
||||
trackId,
|
||||
albumId: albumId ?? null,
|
||||
filePath
|
||||
filePath,
|
||||
formatId
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ interface AlbumDownloadState {
|
||||
isDownloadingAll: boolean;
|
||||
allComplete: boolean;
|
||||
destination?: string;
|
||||
formatId?: number;
|
||||
}
|
||||
|
||||
// Persistent store: albumId -> download state (survives component unmount)
|
||||
@@ -38,6 +39,11 @@ function updateAlbumState(
|
||||
});
|
||||
}
|
||||
|
||||
/** Get the format ID that was used for the most recent download of this album. */
|
||||
export function getAlbumDownloadFormatId(albumId: string): number | undefined {
|
||||
return get(purchaseDownloads)[albumId]?.formatId;
|
||||
}
|
||||
|
||||
export function startAlbumDownload(
|
||||
albumId: string,
|
||||
trackIds: number[],
|
||||
@@ -50,6 +56,7 @@ export function startAlbumDownload(
|
||||
isDownloadingAll: true,
|
||||
allComplete: false,
|
||||
destination,
|
||||
formatId,
|
||||
}));
|
||||
executeAlbumDownload(albumId, trackIds, formatId, destination, qualityDir);
|
||||
}
|
||||
@@ -73,7 +80,7 @@ async function executeAlbumDownload(
|
||||
...state,
|
||||
trackStatuses: { ...state.trackStatuses, [trackId]: 'complete' },
|
||||
}));
|
||||
await markTrackDownloaded(trackId, albumId, filePath).catch(() => {});
|
||||
await markTrackDownloaded(trackId, albumId, filePath, formatId).catch(() => {});
|
||||
} catch {
|
||||
updateAlbumState(albumId, (state) => ({
|
||||
...state,
|
||||
@@ -106,6 +113,7 @@ export function startTrackDownload(
|
||||
updateAlbumState(albumId, (state) => ({
|
||||
...state,
|
||||
trackStatuses: { ...state.trackStatuses, [trackId]: 'downloading' },
|
||||
formatId,
|
||||
}));
|
||||
executeSingleTrackDownload(albumId, trackId, formatId, destination, qualityDir);
|
||||
}
|
||||
@@ -123,7 +131,7 @@ async function executeSingleTrackDownload(
|
||||
...state,
|
||||
trackStatuses: { ...state.trackStatuses, [trackId]: 'complete' },
|
||||
}));
|
||||
await markTrackDownloaded(trackId, albumId, filePath).catch(() => {});
|
||||
await markTrackDownloaded(trackId, albumId, filePath, formatId).catch(() => {});
|
||||
} catch {
|
||||
updateAlbumState(albumId, (state) => ({
|
||||
...state,
|
||||
|
||||
@@ -51,6 +51,7 @@ export interface PurchasedTrack {
|
||||
maximum_sampling_rate?: number;
|
||||
streamable: boolean;
|
||||
downloaded?: boolean;
|
||||
downloaded_format_ids?: number[];
|
||||
purchased_at?: number;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user