Skip to content

Commit c5b5c77

Browse files
committed
Show YoWASP tool load progress.
1 parent 62958b6 commit c5b5c77

File tree

12 files changed

+246
-11
lines changed

12 files changed

+246
-11
lines changed

src/app.css

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
@import '@xterm/xterm/css/xterm.css';
22

33
:root {
4-
--color-theme: #353838;
4+
--color-theme: #2a312f;
55
--color-accent: #88f;
6+
--color-success: #5da45d;
67

78
--color-page-background: oklch(from var(--color-theme) calc(l - 0.06) c h);
89
--color-area-background: var(--color-theme);
@@ -24,9 +25,15 @@
2425
--color-button-disabled-border: var(--color-button-border);
2526
--color-button-disabled-text: oklch(from var(--color-button-text) 0.7 c h);
2627

28+
--color-progress-bar-track: oklch(from var(--color-area-background) calc(l + 0.2) c h);
29+
--color-progress-bar-indicator: var(--color-accent);
30+
--color-progress-bar-indicator-success: var(--color-success);
31+
2732
--color-tree-node-line-hover-background: oklch(from var(--color-theme) 0.8 c h / 0.2);
2833

29-
--color-menu-background: oklch(from var(--color-theme) calc(l + 0.1) c h);
34+
--color-popover-background: oklch(from var(--color-area-background) calc(l + 0.05) c h);
35+
36+
--color-menu-background: oklch(from var(--color-area-background) calc(l + 0.1) c h);
3037

3138
--color-menu-item-background: var(--color-menu-background);
3239
--color-menu-item-hover-background: oklch(from var(--color-menu-background) calc(l + 0.05) c h);
@@ -276,6 +283,37 @@ body {
276283
}
277284
}
278285

286+
.progress-popover {
287+
padding: 8px;
288+
display: grid;
289+
grid-template-areas:
290+
'label progress-text'
291+
'progress-bar progress-bar ';
292+
gap: 8px;
293+
294+
.label {
295+
grid-area: label;
296+
white-space: nowrap;
297+
overflow: hidden;
298+
text-overflow: ellipsis;
299+
}
300+
301+
.progress-text {
302+
grid-area: progress-text;
303+
align-self: end;
304+
font-variant-numeric: tabular-nums;
305+
text-align: right;
306+
white-space: nowrap;
307+
}
308+
309+
.progress-bar {
310+
grid-area: progress-bar;
311+
width: 100%;
312+
/* margin-block-start: 8px; */
313+
margin-block-end: 4px;
314+
}
315+
}
316+
279317
.menu-list {
280318
margin: 0;
281319
padding: 4px 0;
@@ -297,6 +335,25 @@ body {
297335

298336
.terminal-panel {
299337
grid-area: terminal;
338+
339+
.panel-content {
340+
position: relative;
341+
height: 0;
342+
display: grid;
343+
344+
#terminal {
345+
grid-area: 1 / -1;
346+
}
347+
348+
.popover-container {
349+
position: absolute;
350+
inset-block-start: 0;
351+
inset-inline-end: 0;
352+
width: 100%;
353+
max-width: 400px;
354+
padding: 8px;
355+
}
356+
}
300357
}
301358

302359
.terminal-panel,
@@ -305,7 +362,6 @@ body {
305362
}
306363

307364
#terminal {
308-
height: 0;
309365
font-family: monospace;
310366
font-size: max(13px, min(15px, 2vw));
311367
overflow: hidden;

src/app.tsx

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createEffect, createSignal, onMount } from 'solid-js';
1+
import { createEffect, createSignal, onMount, Show } from 'solid-js';
22
import { createStore, reconcile } from 'solid-js/store';
33
import { render } from 'solid-js/web';
44
import debounce from 'lodash/debounce';
@@ -10,6 +10,8 @@ import type { FileTreeNode } from './types';
1010
import { RPCController } from './rpc';
1111
import { Terminal } from './terminal';
1212

13+
import { PopoverContainer } from './components/popover-container';
14+
import { ProgressPopover } from './components/progress-popover';
1315
import { PanelContainer } from './components/panel';
1416
import { TreeView } from './components/tree-view';
1517

@@ -80,6 +82,21 @@ const App = () => {
8082
interrupt();
8183
};
8284

