Skip to content

Commit 57e49b3

Browse files
Feature: File Downloading (#4)
* add downloading files Signed-off-by: Nikita Skrynnik <[email protected]> * uncomment build script Signed-off-by: Nikita Skrynnik <[email protected]> --------- Signed-off-by: Nikita Skrynnik <[email protected]>
1 parent 5e9b69e commit 57e49b3

File tree

9 files changed

+263
-28
lines changed

9 files changed

+263
-28
lines changed

package-lock.json

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"@kobalte/core": "^0.13.11",
1616
"@tauri-apps/api": "^2.8.0",
1717
"@tauri-apps/plugin-opener": "^2",
18-
"cef-client": "^1.1.15",
18+
"cef-client": "^1.2.0",
1919
"lucide-solid": "^0.540.0",
2020
"solid-js": "^1.9.3",
2121
"validator": "^13.15.15"

src/components/browser/Browser.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ class InputHandler {
124124
canvas.onmousemove = (e) => connection.page.mouseMove(e.offsetX, e.offsetY);
125125
canvas.onmousedown = (e) => connection.page.click(e.offsetX, e.offsetY, e.button, true);
126126
canvas.onmouseup = (e) => connection.page.click(e.offsetX, e.offsetY, e.button, false);
127-
canvas.onwheel = (e) => connection.page.scroll(e.offsetX, e.offsetY, -e.deltaX, e.deltaY);
127+
canvas.onwheel = (e) => connection.page.scroll(e.offsetX, e.offsetY, -e.deltaX, -e.deltaY);
128128

129129
canvas.onkeydown = (e) => {
130130
if (app.shortcuts.checkShortcutConflict(e)) return;
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
.popoverTrigger {
2+
position: relative;
3+
background: none;
4+
border: none;
5+
outline: none;
6+
width: 24px;
7+
height: 24px;
8+
9+
&:hover {
10+
background-color: rgba(100, 100, 100, 0.1);
11+
cursor: pointer;
12+
border-radius: 5px;
13+
}
14+
15+
.badge {
16+
position: absolute;
17+
top: -5px;
18+
right: -5px;
19+
background-color: #6c8cff;
20+
color: white;
21+
border-radius: 50%;
22+
width: 16px;
23+
height: 16px;
24+
display: flex;
25+
align-items: center;
26+
justify-content: center;
27+
font-size: 10px;
28+
font-weight: bold;
29+
}
30+
}
31+
32+
.popoverContent {
33+
display: flex;
34+
flex-direction: column;
35+
gap: 10px;
36+
37+
padding: 10px;
38+
background: white;
39+
border-radius: 10px;
40+
border: 1px solid hsl(240 5% 84%);
41+
width: 400px;
42+
max-height: 300px;
43+
outline: none;
44+
}
45+
46+
.popoverHeader {
47+
display: flex;
48+
justify-content: space-between;
49+
align-items: center;
50+
51+
.popoverTitle {
52+
font-size: 16px;
53+
font-weight: normal;
54+
}
55+
56+
.popoverCloseButton {
57+
height: 24px;
58+
width: 24px;
59+
background: none;
60+
border: none;
61+
outline: none;
62+
cursor: pointer;
63+
border-radius: 5px;
64+
65+
&:hover {
66+
background-color: rgba(100, 100, 100, 0.1);
67+
}
68+
}
69+
}
70+
71+
.popoverDescription {
72+
overflow-y: auto;
73+
}
74+
75+
.downloadItem {
76+
margin: 5px 5px;
77+
background-color: rgba(100, 100, 100, 0.05);
78+
padding: 10px;
79+
border-radius: 10px;
80+
cursor: pointer;
81+
82+
&:hover {
83+
background-color: rgba(100, 100, 100, 0.1);
84+
}
85+
}
86+
87+
.info {
88+
display: flex;
89+
justify-content: space-between;
90+
margin-bottom: 8px;
91+
}
92+
93+
.label {
94+
font-size: 12px;
95+
color: #666;
96+
}
97+
98+
.track {
99+
height: 3px;
100+
border-radius: 10px;
101+
background: #BBB;
102+
}
103+
104+
.fill {
105+
width: var(--kb-progress-fill-width);
106+
height: 100%;
107+
background: linear-gradient(90deg, #3b82f6 0%, #1d4ed8 100%);
108+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { Progress } from "@kobalte/core/progress";
2+
import { AppState } from "../../state/state";
3+
import styles from "./Downloads.module.scss";
4+
import { DownloadItem } from "../../state/downloads";
5+
import { Popover } from "@kobalte/core/popover";
6+
import { DownloadIcon, XIcon } from "lucide-solid";
7+
import { For, Show } from "solid-js";
8+
9+
10+
export default function Downloads(props: { app: AppState }) {
11+
const inProgressCount = () => props.app.downloads.items.filter(item => !item.is_complete && !item.is_aborted).length;
12+
13+
return (
14+
<Popover>
15+
<Popover.Trigger class={styles.popoverTrigger}>
16+
<DownloadIcon size={20} />
17+
<Show when={inProgressCount() > 0}>
18+
<span class={styles.badge}>{inProgressCount()}</span>
19+
</Show>
20+
</Popover.Trigger>
21+
<Popover.Portal>
22+
<Popover.Content class={styles.popoverContent}>
23+
24+
<Popover.Arrow class={styles.arrow} />
25+
26+
<div class={styles.popoverHeader}>
27+
<Popover.Title class={styles.popoverTitle}>Downloads</Popover.Title>
28+
<Popover.CloseButton class={styles.popoverCloseButton}>
29+
<XIcon size={24} />
30+
</Popover.CloseButton>
31+
</div>
32+
33+
<Popover.Description class={styles.popoverDescription}>
34+
<For each={props.app.downloads.items}>{(item) => (
35+
<Download item={item} />
36+
)}</For>
37+
</Popover.Description>
38+
39+
</Popover.Content>
40+
</Popover.Portal>
41+
</Popover >
42+
)
43+
}
44+
45+
function Download(props: { item: DownloadItem }) {
46+
return (
47+
<div>
48+
<div class={styles.downloadItem}>
49+
<Progress value={(props.item.received / props.item.total) * 100} minValue={0} maxValue={100} class={styles.progress}>
50+
<div class={styles.info}>
51+
<Progress.Label class={styles.label}> {props.item.path.split("/").pop()}</Progress.Label>
52+
<Progress.ValueLabel class={styles.label} />
53+
</div>
54+
<Progress.Track class={styles.track}>
55+
<Progress.Fill class={styles.fill} />
56+
</Progress.Track>
57+
</Progress>
58+
</div>
59+
</div >
60+
)
61+
}

src/components/sidebar/Layout.module.scss

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,3 @@
1-
.version {
2-
margin-top: auto;
3-
padding: 8px 10px;
4-
font-size: 11px;
5-
color: #999;
6-
text-align: center;
7-
}
8-
91
.pane {
102
height: 100%;
113

@@ -14,11 +6,24 @@
146
}
157

168
.content {
9+
display: flex;
10+
flex-direction: column;
11+
justify-content: space-between;
12+
gap: 8px;
13+
}
14+
15+
.header {
1716
display: flex;
1817
flex-direction: column;
1918
gap: 8px;
2019
}
2120

21+
.footer {
22+
display: flex;
23+
justify-content: space-between;
24+
align-items: center;
25+
}
26+
2227
.newTabButton {
2328
padding: 10px 10px;
2429
border-radius: 5px;
@@ -36,5 +41,14 @@
3641
flex-direction: column;
3742
gap: 8px;
3843

39-
overflow: auto;
44+
flex: 1;
45+
overflow-y: auto;
46+
}
47+
48+
.version {
49+
margin-top: auto;
50+
padding: 8px 10px;
51+
font-size: 11px;
52+
color: #999;
53+
text-align: center;
4054
}

src/components/sidebar/Layout.tsx

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { AppState } from "../../state/state";
88
import { getVersion } from "@tauri-apps/api/app";
99
import styles from "./Layout.module.scss"
1010
import { NewTabInput } from "./NewTabInput";
11+
import Downloads from "./Downloads";
1112

1213
export default function Sidebar(props: { app: AppState }) {
1314
const [version] = createResource(getVersion);
@@ -19,24 +20,29 @@ export default function Sidebar(props: { app: AppState }) {
1920
<>
2021
<ResizablePane width={300} minWidth={190} maxWidth={400} class={styles.pane}>
2122
<ResizablePane.Content class={styles.content}>
22-
<TabControls app={props.app} />
23-
<Input app={props.app} />
24-
<Show when={props.app.profiles}>
25-
<Profiles app={props.app} />
26-
</Show>
27-
<div onClick={onNewTabClick} class={styles.newTabButton}>
28-
<p> + New Tab</p>
23+
<div class={styles.header}>
24+
<TabControls app={props.app} />
25+
<Input app={props.app} />
26+
<Show when={props.app.profiles}>
27+
<Profiles app={props.app} />
28+
</Show>
29+
<div onClick={onNewTabClick} class={styles.newTabButton}>
30+
<p> + New Tab</p>
31+
</div>
2932
</div>
3033
<div class={styles.tabs}>
3134
<For each={props.app.tabs}>{(tab) => (
3235
<Tab tab={tab} />
3336
)}
3437
</For>
3538
</div>
36-
<div class={styles.version}>
37-
<Show when={version()} fallback={<span>Loading version...</span>}>
38-
<span>v{version()}</span>
39-
</Show>
39+
<div class={styles.footer}>
40+
<Downloads app={props.app} />
41+
<div class={styles.version}>
42+
<Show when={version()} fallback={<span>Loading version...</span>}>
43+
<span>v{version()}</span>
44+
</Show>
45+
</div>
4046
</div>
4147
</ResizablePane.Content>
4248
<ResizablePane.Handle />

src/state/downloads.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { createStore, SetStoreFunction } from "solid-js/store";
2+
3+
export interface DownloadItem {
4+
id: number;
5+
path: string;
6+
received: number;
7+
total: number;
8+
is_complete: boolean;
9+
is_aborted: boolean;
10+
}
11+
12+
export class Downloads {
13+
readonly items: DownloadItem[];
14+
private setItems: SetStoreFunction<DownloadItem[]>;
15+
16+
constructor() {
17+
[this.items, this.setItems] = createStore<DownloadItem[]>([]);
18+
}
19+
20+
addItem(item: DownloadItem) {
21+
if (this.exists(item.id)) {
22+
this.setItems(items => items.id === item.id, item);
23+
return;
24+
}
25+
26+
this.setItems(items => [...items, item]);
27+
}
28+
29+
private exists(id: number): boolean {
30+
return this.items.some(item => item.id === id);
31+
}
32+
}

0 commit comments

Comments
 (0)