Skip to content

Commit fbe69e5

Browse files
committed
Modularize patching steps
1 parent 892e8e1 commit fbe69e5

11 files changed

Lines changed: 703 additions & 995 deletions

File tree

frontend/README.md

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
## Usage
22

3-
Those templates dependencies are maintained via [pnpm](https://pnpm.io) via `pnpm up -Lri`.
4-
5-
This is the reason you see a `pnpm-lock.yaml`. That being said, any package manager will work. This file can be safely be removed once you clone a template.
6-
73
```bash
84
$ npm install # or pnpm install or yarn install
95
```
@@ -14,7 +10,7 @@ $ npm install # or pnpm install or yarn install
1410

1511
In the project directory, you can run:
1612

17-
### `npm dev` or `npm start`
13+
### `npm run dev` or `npm start`
1814

1915
Runs the app in the development mode.<br>
2016
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
@@ -26,9 +22,4 @@ The page will reload if you make edits.<br>
2622
Builds the app for production to the `dist` folder.<br>
2723
It correctly bundles Solid in production mode and optimizes the build for the best performance.
2824

29-
The build is minified and the filenames include the hashes.<br>
30-
Your app is ready to be deployed!
31-
32-
## Deployment
33-
34-
You can deploy the `dist` folder to any static host provider (netlify, surge, now, etc.)
25+
This build gets included in the apk after the next rebuild of the app.
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { onMount, Show } from "solid-js";
2+
import { refetchAppInfo, refetchModdingStatus, refetchSettings } from "../../store";
3+
import { refetchMods } from "../../state/mods";
4+
import { useNavigate } from "@solidjs/router";
5+
import { GlobalPatchingData } from "../PatchingModal";
6+
import { launchCurrentApp } from "../../api/android";
7+
import { CustomModal } from "../CustomModal";
8+
import RunButton from "../../components/Buttons/RunButton";
9+
import PlayArrowRounded from "@suid/icons-material/PlayArrowRounded";
10+
import { showConfirmModal } from "../ConfirmModal";
11+
import { SmallText } from "../../styles/TextStyles";
12+
13+
/**
14+
* This step is shown when the patching/installing/restoring is done
15+
* @param props
16+
* @returns
17+
*/
18+
export function DoneStep(props: {
19+
next: () => void,
20+
onClose?: () => void,
21+
patchingData: GlobalPatchingData
22+
customMessage?: string
23+
}) {
24+
25+
let navigate = useNavigate();
26+
function startGame() {
27+
launchCurrentApp();
28+
}
29+
30+
// Refresh everything just to be sure
31+
onMount(() => {
32+
refetchModdingStatus();
33+
refetchAppInfo();
34+
refetchMods();
35+
refetchSettings();
36+
})
37+
38+
return (
39+
<CustomModal title={"Patching is done!"} open onClose={props.onClose}
40+
buttons={<>
41+
{/* If success, close the modal */}
42+
<RunButton text="Start game" icon={<PlayArrowRounded />} onClick={async () => {
43+
let sure = await showConfirmModal({
44+
title: "Start game",
45+
message: "Are you sure you want to start the game without installing mods? Patched game with no mods does not make sense if you are not a developer.",
46+
})
47+
48+
if (sure) {
49+
props?.onClose && props.onClose(); startGame();
50+
} else {
51+
return;
52+
}
53+
}} />
54+
55+
<RunButton text="Install mods" variant='success' onClick={() => {
56+
props?.onClose && props.onClose();
57+
navigate("/mods");
58+
}} />
59+
</>} >
60+
61+
<Show when={props.customMessage}>
62+
<SmallText>{props.customMessage}</SmallText>
63+
</Show>
64+
<Show when={!props.customMessage}>
65+
<SmallText>
66+
The game is successfully installed, you can now start the game or install the mods.
67+
</SmallText>
68+
</Show>
69+
</CustomModal>
70+
)
71+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { createSignal, onCleanup, onMount, Show } from "solid-js";
2+
import { checkIfGameIsInstalled, installGame } from "./utils";
3+
import toast from "solid-toast";
4+
import { config } from "../../store";
5+
import { CustomModal } from "../CustomModal";
6+
import RunButton from "../../components/Buttons/RunButton";
7+
import { MediumText, SmallText } from "../../styles/TextStyles";
8+
import { IBackup } from "../../api/backups";
9+
10+
/**
11+
* Here we ask to install the game
12+
* @param props
13+
* @returns
14+
*/
15+
export function InstallStep(props: {
16+
next: () => void,
17+
onClose?: () => void,
18+
/**
19+
* Selected backup to install (if we want to show additional info)
20+
*/
21+
selectedBackup?: IBackup,
22+
/**
23+
* Name of the backup to install (required)
24+
*/
25+
backupName: string,
26+
},
27+
) {
28+
29+
const [done, setDone] = createSignal(false);
30+
const [error, setError] = createSignal(false);
31+
const [inProgress, setInProgress] = createSignal(false);
32+
const [isInstalled, setIsInstalled] = createSignal(false);
33+
34+
const timer: NodeJS.Timeout = setInterval(async () => {
35+
if (!inProgress()) return;
36+
if (isInstalled()) return;
37+
38+
let installed = await checkIfGameIsInstalled();
39+
40+
if (installed) {
41+
setIsInstalled(true);
42+
setDone(true);
43+
setInProgress(false);
44+
clearInterval(timer);
45+
toast.success("Game is installed successfully");
46+
}
47+
}, 400);
48+
49+
onMount(async () => {
50+
setInProgress(true);
51+
52+
let currentApp = config()?.currentApp;
53+
54+
if (!currentApp) {
55+
toast.error("No game selected");
56+
return;
57+
};
58+
});
59+
60+
61+
onCleanup(() => {
62+
if (timer) clearInterval(timer);
63+
});
64+
65+
return (
66+
<CustomModal title={"Install the patched game"} open onClose={props.onClose}
67+
buttons={<>
68+
{/* If success, allow to go to next step (Uninstalling) */}
69+
<Show when={done() && !error() && isInstalled()}>
70+
<RunButton text="Next step" variant='success' onClick={() => { props.next() }} />
71+
</Show>
72+
{/* If just started show the patch button */}
73+
<Show when={!done() && !error() && !isInstalled()}>
74+
<RunButton text="Install" variant='success' onClick={() => installGame(props.backupName)} />
75+
</Show>
76+
</>} >
77+
<Show when={isInstalled()}>
78+
<MediumText>
79+
The game is successfully <span class="text-accent">installed</span>, click on the button below to go to the next step.
80+
</MediumText>
81+
</Show>
82+
83+
<Show when={!isInstalled()}>
84+
<MediumText>
85+
Install the game on your quest, click on the button below to install the game.
86+
After that, click on the button below to go to the next step.
87+
</MediumText>
88+
<SmallText>
89+
If the game won't install, check your free space on the quest. You should have at least 3gb free.
90+
If you have enough space, try to restart the quest and try again.
91+
One more issue could be that you have the game badly uninstalled, in this case you need to run adb command to uninstall the game using SideQuest.
92+
</SmallText>
93+
</Show>
94+
</CustomModal>
95+
)
96+
}
97+
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import { createSignal, For, onCleanup, Show } from "solid-js";
2+
import { PatchingProgressData } from "../../state/eventBus";
3+
import { currentApplication, refetchModdingStatus } from "../../store";
4+
import toast from "solid-toast";
5+
import { patchCurrentApp } from "../../api/patching";
6+
import { CustomModal } from "../CustomModal";
7+
import PlayArrowRounded from "@suid/icons-material/PlayArrowRounded";
8+
import RunButton from "../../components/Buttons/RunButton";
9+
import { FirePatch } from "../../assets/Icons";
10+
import { MediumText } from "../../styles/TextStyles";
11+
import { Box, LinearProgress } from "@suid/material";
12+
13+
/**
14+
* Hete we patch the game and listen for patching events
15+
*/
16+
export function PatchingStep(props: {
17+
next: () => void,
18+
onClose?: () => void,
19+
setBackupName: (backupName: string) => void,
20+
}) {
21+
const [progress, setProgress] = createSignal(0);
22+
23+
// TODO: Check for free space before patching
24+
25+
const [log, setLog] = createSignal<PatchingProgressData[]>([]);
26+
27+
let logElement: HTMLPreElement | undefined;
28+
29+
const [done, setDone] = createSignal(false);
30+
const [error, setError] = createSignal(false);
31+
const [inProgress, setInProgress] = createSignal(false);
32+
33+
// Update log when a new event is received
34+
function onPatchProgress(e: CustomEvent) {
35+
let data = e.detail as PatchingProgressData;
36+
37+
// find previous operation
38+
let prevOperation = log().find((l) => l.currentOperation === data.currentOperation);
39+
40+
if (prevOperation) {
41+
// if the operation is the same, replace it
42+
setLog((old) => old.map((l) => {
43+
if (l.currentOperation === data.currentOperation) {
44+
return data;
45+
}
46+
return l;
47+
}))
48+
} else {
49+
// if the operation is not the same, add it
50+
setLog((old) => [...old, data]);
51+
}
52+
53+
logElement?.scrollTo(0, logElement.scrollHeight);
54+
55+
setProgress(data.progress * 100);
56+
57+
if (data.done) {
58+
if (data.error) {
59+
setError(true);
60+
toast.error("Failed to patch the game");
61+
} else {
62+
setDone(true);
63+
props.setBackupName(data.backupName);
64+
toast.success("Game patched successfully");
65+
66+
}
67+
68+
// Remove the listener
69+
// @ts-ignore
70+
BackendEvents.removeEventListener("patch-progress", onPatchProgress);
71+
72+
}
73+
74+
75+
}
76+
77+
async function startPatching() {
78+
setInProgress(true);
79+
try {
80+
let result = await patchCurrentApp();
81+
if (!result) {
82+
toast.error("Failed to patch the game");
83+
return;
84+
}
85+
await refetchModdingStatus();
86+
setInProgress(true);
87+
} catch (e) {
88+
console.error(e)
89+
toast.error("Failed to patch game");
90+
setInProgress(false);
91+
setError(true);
92+
}
93+
94+
// @ts-ignore
95+
BackendEvents.addEventListener("patch-progress", onPatchProgress);
96+
}
97+
98+
onCleanup(() => {
99+
// @ts-ignore
100+
BackendEvents.removeEventListener("patch-progress", onPatchProgress);
101+
})
102+
103+
return (
104+
<CustomModal title={"Patching"} open onClose={props.onClose}
105+
buttons={<>
106+
{/* If success, allow to go to next step (Uninstalling) */}
107+
<Show when={done() && !error()}>
108+
<RunButton text="Next step" icon={<PlayArrowRounded />} variant='success' onClick={() => { props.next() }} />
109+
</Show>
110+
{/* If just started show the patch button */}
111+
<Show when={!done() && !error() && !inProgress()}>
112+
<RunButton text="Patch" icon={<FirePatch />} variant='success' onClick={startPatching} />
113+
</Show>
114+
</>} >
115+
<Show when={!inProgress() && !done()}>
116+
<MediumText>
117+
To start the patching process, click on the button below.
118+
</MediumText>
119+
</Show>
120+
121+
<Show when={inProgress()}>
122+
<Box sx={{ width: "100%" }}>
123+
<LinearProgress variant="determinate" value={progress()} />
124+
</Box>
125+
<pre ref={logElement} style={{
126+
background: "black",
127+
color: "white",
128+
padding: "10px",
129+
"border-radius": "0px",
130+
"min-width": "400px",
131+
"max-width": "100vw",
132+
height: "300px",
133+
"overflow-y": "auto",
134+
"font-size": "12px",
135+
}}>
136+
<For each={log()}>
137+
{(line) => <LogLine line={line} />}
138+
</For>
139+
</pre>
140+
</Show>
141+
{/* If done succesfully */}
142+
<Show when={!inProgress() && done() && !error()}>
143+
<MediumText>
144+
The patching is completed successfully.
145+
To continue, click on the button below.
146+
</MediumText>
147+
148+
</Show>
149+
150+
</CustomModal>
151+
)
152+
}
153+
154+
function LogLine({ line }: { line: PatchingProgressData }) {
155+
return <div>({line.doneOperations - 1}/{line.totalOperations}) {line.currentOperation}{line.done ? "OK" : "..."} </div>
156+
}

0 commit comments

Comments
 (0)