mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2026-04-16 06:18:42 +02:00
UI: Make BN-hint popups harder to be dismissed accidentally (#2021)
This commit is contained in:
@@ -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.",
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user