Skip to content

Commit 6bd3285

Browse files
committed
Operation UI
1 parent 9cc7df5 commit 6bd3285

File tree

10 files changed

+213
-65
lines changed

10 files changed

+213
-65
lines changed

bun.lockb

-12 Bytes
Binary file not shown.

src-tauri/src/builder/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pub mod sdk;
2+
pub mod swift;
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::path::{Component, Path, PathBuf};
88
use std::process::Command;
99
use tauri::{AppHandle, Manager};
1010

11-
use crate::swift::{swift_bin, validate_toolchain};
11+
use crate::builder::swift::{swift_bin, validate_toolchain};
1212

1313
const DARWIN_TOOLS_VERSION: &str = "1.0.1";
1414

src-tauri/src/main.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@ mod templates;
88
#[macro_use]
99
mod windows;
1010
#[macro_use]
11-
mod sdk;
12-
#[macro_use]
13-
mod swift;
11+
mod builder;
1412
mod sideloader;
1513

1614
use device::refresh_idevice;
@@ -21,8 +19,8 @@ use sideloader::apple_commands::{
2119
use tauri::Emitter;
2220
use templates::create_template;
2321

24-
use sdk::install_sdk_operation;
25-
use swift::{
22+
use builder::sdk::install_sdk_operation;
23+
use builder::swift::{
2624
build_swift, clean_swift, deploy_swift, get_swiftly_toolchains, get_toolchain_info,
2725
has_darwin_sdk, validate_toolchain,
2826
};

src/components/OperationView.css

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
.operation-content {
2+
display: flex;
3+
flex-direction: column;
4+
gap: var(--padding-xl);
5+
}
6+
7+
.operation-step-icon {
8+
width: 1.5rem;
9+
height: 1.5rem;
10+
min-width: 1.5rem;
11+
}
12+
13+
.operation-step {
14+
display: flex;
15+
gap: var(--padding-md);
16+
align-items: center;
17+
}
18+
19+
.operation-extra-details {
20+
background-color: black;
21+
overflow-x: auto;
22+
padding: var(--padding-md);
23+
border-radius: 1px;
24+
margin: 0;
25+
}
26+
27+
.operation-step-internal {
28+
flex-shrink: 1;
29+
}

src/components/OperationView.tsx

Lines changed: 79 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,96 @@
11
import {
2-
Button,
3-
Input,
2+
Accordion,
3+
AccordionDetails,
4+
AccordionSummary,
5+
Divider,
46
Modal,
57
ModalClose,
68
ModalDialog,
79
Typography,
810
} from "@mui/joy";
9-
import { listen } from "@tauri-apps/api/event";
10-
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
11-
import { invoke } from "@tauri-apps/api/core";
12-
import { Operation } from "../utilities/operations";
11+
import { OperationState } from "../utilities/operations";
12+
import "./OperationView.css";
13+
import { SuccessIcon, ErrorIcon, StyledLoadingIcon } from "react-toast-plus";
14+
import { PanoramaFishEye } from "@mui/icons-material";
1315

14-
interface OperationViewProps {
15-
operation_info: { operation: Operation; params: { [key: string]: any } };
16-
}
17-
18-
export default ({ operation_info }: OperationViewProps) => {
19-
const operation = operation_info.operation;
20-
const params = operation_info.params;
21-
const id = useMemo<string>(() => operation.id + "_operation", [operation]);
22-
const [open, setOpen] = useState(true);
23-
24-
const listenerAdded = useRef(false);
25-
const unlisten = useRef<() => void>(() => {});
26-
27-
useEffect(() => {
28-
if (!listenerAdded.current) {
29-
(async () => {
30-
const unlistenFn = await listen(id, (event) => {});
31-
unlisten.current = unlistenFn;
32-
})();
33-
listenerAdded.current = true;
34-
}
35-
return () => {
36-
unlisten.current();
37-
};
38-
}, []);
39-
40-
const startOperation = useCallback(async () => {
41-
invoke(id);
42-
}, []);
16+
export default ({
17+
operationState,
18+
closeMenu,
19+
}: {
20+
operationState: OperationState;
21+
closeMenu: () => void;
22+
}) => {
23+
const operation = operationState.current;
24+
const failed = operationState.failed.length > 0;
25+
const done =
26+
failed || operationState.completed.length == operation.steps.length;
4327

4428
return (
4529
<Modal
46-
open={open}
30+
open={true}
4731
onClose={() => {
48-
setOpen(false);
32+
if (done) closeMenu();
4933
}}
5034
>
5135
<ModalDialog>
52-
<ModalClose />
53-
<Typography level="h3">Can I put my balls in your jaws?</Typography>
36+
{done && <ModalClose />}
37+
<div>
38+
<Typography level="h3">{operation?.title}</Typography>
39+
<Typography level="body-lg">
40+
{done
41+
? failed
42+
? "Operation failed. Please see steps for details."
43+
: "Operation completed!"
44+
: "Please wait..."}
45+
</Typography>
46+
</div>
47+
<Divider />
48+
<div className="operation-content">
49+
{operation.steps.map((step) => {
50+
let failed = operationState.failed.find((f) => f.stepId == step.id);
51+
let completed = operationState.completed.includes(step.id);
52+
let started = operationState.started.includes(step.id);
53+
let notStarted = !failed && !completed && !started;
54+
return (
55+
<div className="operation-step">
56+
<div className="operation-step-icon">
57+
{failed && <ErrorIcon />}
58+
{!failed && completed && <SuccessIcon />}
59+
{!failed && !completed && started && <StyledLoadingIcon />}
60+
{notStarted && (
61+
<PanoramaFishEye
62+
sx={{
63+
width: "100%",
64+
height: "100%",
65+
color: "neutral.500",
66+
}}
67+
/>
68+
)}
69+
</div>
70+
71+
<div className="operation-step-internal">
72+
<Typography
73+
textColor={notStarted ? "neutral.500" : undefined}
74+
>
75+
{step.title}
76+
</Typography>
77+
{failed && (
78+
<Accordion sx={{ marginTop: 0 }}>
79+
<AccordionSummary>
80+
<Typography level="body-sm">Show Details</Typography>
81+
</AccordionSummary>
82+
<AccordionDetails>
83+
<pre className="operation-extra-details">
84+
{failed.extraDetails}
85+
</pre>
86+
</AccordionDetails>
87+
</Accordion>
88+
)}
89+
</div>
90+
</div>
91+
);
92+
})}
93+
</div>
5494
</ModalDialog>
5595
</Modal>
5696
);

src/components/SDKMenu.tsx

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,13 @@ import { Button, Typography } from "@mui/joy";
22
import { useIDE } from "../utilities/IDEContext";
33
import { open } from "@tauri-apps/plugin-dialog";
44
import { useToast } from "react-toast-plus";
5-
import { invoke } from "@tauri-apps/api/core";
65
import { useCallback, useEffect } from "react";
76
import { openUrl } from "@tauri-apps/plugin-opener";
8-
import OperationView from "./OperationView";
97
import { installSdkOperation } from "../utilities/operations";
108

119
export default () => {
12-
const { selectedToolchain, hasDarwinSDK, checkSDK } = useIDE();
10+
const { selectedToolchain, hasDarwinSDK, checkSDK, startOperation } =
11+
useIDE();
1312
const { addToast } = useToast();
1413

1514
const install = useCallback(async () => {
@@ -27,21 +26,11 @@ export default () => {
2726
addToast.error("No Xcode.xip selected");
2827
return;
2928
}
30-
let promise = invoke("install_sdk", {
29+
const params = {
3130
xcodePath: xipPath,
3231
toolchainPath: selectedToolchain?.path || "",
33-
});
34-
addToast.promise(promise, {
35-
pending: "Installing SDK (this may take a while)...",
36-
success: () => {
37-
checkSDK();
38-
return "SDK installed successfully!";
39-
},
40-
error: (e) => {
41-
console.error("Failed to install SDK:", e);
42-
return `Failed to install SDK: ${e}`;
43-
},
44-
});
32+
};
33+
startOperation(installSdkOperation, params);
4534
}, [selectedToolchain, addToast]);
4635

4736
useEffect(() => {

src/utilities/IDEContext.tsx

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ import {
2323
} from "@mui/joy";
2424
import { useCommandRunner } from "./Command";
2525
import { useStore } from "./StoreContext";
26-
import { Operation } from "./operations";
26+
import { Operation, OperationState, OperationUpdate } from "./operations";
27+
import OperationView from "../components/OperationView";
2728

2829
export interface IDEContextType {
2930
initialized: boolean;
@@ -39,10 +40,10 @@ export interface IDEContextType {
3940
scanToolchains: () => Promise<void>;
4041
checkSDK: () => Promise<void>;
4142
locateToolchain: () => Promise<void>;
42-
startOperation: <T>(
43+
startOperation: (
4344
operation: Operation,
4445
params: { [key: string]: any }
45-
) => Promise<T>;
46+
) => Promise<void>;
4647
setSelectedToolchain: (
4748
value: Toolchain | ((oldValue: Toolchain | null) => Toolchain | null) | null
4849
) => void;
@@ -272,7 +273,59 @@ export const IDEProvider: React.FC<{
272273

273274
const { cancelCommand } = useCommandRunner();
274275

275-
const startOperation = useCallback;
276+
const [operationState, setOperationState] = useState<OperationState | null>(
277+
null
278+
);
279+
280+
const startOperation = useCallback(
281+
async (
282+
operation: Operation,
283+
params: { [key: string]: any }
284+
): Promise<void> => {
285+
return new Promise<void>(async (resolve, reject) => {
286+
const unlistenFn = await listen<OperationUpdate>(
287+
"operation_" + operation.id,
288+
(event) => {
289+
setOperationState((old) => {
290+
if (old == null) return null;
291+
if (event.payload.updateType === "started") {
292+
return {
293+
...old,
294+
started: [...old.started, event.payload.stepId],
295+
};
296+
} else if (event.payload.updateType === "finished") {
297+
return {
298+
...old,
299+
completed: [...old.completed, event.payload.stepId],
300+
};
301+
} else if (event.payload.updateType === "failed") {
302+
return {
303+
...old,
304+
failed: [
305+
...old.failed,
306+
{
307+
stepId: event.payload.stepId,
308+
extraDetails: event.payload.extraDetails,
309+
},
310+
],
311+
};
312+
}
313+
return old;
314+
});
315+
}
316+
);
317+
try {
318+
await invoke(operation.id, params);
319+
unlistenFn();
320+
resolve();
321+
} catch (e) {
322+
unlistenFn();
323+
reject(e);
324+
}
325+
});
326+
},
327+
[setOperationState]
328+
);
276329

277330
const contextValue = useMemo(
278331
() => ({
@@ -290,6 +343,7 @@ export const IDEProvider: React.FC<{
290343
setSelectedToolchain,
291344
hasDarwinSDK,
292345
checkSDK,
346+
startOperation,
293347
}),
294348
[
295349
isWindows,
@@ -306,6 +360,7 @@ export const IDEProvider: React.FC<{
306360
setSelectedToolchain,
307361
hasDarwinSDK,
308362
checkSDK,
363+
startOperation,
309364
]
310365
);
311366

@@ -440,6 +495,14 @@ export const IDEProvider: React.FC<{
440495
</form>
441496
</ModalDialog>
442497
</Modal>
498+
{operationState && (
499+
<OperationView
500+
operationState={operationState}
501+
closeMenu={() => {
502+
setOperationState(null);
503+
}}
504+
/>
505+
)}
443506
</IDEContext.Provider>
444507
);
445508
};

src/utilities/operations.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,29 @@ export type OperationStep = {
99
title: string;
1010
};
1111

12+
export type OperationState = {
13+
current: Operation;
14+
completed: string[];
15+
started: string[];
16+
failed: {
17+
stepId: string;
18+
extraDetails: string;
19+
}[];
20+
};
21+
22+
type OperationInfoUpdate = {
23+
updateType: "started" | "finished";
24+
stepId: string;
25+
};
26+
27+
type OperationFailedUpdate = {
28+
updateType: "failed";
29+
stepId: string;
30+
extraDetails: string;
31+
};
32+
33+
export type OperationUpdate = OperationInfoUpdate | OperationFailedUpdate;
34+
1235
export const installSdkOperation: Operation = {
1336
id: "install_sdk",
1437
title: "Installing Darwin SDK",
@@ -25,5 +48,9 @@ export const installSdkOperation: Operation = {
2548
id: "install_toolset",
2649
title: "Install toolset",
2750
},
51+
{
52+
id: "cleanup",
53+
title: "Clean Up",
54+
},
2855
],
2956
};

0 commit comments

Comments
 (0)