Skip to content

Commit e08f036

Browse files
committed
fix(frontend): harden modal state handling
1 parent bb87ee4 commit e08f036

14 files changed

Lines changed: 187 additions & 104 deletions

frontend/src/App.tsx

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -407,19 +407,27 @@ function App() {
407407
</div>
408408

409409
<TermsModal
410-
isOpen={termsOpen && !isOnboardingMode}
410+
isOpen={
411+
termsOpen && !isOnboardingMode && !isUpdatingMode
412+
}
411413
countdown={termsCountdown}
412414
onAccept={acceptTerms}
413415
/>
414416

415417
<ClarityConsentModal
416-
isOpen={clarityPromptOpen && !isOnboardingMode}
418+
isOpen={
419+
clarityPromptOpen &&
420+
!isOnboardingMode &&
421+
!isUpdatingMode
422+
}
417423
onEnable={acceptClarity}
418424
onKeepDisabled={declineClarity}
419425
/>
420426

421427
<UpdateModal
422-
isOpen={updateOpen && !isOnboardingMode}
428+
isOpen={
429+
updateOpen && !isOnboardingMode && !isUpdatingMode
430+
}
423431
version={updateVersion}
424432
body={updateBody}
425433
loading={updateLoading}
@@ -447,7 +455,11 @@ function App() {
447455
/>
448456

449457
<LipUpdateModal
450-
isOpen={lipUpdateOpen && !isOnboardingMode}
458+
isOpen={
459+
lipUpdateOpen &&
460+
!isOnboardingMode &&
461+
!isUpdatingMode
462+
}
451463
currentVersion={lipCurrentVersion}
452464
latestVersion={lipLatestVersion}
453465
onDismiss={() => {

frontend/src/components/BaseModal.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,12 @@ export const BaseModal: React.FC<ModalProps> = (props) => {
3333
className="wails-draggable fixed inset-x-0 top-0 h-14 z-[75]"
3434
/>
3535
) : null}
36-
<Modal isOpen={isOpen} {...rest} backdrop="blur" classNames={finalClassNames} />
36+
<Modal
37+
isOpen={isOpen}
38+
{...rest}
39+
backdrop="blur"
40+
classNames={finalClassNames}
41+
/>
3742
</>
3843
);
3944
};

frontend/src/components/ClarityConsentModal.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ export const ClarityConsentModal: React.FC<ClarityConsentModalProps> = ({
2121
<UnifiedModal
2222
size="lg"
2323
isOpen={isOpen}
24-
onOpenChange={() => {}}
2524
type="info"
2625
title={t("clarity.prompt.title")}
2726
hideCloseButton
@@ -48,7 +47,9 @@ export const ClarityConsentModal: React.FC<ClarityConsentModalProps> = ({
4847
size="sm"
4948
radius="full"
5049
variant="flat"
51-
onPress={() => Browser.OpenURL("https://clarity.microsoft.com/terms")}
50+
onPress={() =>
51+
Browser.OpenURL("https://clarity.microsoft.com/terms")
52+
}
5253
>
5354
{t("clarity.prompt.clarity_terms")}
5455
</Button>

frontend/src/components/TermsModal.tsx

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ export const TermsModal: React.FC<TermsModalProps> = ({
2121
<UnifiedModal
2222
size="lg"
2323
isOpen={isOpen}
24-
onOpenChange={() => {}}
2524
type="primary"
2625
title={t("terms.title")}
2726
hideCloseButton
@@ -38,11 +37,7 @@ export const TermsModal: React.FC<TermsModalProps> = ({
3837
>
3938
{t("terms.decline")}
4039
</Button>
41-
<Button
42-
color="primary"
43-
isDisabled={countdown > 0}
44-
onPress={onAccept}
45-
>
40+
<Button color="primary" isDisabled={countdown > 0} onPress={onAccept}>
4641
{countdown > 0
4742
? `${t("terms.agree")} (${countdown}s)`
4843
: t("terms.agree")}

frontend/src/components/UnifiedModal.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export type ModalType = "success" | "warning" | "error" | "info" | "primary";
2121

2222
export interface UnifiedModalProps {
2323
isOpen: boolean;
24-
onOpenChange: (open: boolean) => void;
24+
onOpenChange?: (open: boolean) => void;
2525
type?: ModalType;
2626
title: React.ReactNode;
2727
children?: React.ReactNode;
@@ -145,6 +145,7 @@ export const UnifiedModal: React.FC<UnifiedModalProps> = ({
145145
title,
146146
children,
147147
footer,
148+
hideCloseButton = false,
148149
isDismissable = false,
149150
size = "md",
150151
scrollBehavior = "inside",
@@ -167,6 +168,7 @@ export const UnifiedModal: React.FC<UnifiedModalProps> = ({
167168
const { t } = useTranslation();
168169
const config = TYPE_CONFIG[type];
169170
const Icon = config.icon;
171+
const handleOpenChange = onOpenChange ?? (() => {});
170172
const resolvedConfirmText = confirmText ?? t("common.confirm");
171173
const resolvedCancelText = cancelText ?? t("common.cancel");
172174
const resolvedConfirmButtonProps = getUnifiedModalConfirmButtonProps(type, {
@@ -191,9 +193,9 @@ export const UnifiedModal: React.FC<UnifiedModalProps> = ({
191193
return (
192194
<BaseModal
193195
isOpen={isOpen}
194-
onOpenChange={onOpenChange}
196+
onOpenChange={handleOpenChange}
195197
size={size}
196-
hideCloseButton={true}
198+
hideCloseButton={hideCloseButton}
197199
isDismissable={isDismissable}
198200
scrollBehavior={scrollBehavior}
199201
classNames={{

frontend/src/hooks/useLauncher.ts

Lines changed: 50 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ import { useDisclosure } from "@heroui/react";
33
import { Events } from "@wailsio/runtime";
44
import { useNavigate } from "react-router-dom";
55
import { compareVersions } from "@/utils/version";
6-
import { readCurrentVersionName, saveCurrentVersionName } from "@/utils/currentVersion";
6+
import {
7+
readCurrentVersionName,
8+
saveCurrentVersionName,
9+
} from "@/utils/currentVersion";
710
import * as minecraft from "bindings/github.com/liteldev/LeviLauncher/minecraft";
811
import {
912
EnsureGameInputInteractive,
@@ -21,10 +24,30 @@ import { GetMods } from "bindings/github.com/liteldev/LeviLauncher/modsservice";
2124
import { getPlayerGamertagMap, listPlayers } from "@/utils/content";
2225
import { ROUTES } from "@/constants/routes";
2326

24-
let __didCheckGameInput = false;
25-
let __didCheckGamingServices = false;
26-
let __didCheckVcRuntime = false;
2727
const IGNORE_GS_KEY = "ll.ignore.gs";
28+
const DEPENDENCY_CHECK_SESSION_KEYS = {
29+
gameInput: "ll.session.dependency-check.gameinput",
30+
gamingServices: "ll.session.dependency-check.gamingservices",
31+
vcRuntime: "ll.session.dependency-check.vcruntime",
32+
} as const;
33+
34+
const hasSessionDependencyCheckRun = (
35+
key: (typeof DEPENDENCY_CHECK_SESSION_KEYS)[keyof typeof DEPENDENCY_CHECK_SESSION_KEYS],
36+
): boolean => {
37+
try {
38+
return window.sessionStorage.getItem(key) === "1";
39+
} catch {
40+
return false;
41+
}
42+
};
43+
44+
const markSessionDependencyCheckRun = (
45+
key: (typeof DEPENDENCY_CHECK_SESSION_KEYS)[keyof typeof DEPENDENCY_CHECK_SESSION_KEYS],
46+
) => {
47+
try {
48+
window.sessionStorage.setItem(key, "1");
49+
} catch {}
50+
};
2851

2952
const getNextRandomTipIndex = (currentIndex: number, totalTips: number) => {
3053
const normalizedTotal = Math.max(1, totalTips);
@@ -282,8 +305,7 @@ export const useLauncher = (args: any) => {
282305
try {
283306
const mods = ((await (GetMods as any)?.(name)) || []) as any[];
284307
installed = mods.some(
285-
(m: any) =>
286-
String(m?.name || "").toLowerCase() === "levilamina",
308+
(m: any) => String(m?.name || "").toLowerCase() === "levilamina",
287309
);
288310
} catch {}
289311

@@ -612,8 +634,10 @@ export const useLauncher = (args: any) => {
612634
useEffect(() => {
613635
if (!hasBackend) return;
614636
const timer = setTimeout(() => {
615-
if (!__didCheckVcRuntime) {
616-
__didCheckVcRuntime = true;
637+
if (
638+
!hasSessionDependencyCheckRun(DEPENDENCY_CHECK_SESSION_KEYS.vcRuntime)
639+
) {
640+
markSessionDependencyCheckRun(DEPENDENCY_CHECK_SESSION_KEYS.vcRuntime);
617641
try {
618642
(minecraft as any)?.IsVcRuntimeInstalled?.().then((ok: boolean) => {
619643
if (!ok) {
@@ -622,8 +646,10 @@ export const useLauncher = (args: any) => {
622646
});
623647
} catch {}
624648
}
625-
if (!__didCheckGameInput) {
626-
__didCheckGameInput = true;
649+
if (
650+
!hasSessionDependencyCheckRun(DEPENDENCY_CHECK_SESSION_KEYS.gameInput)
651+
) {
652+
markSessionDependencyCheckRun(DEPENDENCY_CHECK_SESSION_KEYS.gameInput);
627653
try {
628654
minecraft?.IsGameInputInstalled?.().then((ok: boolean) => {
629655
if (!ok) {
@@ -632,8 +658,14 @@ export const useLauncher = (args: any) => {
632658
});
633659
} catch {}
634660
}
635-
if (!__didCheckGamingServices) {
636-
__didCheckGamingServices = true;
661+
if (
662+
!hasSessionDependencyCheckRun(
663+
DEPENDENCY_CHECK_SESSION_KEYS.gamingServices,
664+
)
665+
) {
666+
markSessionDependencyCheckRun(
667+
DEPENDENCY_CHECK_SESSION_KEYS.gamingServices,
668+
);
637669
try {
638670
const ig = String(localStorage.getItem(IGNORE_GS_KEY) || "") === "1";
639671
if (!ig) {
@@ -647,7 +679,12 @@ export const useLauncher = (args: any) => {
647679
}
648680
}, 0);
649681
return () => clearTimeout(timer);
650-
}, [hasBackend, gameInputMissingDisclosure, gamingServicesMissingDisclosure]);
682+
}, [
683+
hasBackend,
684+
gameInputMissingDisclosure,
685+
gamingServicesMissingDisclosure,
686+
vcRuntimeMissingDisclosure,
687+
]);
651688

652689
useEffect(() => {
653690
const unlistenGiStart = Events.On("gameinput.ensure.start", () => {
@@ -1256,5 +1293,3 @@ export const useLauncher = (args: any) => {
12561293
handleGdkMissingGoSettings,
12571294
};
12581295
};
1259-
1260-

frontend/src/pages/BehaviorPacksPage.tsx

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -597,15 +597,15 @@ export default function BehaviorPacksPage() {
597597
/>
598598

599599
<div className="flex items-center gap-3">
600-
<Dropdown classNames={COMPONENT_STYLES.dropdown}>
601-
<DropdownTrigger>
602-
<Button
603-
variant="flat"
604-
radius="full"
605-
className="min-w-[120px] bg-default-100 dark:bg-zinc-800 text-default-600 dark:text-zinc-200 font-medium"
606-
startContent={
607-
sort.sortAsc ? <FaSortAmountDown /> : <FaSortAmountUp />
608-
}
600+
<Dropdown classNames={COMPONENT_STYLES.dropdown}>
601+
<DropdownTrigger>
602+
<Button
603+
variant="flat"
604+
radius="full"
605+
className="min-w-[120px] bg-default-100 dark:bg-zinc-800 text-default-600 dark:text-zinc-200 font-medium"
606+
startContent={
607+
sort.sortAsc ? <FaSortAmountDown /> : <FaSortAmountUp />
608+
}
609609
>
610610
{sort.sortKey === "name"
611611
? (t("filemanager.sort.name") as string)
@@ -666,9 +666,7 @@ export default function BehaviorPacksPage() {
666666
</span>
667667
<span className="text-default-300 dark:text-zinc-700">|</span>
668668
<span>{t("contentpage.isolation")}:</span>
669-
<span
670-
className="font-medium text-default-700 dark:text-zinc-200 bg-default-100 dark:bg-zinc-800 px-2 py-0.5 rounded-md"
671-
>
669+
<span className="font-medium text-default-700 dark:text-zinc-200 bg-default-100 dark:bg-zinc-800 px-2 py-0.5 rounded-md">
672670
{roots.isIsolation ? t("common.yes") : t("common.no")}
673671
</span>
674672
</div>
@@ -878,7 +876,6 @@ export default function BehaviorPacksPage() {
878876

879877
<UnifiedModal
880878
isOpen={transferring}
881-
onOpenChange={() => {}}
882879
type="primary"
883880
title={t("contentpage.transfer_progress_title")}
884881
icon={<FaExchangeAlt className="w-6 h-6" />}

frontend/src/pages/ContentPage.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,7 @@ export default function ContentPage() {
8787
|
8888
</span>
8989
<span>{t("contentpage.isolation")}:</span>
90-
<span
91-
className="font-medium text-default-700 dark:text-zinc-200 bg-default-100 dark:bg-zinc-800 px-2 py-0.5 rounded-md"
92-
>
90+
<span className="font-medium text-default-700 dark:text-zinc-200 bg-default-100 dark:bg-zinc-800 px-2 py-0.5 rounded-md">
9391
{cp.roots.isIsolation ? t("common.yes") : t("common.no")}
9492
</span>
9593
<span className="text-default-300 dark:text-zinc-600">
@@ -520,7 +518,6 @@ export default function ContentPage() {
520518

521519
<UnifiedModal
522520
isOpen={cp.importing}
523-
onOpenChange={() => {}}
524521
type="primary"
525522
title={
526523
cp.transferring

0 commit comments

Comments
 (0)