Skip to content

Commit e128a2a

Browse files
committed
refactor frontend, code and performance improvements
1 parent 5fac359 commit e128a2a

File tree

45 files changed

+4134
-2904
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+4134
-2904
lines changed

src/App.tsx

Lines changed: 150 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,143 +1,203 @@
11
import { invoke } from "@tauri-apps/api";
22
import {
3-
LogicalSize,
43
appWindow,
4+
LogicalSize,
55
type PhysicalSize,
66
} from "@tauri-apps/api/window";
7-
import { useCallback, useEffect, useRef, useState } from "react";
7+
import {
8+
lazy,
9+
memo,
10+
useCallback,
11+
useEffect,
12+
useMemo,
13+
useRef,
14+
useState,
15+
} from "react";
816
import { StyleSheet, View } from "react-native";
917
import { DEBUG_MODE, IN_GAME, IN_GAME_PROCESS_ID } from "./constants/app";
10-
import AddThirdPartyServerModal from "./containers/AddThirdPartyServer";
11-
import ExternalServerHandler from "./containers/ExternalServerHandler";
12-
import JoinServerPrompt from "./containers/JoinServerPrompt";
1318
import LoadingScreen from "./containers/LoadingScreen";
14-
import MainView from "./containers/MainBody";
15-
import MessageBox from "./containers/MessageBox";
16-
import NavBar from "./containers/NavBar";
17-
import Notification from "./containers/Notification";
18-
import ContextMenu from "./containers/ServerContextMenu";
19-
import SettingsModal from "./containers/Settings";
2019
import WindowTitleBar from "./containers/WindowTitleBar";
21-
import i18n from "./locales";
20+
import { changeLanguage } from "./locales";
2221
import { useGenericPersistentState } from "./states/genericStates";
2322
import { useTheme } from "./states/theme";
24-
import { debounce } from "./utils/debounce";
23+
import { throttle } from "./utils/debounce";
2524
import {
2625
checkIfProcessAlive,
2726
fetchServers,
2827
fetchUpdateInfo,
2928
generateLanguageFilters,
3029
} from "./utils/helpers";
30+
import PerformanceMonitor from "./utils/performance";
3131
import { sc } from "./utils/sizeScaler";
32-
// import MouseFollower from "./components/MouseFollower";
3332

