UI: Make BN-hint popups harder to be dismissed accidentally (#2021)

This commit is contained in:
catloversg
2025-03-23 13:59:38 +07:00
committed by GitHub
parent 17ffabdfa5
commit 41c497161f
6 changed files with 55 additions and 26 deletions

View File

@@ -3,7 +3,6 @@ import { FactionName } from "@enums";
import { Router } from "../../ui/GameRoot";
import { Page } from "../../ui/Router";
import { CinematicText } from "../../ui/React/CinematicText";
import { dialogBoxCreate } from "../../ui/React/DialogBox";
export function BladeburnerCinematic(): React.ReactElement {
return (
@@ -33,10 +32,6 @@ export function BladeburnerCinematic(): React.ReactElement {
]}
onDone={() => {
Router.toPage(Page.Terminal);
dialogBoxCreate(
`Visit the National Security Agency (NSA) to apply for their ${FactionName.Bladeburners} ` +
"division! You will need 100 of each combat stat before doing this.",
);
}}
/>
);

View File

@@ -1648,7 +1648,7 @@ export const ns: InternalAPI<NSFull> = {
},
alert: (ctx) => (_message) => {
const message = helpers.string(ctx, "message", _message);
dialogBoxCreate(message, true);
dialogBoxCreate(message, { html: true, canBeDismissedEasily: true });
},
toast:
(ctx) =>

View File

@@ -32,8 +32,8 @@ import { canAccessBitNodeFeature } from "./BitNode/BitNodeUtils";
import { pendingUIShareJobIds } from "./NetworkShare/Share";
const BitNode8StartingMoney = 250e6;
function delayedDialog(message: string) {
setTimeout(() => dialogBoxCreate(message), 200);
function delayedDialog(message: string, canBeDismissedEasily = true) {
setTimeout(() => dialogBoxCreate(message, { html: false, canBeDismissedEasily }), 200);
}
function setInitialExpForPlayer() {
@@ -268,12 +268,16 @@ export function prestigeSourceFile(isFlume: boolean): void {
"You received a copy of the Corporation Management Handbook on your home computer. It's a short introduction for " +
"managing Corporation.\n\nYou should check the in-game Corporation documentation in the Documentation tab " +
"(Documentation -> Advanced Mechanics -> Corporation). It's the most useful and up-to-date resource for managing Corporation.",
false,
);
}
// BitNode 6: Bladeburners and BitNode 7: Bladeburners 2079
if (Player.bitNodeN === 6 || Player.bitNodeN === 7) {
delayedDialog(`The ${CompanyName.NSA} would like to have a word with you once you're ready.`);
delayedDialog(
`The ${CompanyName.NSA} would like to have a word with you once you're ready. You should train your combat stats to level 100 before going there.`,
false,
);
}
// BitNode 8: Ghost of Wall Street
@@ -289,6 +293,7 @@ export function prestigeSourceFile(isFlume: boolean): void {
if (Player.bitNodeN === 10) {
delayedDialog(
`Seek out ${FactionName.TheCovenant} if you'd like to purchase a new sleeve or two! And see what ${CompanyName.VitaLife} in ${CityName.NewTokyo} has to offer for you`,
false,
);
}
@@ -298,7 +303,7 @@ export function prestigeSourceFile(isFlume: boolean): void {
}
if (Player.bitNodeN === 13) {
delayedDialog(`Trouble is brewing in ${CityName.Chongqing}`);
delayedDialog(`Trouble is brewing in ${CityName.Chongqing}`, false);
}
// Reset Stock market, gang, and corporation
@@ -343,6 +348,7 @@ export function prestigeSourceFile(isFlume: boolean): void {
if (!isFlume && Player.sourceFiles.size === 1 && Player.sourceFileLvl(1) === 1) {
delayedDialog(
"Congratulations on destroying your first BitNode! Make sure to check the Documentation tab. Many pages are unlocked now.",
false,
);
}
}

View File

@@ -4,31 +4,34 @@ import { Modal } from "./Modal";
import Typography from "@mui/material/Typography";
import Box from "@mui/material/Box";
import { cyrb53 } from "../../utils/HashUtils";
import Button from "@mui/material/Button";
export const AlertEvents = new EventEmitter<[string | JSX.Element]>();
export const AlertEvents = new EventEmitter<[string | JSX.Element, boolean?]>();
interface Alert {
text: string | JSX.Element;
hash: string;
/**
* If it's true, the player can dismiss the modal by pressing the Esc button or clicking on the backdrop.
*
* Note that there are 2 different behaviors when pressing the Esc button, depending on whether the player focused on
* the modal. If they focused on the modal and canBeDismissedEasily is false, the modal would not be dismissed. If
* they did not, pressing the Esc button would always dismiss **all** popups in the queue maintained by this manager.
*/
canBeDismissedEasily: boolean;
}
export function AlertManager({ hidden }: { hidden: boolean }): React.ReactElement {
const [alerts, setAlerts] = useState<Alert[]>([]);
useEffect(
() =>
AlertEvents.subscribe((text: string | JSX.Element) => {
AlertEvents.subscribe((text: string | JSX.Element, canBeDismissedEasily = true) => {
const hash = getMessageHash(text);
setAlerts((old) => {
if (old.some((a) => a.hash === hash)) {
return old;
}
return [
...old,
{
text: text,
hash: hash,
},
];
return [...old, { text, hash, canBeDismissedEasily }];
});
}),
[],
@@ -36,15 +39,17 @@ export function AlertManager({ hidden }: { hidden: boolean }): React.ReactElemen
useEffect(() => {
function handle(this: Document, event: KeyboardEvent): void {
if (event.code === "Escape") {
setAlerts([]);
if (event.code !== "Escape") {
return;
}
setAlerts([]);
}
document.addEventListener("keydown", handle);
return () => document.removeEventListener("keydown", handle);
}, []);
}, [alerts]);
const alertMessage = alerts[0]?.text || "No alert to show";
const canBeDismissedEasily = alerts[0]?.canBeDismissedEasily;
function getMessageHash(text: string | JSX.Element): string {
if (typeof text === "string") {
@@ -75,10 +80,15 @@ export function AlertManager({ hidden }: { hidden: boolean }): React.ReactElemen
}
return (
<Modal open={!hidden && alerts.length > 0} onClose={close}>
<Modal open={!hidden && alerts.length > 0} onClose={close} canBeDismissedEasily={canBeDismissedEasily}>
<Box overflow="scroll" sx={{ overflowWrap: "break-word", whiteSpace: "pre-line" }}>
<Typography component={"span"}>{alertMessage}</Typography>
</Box>
{!canBeDismissedEasily && (
<Button onClick={close} sx={{ marginTop: "10px" }}>
OK
</Button>
)}
</Modal>
);
}

View File

@@ -3,7 +3,10 @@ import { AlertEvents } from "./AlertManager";
import React from "react";
import { Typography } from "@mui/material";
export function dialogBoxCreate(txt: string | JSX.Element, html = false): void {
export function dialogBoxCreate(
txt: string | JSX.Element,
{ html, canBeDismissedEasily } = { html: false, canBeDismissedEasily: true },
): void {
AlertEvents.emit(
typeof txt !== "string" ? (
txt
@@ -14,5 +17,6 @@ export function dialogBoxCreate(txt: string | JSX.Element, html = false): void {
{txt}
</Typography>
),
canBeDismissedEasily,
);
}

View File

@@ -44,9 +44,18 @@ interface ModalProps {
children: React.ReactNode;
sx?: SxProps<Theme>;
removeFocus?: boolean;
// If it's true, the player can dismiss the modal by pressing the Esc button or clicking on the backdrop.
canBeDismissedEasily?: boolean;
}
export const Modal = ({ open, onClose, children, sx, removeFocus = true }: ModalProps): React.ReactElement => {
export const Modal = ({
open,
onClose,
children,
sx,
removeFocus = true,
canBeDismissedEasily = true,
}: ModalProps): React.ReactElement => {
const { classes } = useStyles();
const [content, setContent] = useState(children);
useEffect(() => {
@@ -61,7 +70,12 @@ export const Modal = ({ open, onClose, children, sx, removeFocus = true }: Modal
disableEnforceFocus
disableAutoFocus={removeFocus}
open={open}
onClose={onClose}
onClose={() => {
if (!canBeDismissedEasily) {
return;
}
onClose();
}}
closeAfterTransition
className={classes.modal}
sx={sx}