First working POC

This commit is contained in:
2026-01-26 22:21:54 +01:00
commit cfddc1edea
255 changed files with 77606 additions and 0 deletions
@@ -0,0 +1,131 @@
<script lang="ts">
import { downloads } from "$lib/stores/downloads";
import { auth } from "$lib/stores/auth";
import { invoke } from "@tauri-apps/api/core";
import DownloadButtonCore from "./DownloadButtonCore.svelte";
import type { DownloadState } from "./DownloadButtonCore.svelte";
/**
* 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() {
console.log("🖱️ 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) {
console.error("No user ID found");
return;
}
try {
const repo = auth.getRepository();
console.log("🎯 Starting download for item:", itemId);
// Get stream URL
const streamUrl = await repo.getAudioStreamUrl(itemId);
console.log(" Stream URL obtained:", streamUrl?.substring(0, 50) + "...");
if (!streamUrl) {
throw new Error("Failed to get stream URL");
}
// Get target directory
const targetDir = await invoke<string>("storage_get_path");
console.log(" Target directory:", targetDir);
// Queue and start download in single atomic operation
const downloadId = await invoke<number>("download_item_and_start", {
itemId,
userId,
streamUrl,
targetDir,
itemName: itemName || undefined,
artistName: artistName || undefined,
albumName: albumName || undefined,
});
console.log(" Download queued and started with ID:", downloadId);
// Refresh downloads list
await downloads.refresh(userId);
} catch (e) {
console.error("❌ Failed to start download:", e);
}
}
} catch (error) {
console.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>