34-
const App = () => {
33+
// Lazy load heavy components for better initial load time
34+
const MainView = lazy(() => import("./containers/MainBody"));
35+
const NavBar = lazy(() => import("./containers/NavBar"));
36+
const AddThirdPartyServerModal = lazy(
37+
() => import("./containers/AddThirdPartyServer")
38+
);
39+
const ExternalServerHandler = lazy(
40+
() => import("./containers/ExternalServerHandler")
41+
);
42+
const JoinServerPrompt = lazy(() => import("./containers/JoinServerPrompt"));
43+
const MessageBox = lazy(() => import("./containers/MessageBox"));
44+
const Notification = lazy(() => import("./containers/Notification"));
45+
const ContextMenu = lazy(() => import("./containers/ServerContextMenu"));
46+
const SettingsModal = lazy(() => import("./containers/Settings"));
47+
48+
const App = memo(() => {
3549
const [loading, setLoading] = useState(!IN_GAME);
3650
const [maximized, setMaximized] = useState(false);
3751
const { theme } = useTheme();
3852
const { language } = useGenericPersistentState();
3953
const windowSize = useRef<PhysicalSize>();
4054
const mainWindowSize = useRef<LogicalSize>();
55+
const processCheckInterval = useRef<NodeJS.Timeout>();
4156

4257
const windowResizeListener = useCallback(
43-
debounce(async ({ payload }: { payload: PhysicalSize }) => {
44-
if (
45-
payload.width !== windowSize.current?.width ||
46-
payload.height !== windowSize.current?.height
47-
)
48-
setMaximized(await appWindow.isMaximized());
49-
50-
windowSize.current = payload;
51-
}, 50),
58+
throttle(async ({ payload }: { payload: PhysicalSize }) => {
59+
const endTimer = PerformanceMonitor.time("window-resize");
60+
61+
try {
62+
const hasChanged =
63+
payload.width !== windowSize.current?.width ||
64+
payload.height !== windowSize.current?.height;
65+
66+
if (hasChanged) {
67+
const isMaximized = await appWindow.isMaximized();
68+
setMaximized(isMaximized);
69+
windowSize.current = payload;
70+
}
71+
} finally {
72+
endTimer();
73+
}
74+
}, 100), // Increased throttle delay for better performance
5275
[]
5376
);
5477

55-
const initializeApp = async () => {
56-
fetchServers();
57-
fetchUpdateInfo();
58-
generateLanguageFilters();
59-
60-
mainWindowSize.current = (await appWindow.innerSize()).toLogical(
61-
await appWindow.scaleFactor()
62-
);
63-
// Set window attributes for loading screen
64-
appWindow.setResizable(false);
65-
appWindow.setSize(new LogicalSize(250, 300));
66-
appWindow.center();
67-
};
78+
const initializeApp = useCallback(async () => {
79+
const endTimer = PerformanceMonitor.time("app-initialization");
80+
81+
try {
82+
const [innerSize, scaleFactor] = await Promise.all([
83+
appWindow.innerSize(),
84+
appWindow.scaleFactor(),
85+
]);
86+
87+
mainWindowSize.current = innerSize.toLogical(scaleFactor);
88+
89+
// Set window attributes for loading screen
90+
await Promise.all([
91+
appWindow.setSize(new LogicalSize(250, 300)),
92+
appWindow.setResizable(false),
93+
appWindow.center(),
94+
]);
95+
96+
// Run independent operations in parallel
97+
await Promise.all([
98+
// Start these operations without waiting
99+
fetchServers(),
100+
fetchUpdateInfo(),
101+
generateLanguageFilters(),
102+
]);
103+
} finally {
104+
endTimer();
105+
}
106+
}, []);
68107

69108
useEffect(() => {
70-
i18n.changeLanguage(language);
109+
changeLanguage(language as any);
71110
}, [language]);
72111

73112
useEffect(() => {
74113
let killResizeListener: (() => void) | null = null;
75114

76115
const setupListeners = async () => {
77-
document.addEventListener("contextmenu", (event) => {
78-
try {
79-
if (DEBUG_MODE == false) {
80-
event.preventDefault();
81-
}
82-
} catch (e) {}
83-
});
116+
// Optimize context menu handler
117+
if (!DEBUG_MODE) {
118+
const handleContextMenu = (event: Event) => {
119+
event.preventDefault();
120+
};
121+
document.addEventListener("contextmenu", handleContextMenu, {
122+
passive: false,
123+
});
124+
}
84125

85126
killResizeListener = await appWindow.onResized(windowResizeListener);
86127
};
87128

129+
const setupGameMonitoring = () => {
130+
if (IN_GAME) {
131+
processCheckInterval.current = setInterval(async () => {
132+
try {
133+
const isAlive = await checkIfProcessAlive(IN_GAME_PROCESS_ID);
134+
if (!isAlive) {
135+
await invoke("send_message_to_game", {
136+
id: IN_GAME_PROCESS_ID,
137+
message: "close_overlay",
138+
});
139+
setTimeout(() => appWindow.close(), 300);
140+
}
141+
} catch (error) {
142+
console.error("Game process check failed:", error);
143+
}
144+
}, 1000); // Reduced frequency for better performance
145+
}
146+
};
147+
88148
setupListeners();
89149
initializeApp();
90-
91-
if (IN_GAME) {
92-
setInterval(async () => {
93-
if ((await checkIfProcessAlive(IN_GAME_PROCESS_ID)) == false) {
94-
invoke("send_message_to_game", {
95-
id: IN_GAME_PROCESS_ID,
96-
message: "close_overlay",
97-
});
98-
setTimeout(() => {
99-
appWindow.close();
100-
}, 300);
101-
}
102-
}, 200);
103-
}
150+
setupGameMonitoring();
104151

105152
return () => {
106-
if (killResizeListener) killResizeListener();
153+
killResizeListener?.();
154+
if (processCheckInterval.current) {
155+
clearInterval(processCheckInterval.current);
156+
}
107157
};
158+
}, [windowResizeListener, initializeApp]);
159+
160+
const handleLoadingEnd = useCallback(async () => {
161+
const endTimer = PerformanceMonitor.time("loading-end");
162+
163+
try {
164+
const targetSize = mainWindowSize.current || new LogicalSize(1000, 700);
165+
166+
await Promise.all([
167+
appWindow.setResizable(true),
168+
appWindow.setSize(targetSize),
169+
appWindow.center(),
170+
]);
171+
172+
setLoading(false);
173+
} finally {
174+
endTimer();
175+
}
108176
}, []);
109177

178+
const appStyle = useMemo(
179+
() => [styles.app, { padding: maximized || IN_GAME ? 0 : 4 }],
180+
[maximized]
181+
);
182+
183+
const appViewStyle = useMemo(
184+
() => [
185+
styles.appView,
186+
{
187+
borderRadius: maximized || IN_GAME ? 0 : 10,
188+
backgroundColor: theme.secondary,
189+
},
190+
],
191+
[maximized, theme.secondary]
192+
);
193+
110194
if (loading) {
111-
return (
112-
<LoadingScreen
113-
onEnd={async () => {
114-
await appWindow.setResizable(true);
115-
await appWindow.setSize(
116-
mainWindowSize.current
117-
? mainWindowSize.current
118-
: new LogicalSize(1000, 700)
119-
);
120-
await appWindow.center();
121-
setLoading(false);
122-
}}
123-
/>
124-
);
195+
return <LoadingScreen onEnd={handleLoadingEnd} />;
125196
}
126197

127198
return (
128-
<View
129-
style={[styles.app, { padding: maximized || IN_GAME ? 0 : 4 }]}
130-
key={language}
131-
>
132-
<View
133-
style={[
134-
styles.appView,
135-
{
136-
borderRadius: maximized || IN_GAME ? 0 : sc(10),
137-
backgroundColor: theme.secondary,
138-
},
139-
]}
140-
>
199+
<View style={appStyle} key={language}>
200+
<View style={appViewStyle}>
141201
<WindowTitleBar />
142202
<View style={styles.appBody}>
143203
<NavBar />
@@ -153,7 +213,9 @@ const App = () => {
153213
</View>
154214
</View>
155215
);
156-
};
216+
});
217+
218+
App.displayName = "App";
157219

158220
const styles = StyleSheet.create({
159221
app: {

src/api/apis.ts

Lines changed: 46 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,36 +4,52 @@ import { mapAPIResponseServerListToAppStructure } from "../utils/helpers";
44
import { Log } from "../utils/logger";
55
import { APIResponseServer, Server } from "../utils/types";
66

7-
export const getCachedList = async () => {
8-
return new Promise<{ success: boolean; servers: Server[] }>((resolve, _) => {
9-
api
10-
.get("/servers/full")
11-
.then((response) => {
12-
const list: APIResponseServer[] = response.data;
13-
if (Array.isArray(list)) {
14-
const restructuredList = mapAPIResponseServerListToAppStructure(list);
15-
resolve({ success: true, servers: restructuredList });
16-
}
17-
})
18-
.catch((e) => {
19-
Log.debug(e);
20-
resolve({ success: false, servers: [] });
21-
});
22-
});
23-
};
7+
interface ApiResponse<T> {
8+
success: boolean;
9+
data: T;
10+
error?: string;
11+
}
12+
13+
export const getCachedList = async (): Promise<ApiResponse<Server[]>> => {
14+
try {
15+
const response = await api.get<APIResponseServer[]>("/servers/full");
2416

25-
export const getUpdateInfo = async () => {
26-
return new Promise<{ success: boolean; info: UpdateInfo | undefined }>(
27-
(resolve, _) => {
28-
api
29-
.get("/launcher")
30-
.then((response) => {
31-
resolve({ success: true, info: response.data });
32-
})
33-
.catch((e) => {
34-
Log.debug(e);
35-
resolve({ success: false, info: undefined });
36-
});
17+
if (!Array.isArray(response.data)) {
18+
Log.debug("Invalid API response: expected array");
19+
return { success: false, data: [], error: "Invalid response format" };
3720
}
38-
);
21+
22+
const restructuredList = mapAPIResponseServerListToAppStructure(
23+
response.data
24+
);
25+
26+
// Update server store with the fetched data
27+
const { useServers } = await import("../states/servers");
28+
useServers.getState().setServers(restructuredList);
29+
30+
return { success: true, data: restructuredList };
31+
} catch (error) {
32+
Log.debug("Failed to fetch server list:", error);
33+
return {
34+
success: false,
35+
data: [],
36+
error: error instanceof Error ? error.message : "Unknown error",
37+
};
38+
}
39+
};
40+
41+
export const getUpdateInfo = async (): Promise<
42+
ApiResponse<UpdateInfo | undefined>
43+
> => {
44+
try {
45+
const response = await api.get<UpdateInfo>("/launcher");
46+
return { success: true, data: response.data };
47+
} catch (error) {
48+
Log.debug("Failed to fetch update info:", error);
49+
return {
50+
success: false,
51+
data: undefined,
52+
error: error instanceof Error ? error.message : "Unknown error",
53+
};
54+
}
3955
};

0 commit comments

Comments
 (0)