import { type Accessor, batch, createMemo, createSignal, For, type Setter, Show } from "solid-js"; import "./DownloadManager.css"; import "./Dialog.css"; import "toastify-js/src/toastify.css"; import { type DownloadStatus, downloadStatusMap, type Progress, setDownloadStatusMap } from "../offline"; import { formatBytes } from "../utils"; import { Modal } from "./Modal"; import { ClearSVG, RefreshSVG, StopSVG } from "./SVG"; interface DownloadManagerProps { showDownloadManager: Accessor; setShowDownloadManager: Setter; } export default function DownloadManager(props: DownloadManagerProps) { const downloadKeys = createMemo(() => { const keys = Object.keys(downloadStatusMap); const progressKeys = keys.filter((key) => isProgress(downloadStatusMap[key])); // Sort the keys: Running first (by path), then Failed (by path), then Aborted (by path) progressKeys.sort((pathA, pathB) => { const statusA = downloadStatusMap[pathA] as Progress; const statusB = downloadStatusMap[pathB] as Progress; // 0: Running, 1: Failed, 2: Aborted const typeA = typeof statusA.status === "number" ? (statusA.status > 0 ? 0 : 3) : statusA.status === "Failed" ? 1 : 2; const typeB = typeof statusB.status === "number" ? (statusB.status > 0 ? 0 : 3) : statusB.status === "Failed" ? 1 : 2; if (typeA !== typeB) { return typeA - typeB; // Sort by type first } // If types are the same, sort by path numerically/alphabetically return pathA.localeCompare(pathB, undefined, { numeric: true }); }); return progressKeys; }); function isProgress(status: DownloadStatus | undefined): status is Progress { return status !== undefined && typeof status === "object" && "status" in status; } const counts = createMemo(() => { const counted = { downloading: 0, aborted: 0, failed: 0 }; for (const key of downloadKeys()) { const status = downloadStatusMap[key]; if (!isProgress(status)) continue; if (typeof status.status === "number") counted.downloading++; else if (status.status === "Aborted") counted.aborted++; else if (status.status === "Failed") counted.failed++; } return counted; }); return ( {(close) => (
Downloads

In progress: {counts().downloading} Aborted: {counts().aborted} Failed: {counts().failed}
{(path) => { const [showReason, setShowReason] = createSignal(false); //an accessor, not a snapshot: progress updates replace the whole value object while //keeps this row alive, so reading it once would freeze the byte counter and the status styling const status = () => downloadStatusMap[path] as Progress | undefined; return ( {(currentStatus) => (
{path}
{typeof currentStatus().status === "number" ? ( <> {formatBytes(currentStatus().status as number)} ) : ( <> {(currentStatus().status === "Failed" && ( )) || "Aborted"} )}
{(() => { const reasonStatus = currentStatus(); return "reason" in reasonStatus && reasonStatus.reason; })()}
)}
); }}
)}
); }