85+
const [currentlyLoadingTool, setCurrentlyLoadingTool] = createSignal<
86+
{ command: string; totalLength: number; doneLength: number } | null
87+
>(null);
88+
89+
let hideToolLoadProgressTimeout = 0;
90+
91+
createEffect(() => {
92+
let info;
93+
if (info = currentlyLoadingTool()) {
94+
clearTimeout(hideToolLoadProgressTimeout);
95+
const timeout = info.doneLength === info.totalLength ? 2000 : 10000;
96+
hideToolLoadProgressTimeout = setTimeout(() => setCurrentlyLoadingTool(null), timeout);
97+
}
98+
});
99+
83100
let [creatingNewFileNode, setCreatingNewFileNode] = createSignal<
84101
Parameters<typeof TreeView<FileTreeNode>>[0]['creatingNewNode']
85102
>(null);
@@ -270,6 +287,11 @@ const App = () => {
270287
updateFileTree(message.tree);
271288
break;
272289
}
290+
case 'tool-load-progress': {
291+
let { command, totalLength, doneLength } = message;
292+
setCurrentlyLoadingTool({ command, totalLength, doneLength });
293+
break;
294+
}
273295
default: message satisfies never;
274296
}
275297
});
@@ -352,9 +374,26 @@ const App = () => {
352374
},
353375
]);
354376
},
355-
children: (
356-
<div ref={el => xtermContainer = el} class="panel-content" id="terminal" />
357-
),
377+
get children() {
378+
return <div class="panel-content">
379+
<div ref={el => xtermContainer = el} id="terminal" />
380+
<PopoverContainer>
381+
<Show when={currentlyLoadingTool()}>
382+
{(info) => {
383+
const fmt = (n: number) => (n / 1048576).toFixed(1);
384+
const done = () => info().doneLength === info().totalLength;
385+
return <ProgressPopover
386+
label={!done() ? `Loading ${info().command}...` : `Loaded ${info().command}`}
387+
progressText={(!done() ? `${fmt(info().doneLength)} / ` : '') +
388+
`${fmt(info().totalLength)} MiB`}
389+
progressValue={info().doneLength / info().totalLength}
390+
done={done()}
391+
/>;
392+
}}
393+
</Show>
394+
</PopoverContainer>
395+
</div>;
396+
},
358397
},
359398

