import { useState, useCallback } from "react"; export function useDownloadQueue(maxConcurrent = 3) { const [queue, setQueue] = useState([]); // queued tasks const [activeCount, setActiveCount] = useState(0); const [statuses, setStatuses] = useState({}); // { id: "idle"|"queued"|"downloading"|"done"|"failed" } const scheduleNext = useCallback(() => { setQueue((queueCurr) => { if (activeCount >= maxConcurrent || queueCurr.length === 0) return queueCurr; const [task, ...rest] = queueCurr; setStatuses((s) => ({ ...s, [task.id]: "downloading" })); setActiveCount((c) => c + 1); // Run the download fully in async IIFE (async () => { try { await task.downloadFn(); // triggers fetch + blob + click setStatuses((s) => ({ ...s, [task.id]: "done" })); } catch (err) { console.error(err); setStatuses((s) => ({ ...s, [task.id]: "failed" })); } finally { setActiveCount((c) => c - 1); scheduleNext(); // promote next in queue } })(); return rest; }); }, [activeCount, maxConcurrent]); const startDownload = useCallback( (id, downloadFn) => { setStatuses((s) => ({ ...s, [id]: "queued" })); setQueue((curr) => [...curr, { id, downloadFn }]); scheduleNext(); }, [scheduleNext] ); const getStatus = useCallback((id) => statuses[id] || "idle", [statuses]); return { startDownload, getStatus }; }