DownloadManager.tsx
⎇
Raw
1import { createEffect, createSignal, For, onMount, Show, untrack, type Accessor, type Setter } from "solid-js";
2import "./DownloadManager.css";
3import "./Dialog.css";
4import "toastify-js/src/toastify.css";
5import { formatBytes } from "../utils";
6import { ClearSVG, RefreshSVG, StopSVG } from "./SVG";
7import { downloadStatusMap, setDownloadStatusMap, type Progress } from "../offline";
8import { createStore } from "solid-js/store";
9
10interface DownloadManagerProps {
11 showDownloadManager: Accessor<boolean>;
12 setShowDownloadManager: Setter<boolean>;
13}
14
15export default function DownloadManager(props: DownloadManagerProps) {
16 let dialogElement!: HTMLDialogElement;
17
18 createEffect(() => {
19 if (props.showDownloadManager()) {
20 dialogElement.showModal();
21 }
22 });
23
24 function handleClose() {
25 dialogElement.close();
26 props.setShowDownloadManager(false);
27 }
28
29 const [downloadItems, setDownloadItems] = createStore<Record<string, Progress | undefined>>({});
30
31 createEffect(() => {
32 const paths = Object.keys(downloadStatusMap);
33 untrack(() => {
34 // Update the downloadItems store with the current status of each path
35 for (const path of paths) {
36 const status = downloadStatusMap[path];
37 if (typeof status === "object" && "status" in status) setDownloadItems(path, status);
38 else setDownloadItems(path, undefined);
39 }
40 // Remove any paths that no longer exist in downloadStatusMap
41 for (const path of Object.keys(downloadItems)) {
42 if (!Object.keys(downloadStatusMap).includes(path)) {
43 setDownloadItems(path, undefined);
44 }
45 }
46 });
47 });
48
49 return (
50 <dialog
51 style={{ height: "90%" }}
52 ref={dialogElement}
53 onClose={handleClose}
54 onclick={(e) => e.target.nodeName === "DIALOG" && handleClose()}
55 >
56 <div style={{ height: "100%" }}>
57 <div class="downloadmanager-header title">
58 Download
59 <div class="spacer" />
60 <button
61 title="Retry all failed/aborted downloads"
62 onClick={() => {
63 for (const status of Object.values(downloadItems)) {
64 if (status && "retry" in status) status.retry();
65 }
66 }}
67 >
68 <RefreshSVG />
69 </button>
70 <button
71 title="stop all running downloads"
72 onClick={() => {
73 for (const status of Object.values(downloadItems)) {
74 if (status && typeof status.status === "number") status.abort();
75 }
76 }}
77 >
78 <StopSVG />
79 </button>
80 <button
81 title="Clear all failed/aborted downloads"
82 onClick={() => {
83 for (const [path, status] of Object.entries(downloadItems)) {
84 if (status && (status.status === "Aborted" || status.status === "Failed"))
85 setDownloadStatusMap(path, undefined);
86 }
87 }}
88 >
89 Clear
90 </button>
91 <button title="Close dialog" onClick={handleClose}>
92 <ClearSVG />
93 </button>
94 </div>
95 <hr />
96 <div class="downloadmanager-content">
97 <For each={Object.entries(downloadItems)}>
98 {([path, status]) => {
99 if (!status) return;
100 const [showReason, setShowReason] = createSignal(false);
101 return (
102 <div
103 classList={{
104 "downloadmanager-item": true,
105 "downloadmanager-item-failed": status.status === "Failed",
106 "downloadmanager-item-aborted": status.status === "Aborted",
107 }}
108 >
109 <span>{path}</span>
110 <hr />
111 <div class="downloadmanager-buttons">
112 {status && typeof status.status === "number" ? (
113 <>
114 <span>{formatBytes(status.status)}</span>
115 <button title="Stop Download" onClick={() => status.abort()}>
116 <StopSVG />
117 </button>
118 </>
119 ) : (
120 <>
121 {(status.status === "Failed" && (
122 <button title="Show reason" onclick={() => setShowReason(!showReason())}>
123 Reason
124 </button>
125 )) ||
126 "Aborted"}
127 <button title="Retry download" onClick={() => status.retry()}>
128 <RefreshSVG />
129 </button>
130 <button title="Clear Download" onClick={() => setDownloadStatusMap(path, undefined)}>
131 <ClearSVG />
132 </button>
133 </>
134 )}
135 </div>
136 <Show when={showReason()}>{"reason" in status && status.reason}</Show>
137 </div>
138 );
139 }}
140 </For>
141 </div>
142 </div>
143 </dialog>
144 );
145}
146