360399
{

src/builder.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ export class Builder {
3333
build(
3434
files: Tree,
3535
scriptName: string,
36-
writeOutput: (bytes: Uint8Array) => void
36+
writeOutput: (bytes: Uint8Array) => void,
37+
loadProgress: (event: { command: string; totalLength: number; doneLength: number }) => void,
3738
): Promise<{ code: number, files: Tree }> {
3839
if (this.#busy) {
3940
throw new Error("Builder is busy");
@@ -44,6 +45,9 @@ export class Builder {
4445
const message = event.data;
4546
if (message.type === 'output') {
4647
writeOutput(message.bytes);
48+
} else if (message.type === 'loadProgress') {
49+
let { command, totalLength, doneLength } = message;
50+
loadProgress({ command, totalLength, doneLength });
4751
} else if (message.type === 'result') {
4852
resolve({ code: message.code, files: message.files });
4953
this.#busy = false;

src/builder/proto.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ export interface PackagesMessage {
55
packages: { [name: string]: string }
66
}
77

8+
export interface LoadProgressMessage {
9+
type: 'loadProgress';
10+
command: string;
11+
totalLength: number;
12+
doneLength: number;
13+
}
14+
815
export interface BuildMessage {
916
type: 'build',
1017
files: Tree,
@@ -32,6 +39,7 @@ export type AppToBuilderMessage =
3239

3340
export type BuilderToAppMessage =
3441
| PackagesMessage
42+
| LoadProgressMessage
3543
| OutputMessage
3644
| ResultMessage
3745
| ErrorMessage;

src/builder/worker.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ async function executeScript(
7474
stdout: writeBuffered,
7575
stderr: writeBuffered,
7676
decodeASCII: false,
77+
fetchProgress: ({ totalLength, doneLength }) => {
78+
postMessage({ type: 'loadProgress', command: name, totalLength, doneLength });
79+
},
7780
})!;
7881
} catch (error) {
7982
if (error instanceof command.Exit) {
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { children, createComputed, createSignal, type JSX } from 'solid-js';
2+
3+
const ANIMATION_ID = 'popover-container-animation';
4+
5+
interface PopoverContainerProps {
6+
children: JSX.Element;
7+
}
8+
9+
export const PopoverContainer = (props: PopoverContainerProps) => {
10+
const resolved = children(() => props.children);
11+
12+
const [rendered, setRendered] = createSignal<JSX.Element>(null);
13+
14+
function cancelExistingAnimation(element: HTMLElement) {
15+
element.getAnimations().find(anim => anim.id === ANIMATION_ID)?.cancel();
16+
}
17+
18+
createComputed((previous) => {
19+
const current = resolved();
20+
21+
if (current !== undefined && current !== null && !(current instanceof HTMLElement)) {
22+
throw new Error('Popover container only supports a single popover');
23+
}
24+
25+
if (previous !== undefined && previous !== null && !(previous instanceof HTMLElement)) {
26+
throw new Error('Unexpected children type');
27+
}
28+
29+
if (!previous && current) {
30+
setRendered(current);
31+
cancelExistingAnimation(current);
32+
current.animate(
33+
{ transform: 'translateY(16px)', opacity: 0, offset: 0 },
34+
{ duration: 150, id: ANIMATION_ID, timeline: document.timeline },
35+
);
36+
} else if (previous && !current) {
37+
cancelExistingAnimation(previous);
38+
const animation = previous.animate(
39+
{ transform: 'translateY(16px)', opacity: 0 },
40+
{ duration: 150, id: ANIMATION_ID, timeline: document.timeline },
41+
);
42+
animation.finished.then(() => {
43+
if (rendered() === previous) {
44+
setRendered(null);
45+
}
46+
});
47+
}
48+
49+
return current;
50+
}, resolved());
51+
52+
return (
53+
<div class="popover-container">
54+
{rendered()}
55+
</div>
56+
);
57+
};

src/components/progress-bar.css

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
.progress-bar {
2+
block-size: 4px;
3+
inline-size: 10em;
4+
border-radius: 9999px;
5+
background: var(--color-progress-bar-track);
6+
overflow: hidden;
7+
8+
> .filled {
9+
block-size: 100%;
10+
background: var(--color-progress-bar-indicator);
11+
}
12+
13+
&.success > .filled {
14+
background: var(--color-progress-bar-indicator-success);
15+
}
16+
}

src/components/progress-bar.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { cls } from '../helpers/class-names';
2+
import './progress-bar.css';
3+
4+
interface ProgressBarProps {
5+
value: number;
6+
style?: 'normal' | 'success';
7+
}
8+
9+
export const ProgressBar = (props: ProgressBarProps) => {
10+
return (
11+
<div
12+
class={cls('progress-bar', props.style === 'success' && 'success')}
13+
role="progressbar"
14+
aria-valuenow={props.value}
15+
aria-valuemax={1}
16+
>
17+
<div class="filled" style={{ 'inline-size': `${(props.value * 100).toFixed(2)}%` }} />
18+
</div>
19+
);
20+
};
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { ProgressBar } from './progress-bar';
2+
3+
interface ProgressPopoverProps {
4+
label: string;
5+
progressText: string;
6+
progressValue: number;
7+
done: boolean;
8+
}
9+
10+
export const ProgressPopover = (props: ProgressPopoverProps) => {
11+
return (
12+
<div class="progress-popover popover">
13+
<div class="label">{props.label}</div>
14+
<div class="progress-text">{props.progressText}</div>
15+
<ProgressBar value={props.progressValue} style={props.done ? 'success' : 'normal'} />
16+
</div>
17+
);
18+
};

src/proto.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,13 @@ interface HomeChangeMessage {
8585
tree: FileTreeNode[];
8686
}
8787

88+
interface ToolLoadProgressMessage {
89+
type: 'tool-load-progress';
90+
command: string;
91+
totalLength: number;
92+
doneLength: number;
93+
}
94+
8895
export type PageToWorkerMessage =
8996
| RPCRequestMessage
9097
| RPCResponseMessage
@@ -104,4 +111,5 @@ export type WorkerToPageMessage =
104111
| TerminalStateChangeMessage
105112
| MountStateChangeMessage
106113
| HomeChangeMessage
114+
| ToolLoadProgressMessage
107115
;

0 commit comments

Comments
 (0)