feat(download): add download button to playlist view and album download button
- Add download functionality to PlaylistDetailView (track-by-track) - Add handleDisplayTrackDownload handler for playlist tracks - Add "Download Album" button to AlbumDetailView header actions - handleDownloadAlbum queues all non-downloaded tracks for download
This commit is contained in:
1 parent
97951b2189
commit
6de37c296b
3 files changed
+83
-3
No files matched your search
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { ArrowLeft, Play, Shuffle, Heart, Plus, MoreHorizontal } from 'lucide-svelte';
|
||||
import { ArrowLeft, Play, Shuffle, Heart, Plus, MoreHorizontal, Download } from 'lucide-svelte';
|
||||
import TrackRow from '../TrackRow.svelte';
|
||||
import { getDownloadState, type DownloadStatus } from '$lib/stores/downloadState';
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
onTrackDownload?: (track: Track) => void;
|
||||
onTrackRemoveDownload?: (trackId: number) => void;
|
||||
getTrackDownloadStatus?: (trackId: number) => { status: DownloadStatus; progress: number };
|
||||
onDownloadAlbum?: () => void;
|
||||
}
|
||||
|
||||
let {
|
||||
@@ -71,7 +72,8 @@
|
||||
onAddTrackToPlaylist,
|
||||
onTrackDownload,
|
||||
onTrackRemoveDownload,
|
||||
getTrackDownloadStatus
|
||||
getTrackDownloadStatus,
|
||||
onDownloadAlbum
|
||||
}: Props = $props();
|
||||
|
||||
let currentTrack = $state<number | null>(null);
|
||||
@@ -186,6 +188,11 @@
|
||||
<button class="icon-btn" onclick={onAddToQueue}>
|
||||
<Plus size={20} color="white" />
|
||||
</button>
|
||||
{#if onDownloadAlbum}
|
||||
<button class="icon-btn" onclick={onDownloadAlbum} title="Download album">
|
||||
<Download size={20} color="white" />
|
||||
</button>
|
||||
{/if}
|
||||
<button class="icon-btn">
|
||||
<MoreHorizontal size={20} color="white" />
|
||||
</button>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import { open } from '@tauri-apps/plugin-dialog';
|
||||
import { onMount } from 'svelte';
|
||||
import TrackRow from '../TrackRow.svelte';
|
||||
import { type DownloadStatus } from '$lib/stores/downloadState';
|
||||
|
||||
interface PlaylistTrack {
|
||||
id: number;
|
||||
@@ -71,6 +72,9 @@
|
||||
onTrackShareSonglink?: (track: DisplayTrack) => void;
|
||||
onTrackGoToAlbum?: (albumId: string) => void;
|
||||
onTrackGoToArtist?: (artistId: number) => void;
|
||||
onTrackDownload?: (track: DisplayTrack) => void;
|
||||
onTrackRemoveDownload?: (trackId: number) => void;
|
||||
getTrackDownloadStatus?: (trackId: number) => { status: DownloadStatus; progress: number };
|
||||
}
|
||||
|
||||
let {
|
||||
@@ -84,7 +88,10 @@
|
||||
onTrackShareQobuz,
|
||||
onTrackShareSonglink,
|
||||
onTrackGoToAlbum,
|
||||
onTrackGoToArtist
|
||||
onTrackGoToArtist,
|
||||
onTrackDownload,
|
||||
onTrackRemoveDownload,
|
||||
getTrackDownloadStatus
|
||||
}: Props = $props();
|
||||
|
||||
let playlist = $state<Playlist | null>(null);
|
||||
@@ -494,6 +501,7 @@
|
||||
</div>
|
||||
|
||||
{#each displayTracks as track, idx (track.id)}
|
||||
{@const downloadInfo = getTrackDownloadStatus?.(track.id) ?? { status: 'none' as const, progress: 0 }}
|
||||
<TrackRow
|
||||
number={idx + 1}
|
||||
title={track.title}
|
||||
@@ -501,7 +509,11 @@
|
||||
duration={track.duration}
|
||||
quality={track.hires ? 'Hi-Res' : undefined}
|
||||
isFavorite={favoriteTrackIds.has(track.id)}
|
||||
downloadStatus={downloadInfo.status}
|
||||
downloadProgress={downloadInfo.progress}
|
||||
onPlay={() => handleTrackClick(track)}
|
||||
onDownload={onTrackDownload ? () => onTrackDownload(track) : undefined}
|
||||
onRemoveDownload={onTrackRemoveDownload ? () => onTrackRemoveDownload(track.id) : undefined}
|
||||
menuActions={{
|
||||
onPlayNow: () => handleTrackClick(track),
|
||||
onPlayNext: onTrackPlayNext ? () => onTrackPlayNext(track) : undefined,
|
||||
|
||||
@@ -1004,12 +1004,69 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDownloadAlbum() {
|
||||
if (!selectedAlbum) return;
|
||||
|
||||
const tracksToDownload = selectedAlbum.tracks.filter(track => {
|
||||
const status = getDownloadState(track.id).status;
|
||||
return status === 'none' || status === 'failed';
|
||||
});
|
||||
|
||||
if (tracksToDownload.length === 0) {
|
||||
showToast('All tracks already downloaded', 'info');
|
||||
return;
|
||||
}
|
||||
|
||||
showToast(`Downloading ${tracksToDownload.length} tracks from "${selectedAlbum.title}"`, 'info');
|
||||
|
||||
for (const track of tracksToDownload) {
|
||||
try {
|
||||
await downloadTrack({
|
||||
id: track.id,
|
||||
title: track.title,
|
||||
artist: track.artist || selectedAlbum.artist || 'Unknown',
|
||||
album: selectedAlbum.title,
|
||||
albumId: selectedAlbum.id,
|
||||
durationSecs: track.durationSeconds,
|
||||
quality: track.quality || 'CD Quality',
|
||||
bitDepth: track.bitDepth,
|
||||
sampleRate: track.samplingRate,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(`Failed to queue download for "${track.title}":`, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getTrackDownloadStatus(trackId: number) {
|
||||
// Access downloadStateVersion to trigger reactivity
|
||||
void downloadStateVersion;
|
||||
return getDownloadState(trackId);
|
||||
}
|
||||
|
||||
async function handleDisplayTrackDownload(track: PlaylistTrack) {
|
||||
try {
|
||||
const quality = track.hires && track.bitDepth && track.samplingRate
|
||||
? `${track.bitDepth}bit/${track.samplingRate}kHz`
|
||||
: 'CD Quality';
|
||||
await downloadTrack({
|
||||
id: track.id,
|
||||
title: track.title,
|
||||
artist: track.artist || 'Unknown',
|
||||
album: track.album,
|
||||
albumId: track.albumId,
|
||||
durationSecs: track.durationSeconds,
|
||||
quality,
|
||||
bitDepth: track.bitDepth,
|
||||
sampleRate: track.samplingRate,
|
||||
});
|
||||
showToast(`Downloading "${track.title}"`, 'info');
|
||||
} catch (err) {
|
||||
console.error('Failed to start download:', err);
|
||||
showToast('Failed to start download', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function handleDisplayTrackPlayNext(track: PlaylistTrack) {
|
||||
queueTrackNext(buildQueueTrackFromDisplayTrack(track));
|
||||
}
|
||||
@@ -1532,6 +1589,7 @@
|
||||
onTrackDownload={handleTrackDownload}
|
||||
onTrackRemoveDownload={handleTrackRemoveDownload}
|
||||
getTrackDownloadStatus={getTrackDownloadStatus}
|
||||
onDownloadAlbum={handleDownloadAlbum}
|
||||
/>
|
||||
{:else if activeView === 'artist' && selectedArtist}
|
||||
<ArtistDetailView
|
||||
@@ -1568,6 +1626,9 @@
|
||||
onTrackShareSonglink={(track) => shareSonglinkTrack(track.id, track.isrc)}
|
||||
onTrackGoToAlbum={handleAlbumClick}
|
||||
onTrackGoToArtist={handleArtistClick}
|
||||
onTrackDownload={handleDisplayTrackDownload}
|
||||
onTrackRemoveDownload={handleTrackRemoveDownload}
|
||||
getTrackDownloadStatus={getTrackDownloadStatus}
|
||||
/>
|
||||
{:else if activeView === 'favorites'}
|
||||
<FavoritesView
|
||||
|
||||
Reference in new issue
Block a user