Skip to content

Commit d4e65a1

Browse files
committed
[frontend] add pause and cancel button to download popup entry
1 parent 898d3f6 commit d4e65a1

File tree

2 files changed

+120
-12
lines changed

2 files changed

+120
-12
lines changed

frontend/src/Browser.tsx

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createState, type Stateful } from "dreamland/core";
1+
import { createState, type Delegate, type Stateful } from "dreamland/core";
22
import { StatefulClass } from "./StatefulClass";
33
import { Tab, type SerializedTab } from "./Tab";
44
import { createDelegate } from "dreamland/core";
@@ -67,6 +67,13 @@ export type DownloadEntry = {
6767
timestamp: number;
6868
size: number;
6969
id: string;
70+
cancelled: boolean;
71+
72+
progress?: number;
73+
progressbytes?: number;
74+
paused?: boolean;
75+
cancel?: Delegate<void>;
76+
pause?: Delegate<void>;
7077
};
7178

7279
export type Settings = {
@@ -83,8 +90,8 @@ export class Browser extends StatefulClass {
8390
globalhistory: HistoryState[] = [];
8491
bookmarks: BookmarkEntry[] = [];
8592

86-
sessionDownloadHistory: DownloadEntry[] = [];
87-
globalDownloadHistory: DownloadEntry[] = [];
93+
sessionDownloadHistory: Stateful<DownloadEntry>[] = [];
94+
globalDownloadHistory: Stateful<DownloadEntry>[] = [];
8895

8996
unfocusframes: boolean = false;
9097

@@ -118,27 +125,43 @@ export class Browser extends StatefulClass {
118125
url.hostname.replaceAll(".", "-");
119126
}
120127

121-
let entry: DownloadEntry = {
128+
let cancel = createDelegate<void>();
129+
let pause = createDelegate<void>();
130+
131+
let entry: Stateful<DownloadEntry> = createState({
122132
filename,
123133
url: download.url,
124134
size: download.length,
125135
timestamp: Date.now(),
126136
id: crypto.randomUUID(),
127-
};
137+
cancelled: false,
138+
139+
progress: 0,
140+
progressbytes: 0,
141+
paused: false,
142+
cancel,
143+
pause,
144+
});
128145
this.globalDownloadHistory = [entry, ...this.globalDownloadHistory];
129146
this.sessionDownloadHistory = [entry, ...this.sessionDownloadHistory];
130147

131148
await download.body.pipeTo(
132149
new WritableStream({
133150
write(chunk) {
134151
downloaded += chunk.byteLength;
135-
browser.downloadProgress = Math.min(
152+
entry.progressbytes = downloaded;
153+
browser.downloadProgress = entry.progress = Math.min(
136154
downloaded / download.length + 0.1,
137155
1
138156
);
139157
},
140158
})
141159
);
160+
entry.cancel = undefined;
161+
entry.pause = undefined;
162+
entry.progress = undefined;
163+
entry.progressbytes = undefined;
164+
entry.paused = false;
142165
showDownloadsPopup();
143166
setTimeout(() => {
144167
this.downloadProgress = 0;
@@ -168,7 +191,7 @@ export class Browser extends StatefulClass {
168191
}
169192
this.activetab = this.tabs[0];
170193
this.bookmarks = de.bookmarks;
171-
this.globalDownloadHistory = de.globalDownloadHistory;
194+
this.globalDownloadHistory = de.globalDownloadHistory.map(createState);
172195
this.settings = createState(de.settings);
173196
// this.activetab = this.tabs.find((t) => t.id == de.activetab)!;
174197
}

frontend/src/components/Omnibox.tsx

Lines changed: 90 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import iconRefresh from "@ktibow/iconset-ion/refresh";
55
import iconExtension from "@ktibow/iconset-ion/extension-puzzle-outline";
66
import iconDownload from "@ktibow/iconset-ion/download-outline";
77
import iconMore from "@ktibow/iconset-ion/more";
8+
import iconPause from "@ktibow/iconset-ion/pause-outline";
89
import iconClose from "@ktibow/iconset-ion/close";
10+
import iconFolder from "@ktibow/iconset-ion/folder-outline";
911
import iconOpen from "@ktibow/iconset-ion/open-outline";
1012
import {
1113
closeMenu,
@@ -122,7 +124,7 @@ const DownloadsPopup: Component<{}> = function (cx) {
122124
<div>
123125
<div class="title">
124126
<span>Recent Downloads</span>
125-
<div class="iconcontainer">
127+
<div class="buttoniconcontainer">
126128
<button
127129
on:click={() => {
128130
closeMenu();
@@ -140,7 +142,52 @@ const DownloadsPopup: Component<{}> = function (cx) {
140142
</div>
141143
<div class="contents">
142144
<span>{b.filename}</span>
143-
<span class="data">{formatBytes(b.size)}</span>
145+
{use(b.progressbytes).andThen(
146+
<span class="data">
147+
{use(b.progressbytes).map((s) => formatBytes(s!))}/
148+
{formatBytes(b.size)}
149+
</span>
150+
)}
151+
{use(b.progressbytes)
152+
.map((b) => !b)
153+
.andThen(<span class="data">{formatBytes(b.size)}</span>)}
154+
{use(b.progress).andThen(
155+
<progress value={use(b.progress).map((p) => p || 0)} max="1">
156+
50%
157+
</progress>
158+
)}
159+
</div>
160+
<div class="buttoniconcontainer">
161+
{use(b.progress)
162+
.map((b) => !b)
163+
.andThen(
164+
<>
165+
<button>
166+
<Icon icon={iconFolder}></Icon>
167+
</button>
168+
<button>
169+
<Icon icon={iconOpen}></Icon>
170+
</button>
171+
</>
172+
)}
173+
{use(b.progress).andThen(
174+
<>
175+
<button
176+
on:click={() => {
177+
b.pause!();
178+
}}
179+
>
180+
<Icon icon={iconPause}></Icon>
181+
</button>
182+
<button
183+
on:click={() => {
184+
b.cancel!();
185+
}}
186+
>
187+
<Icon icon={iconClose}></Icon>
188+
</button>
189+
</>
190+
)}
144191
</div>
145192
</div>
146193
))}
@@ -153,7 +200,7 @@ const DownloadsPopup: Component<{}> = function (cx) {
153200
}}
154201
>
155202
<span>Full Download History</span>
156-
<div class="iconcontainer">
203+
<div class="buttoniconcontainer">
157204
<Icon icon={iconOpen}></Icon>
158205
</div>
159206
</div>
@@ -165,6 +212,7 @@ DownloadsPopup.style = css`
165212
width: 20em;
166213
display: flex;
167214
flex-direction: column;
215+
user-select: none;
168216
}
169217
170218
.title {
@@ -198,7 +246,7 @@ DownloadsPopup.style = css`
198246
max-height: 30em;
199247
display: flex;
200248
flex-direction: column;
201-
overflow-y: scroll;
249+
overflow-y: auto;
202250
overflow-x: hidden;
203251
}
204252
@@ -207,15 +255,52 @@ DownloadsPopup.style = css`
207255
display: flex;
208256
gap: 1em;
209257
font-size: 0.9em;
258+
position: relative;
210259
}
211260
.entry:hover {
212261
background: var(--bg20);
213262
}
214263
.contents {
215264
display: flex;
265+
overflow: hidden;
216266
flex-direction: column;
217267
gap: 0.5em;
218268
}
269+
.entry .buttoniconcontainer {
270+
display: none;
271+
}
272+
.entry:hover .buttoniconcontainer {
273+
display: flex;
274+
}
275+
.entry .buttoniconcontainer {
276+
position: absolute;
277+
right: 0;
278+
top: 0;
279+
padding: 1em;
280+
background: var(--bg20);
281+
height: 100%;
282+
align-items: start;
283+
gap: 1em;
284+
}
285+
.entry .buttoniconcontainer button {
286+
font-size: 1.15em;
287+
position: relative;
288+
z-index: 1;
289+
display: flex;
290+
}
291+
.entry .buttoniconcontainer button:hover::before {
292+
content: "";
293+
z-index: -1;
294+
position: absolute;
295+
width: 150%;
296+
height: 150%;
297+
top: 50%;
298+
left: 50%;
299+
transform: translate(-50%, -50%);
300+
background: var(--fg4);
301+
border-radius: 50%;
302+
}
303+
219304
.contents .data {
220305
color: var(--fg2);
221306
}
@@ -230,7 +315,7 @@ DownloadsPopup.style = css`
230315
background: var(--bg20);
231316
}
232317
233-
.iconcontainer {
318+
.buttoniconcontainer {
234319
flex: 1;
235320
display: flex;
236321
justify-content: right;

0 commit comments

Comments
 (0)