Files
jellytau/src/lib/components/library/DownloadButton.svelte
T
dtourolle ad48d89dfe chore(format): run prettier over src/ and scripts/
Formatting was configured but never enforced: `bun run format:check`
reported 199 unformatted files and ran in no workflow and in no git hook,
so .prettierrc (printWidth 100, trailing commas) described an intention
rather than the tree.

This is the one-time sweep that makes the check gateable. Whitespace and
token-reflow only -- no behavioural change: `bun run check` reports 0
errors and all 1053 frontend tests pass before and after.

Kept out of every other commit on purpose. A 199-file diff mixed with
real changes is unreviewable, and the next commit turns format:check
into a hard CI gate so this cannot silently accumulate again.
2026-08-21 17:41:44 +02:00

149 lines
4.1 KiB
Svelte

<script lang="ts">
import { downloads } from "$lib/stores/downloads";
import { auth } from "$lib/stores/auth";
import { commands } from "$lib/api/bindings";
import DownloadButtonCore from "./DownloadButtonCore.svelte";
import type { DownloadState } from "./DownloadButtonCore.svelte";
import { createLogger } from "$lib/utils/logger";
const log = createLogger("DownloadButton");
/**
* Single audio track download button
* @req: UR-011 - Download for offline playback
* @req: UR-018 - Download entire albums or playlists
* @req: DR-018 - Download buttons on library/album/player screens
*/
interface Props {
itemId: string;
itemName?: string;
artistName?: string;
albumName?: string;
size?: "sm" | "md" | "lg";
className?: string;
}
let {
itemId,
itemName = "",
artistName = "",
albumName = "",
size = "md",
className = "",
}: Props = $props();
let isProcessing = $state(false);
// Find download for this item
const downloadInfo = $derived(
Object.values($downloads.downloads).find((d) => d.itemId === itemId),
);
const status = $derived(downloadInfo?.status || "not_downloaded");
const progress = $derived(downloadInfo?.progress || 0);
const buttonState = $derived<DownloadState>({
status: (status as DownloadState["status"]) || "not_downloaded",
progress: progress || 0,
});
async function handleClick() {
log.debug("🖱️ Download button clicked! Current status:", status);
if (isProcessing) return;
isProcessing = true;
try {
if (status === "completed") {
// Delete download
if (downloadInfo?.id) {
await downloads.delete(downloadInfo.id);
}
} else if (status === "downloading" || status === "pending") {
// Cancel download
if (downloadInfo?.id) {
await downloads.cancel(downloadInfo.id);
}
} else if (status === "failed") {
// Retry failed download
if (downloadInfo?.id) {
await downloads.resume(downloadInfo.id);
}
} else {
// Start download
const userId = $auth.user?.id;
if (!userId) {
log.error("No user ID found");
return;
}
try {
const repo = auth.getRepository();
log.debug("🎯 Starting download for item:", itemId);
// Get stream URL
const streamUrl = await repo.getAudioStreamUrl(itemId);
log.debug(" Stream URL obtained:", streamUrl?.substring(0, 50) + "...");
if (!streamUrl) {
throw new Error("Failed to get stream URL");
}
// Get target directory
const targetDir = await commands.storageGetPath();
log.debug(" Target directory:", targetDir);
// Queue and start download in single atomic operation
const downloadId = await commands.downloadItemAndStart({
itemId,
userId,
streamUrl,
targetDir,
itemName: itemName || null,
artistName: artistName || null,
albumName: albumName || null,
});
log.debug(" Download queued and started with ID:", downloadId);
// Refresh downloads list
await downloads.refresh(userId);
} catch (e) {
log.error("❌ Failed to start download:", e);
}
}
} catch (error) {
log.error("Download operation failed:", error);
} finally {
isProcessing = false;
}
}
function getTitle(): string {
switch (status) {
case "completed":
return "Downloaded - Click to remove";
case "downloading":
return `Downloading... ${Math.round(progress * 100)}%`;
case "pending":
return "Queued for download";
case "paused":
return "Download paused";
case "failed":
return "Download failed - Click to retry";
default:
return "Download for offline playback";
}
}
</script>
<div class="p-2 rounded-full">
<DownloadButtonCore
{size}
state={buttonState}
title={getTitle()}
onClick={handleClick}
{isProcessing}
{className}
/>
</div>