Allow drag on character overview

Makes the character overview (and tutorial) draggable, persisting the
{x, y, opened} in the user's settings.

- Remove margin & padding from html, body and ensure main content is
full height
- Add setting to disable progress bars
- Refactor options to use new OptionSwitch
- Add exclusions to prettierignore
- Specify line ending in prettier & gitattributes
This commit is contained in:
Martin Fournier 2022-01-09 11:15:09 -05:00
parent a53b36ed27
commit 7ee2612c17
12 changed files with 375 additions and 371 deletions

2
.gitattributes vendored

@ -1 +1 @@
* text=auto * text=auto eol=lf

@ -2,3 +2,14 @@ node_modules
package.json package.json
dist dist
doc/build/ doc/build/
doc/source
.build
.package
editor.main.js
main.bundle.js
index.html
markdown
package.json
package.lock.json

@ -1,5 +1,6 @@
{ {
"trailingComma": "all", "trailingComma": "all",
"endOfLine": "lf",
"tabWidth": 2, "tabWidth": 2,
"printWidth": 120 "printWidth": 120
} }

@ -2,7 +2,8 @@ import { ISelfInitializer, ISelfLoading } from "../types";
import { OwnedAugmentationsOrderSetting, PurchaseAugmentationsOrderSetting } from "./SettingEnums"; import { OwnedAugmentationsOrderSetting, PurchaseAugmentationsOrderSetting } from "./SettingEnums";
import { defaultTheme, ITheme } from "./Themes"; import { defaultTheme, ITheme } from "./Themes";
import { defaultStyles, IStyleSettings } from "./Styles"; import { defaultStyles, IStyleSettings } from "./Styles";
import { WordWrapOptions } from '../ScriptEditor/ui/Options'; import { WordWrapOptions } from "../ScriptEditor/ui/Options";
import { OverviewSettings } from "../ui/React/Overview";
/** /**
* Represents the default settings the player could customize. * Represents the default settings the player could customize.
@ -41,6 +42,11 @@ interface IDefaultSettings {
*/ */
DisableTextEffects: boolean; DisableTextEffects: boolean;
/**
* Whether overview progress bars should be visible.
*/
DisableOverviewProgressBars: boolean;
/** /**
* Enable bash hotkeys * Enable bash hotkeys
*/ */
@ -125,6 +131,11 @@ interface IDefaultSettings {
* Use GiB instead of GB * Use GiB instead of GB
*/ */
UseIEC60027_2: boolean; UseIEC60027_2: boolean;
/*
* Character overview settings
*/
overview: OverviewSettings;
} }
/** /**
@ -160,6 +171,7 @@ export const defaultSettings: IDefaultSettings = {
DisableASCIIArt: false, DisableASCIIArt: false,
DisableHotkeys: false, DisableHotkeys: false,
DisableTextEffects: false, DisableTextEffects: false,
DisableOverviewProgressBars: false,
EnableBashHotkeys: false, EnableBashHotkeys: false,
TimestampsFormat: "", TimestampsFormat: "",
Locale: "en", Locale: "en",
@ -178,6 +190,7 @@ export const defaultSettings: IDefaultSettings = {
theme: defaultTheme, theme: defaultTheme,
styles: defaultStyles, styles: defaultStyles,
overview: { x: 0, y: 0, opened: true },
}; };
/** /**
@ -192,6 +205,7 @@ export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
DisableASCIIArt: defaultSettings.DisableASCIIArt, DisableASCIIArt: defaultSettings.DisableASCIIArt,
DisableHotkeys: defaultSettings.DisableHotkeys, DisableHotkeys: defaultSettings.DisableHotkeys,
DisableTextEffects: defaultSettings.DisableTextEffects, DisableTextEffects: defaultSettings.DisableTextEffects,
DisableOverviewProgressBars: defaultSettings.DisableOverviewProgressBars,
EnableBashHotkeys: defaultSettings.EnableBashHotkeys, EnableBashHotkeys: defaultSettings.EnableBashHotkeys,
TimestampsFormat: defaultSettings.TimestampsFormat, TimestampsFormat: defaultSettings.TimestampsFormat,
Locale: "en", Locale: "en",
@ -213,10 +227,11 @@ export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
MonacoInsertSpaces: false, MonacoInsertSpaces: false,
MonacoFontSize: 20, MonacoFontSize: 20,
MonacoVim: false, MonacoVim: false,
MonacoWordWrap: 'off', MonacoWordWrap: "off",
theme: { ...defaultTheme }, theme: { ...defaultTheme },
styles: { ...defaultStyles }, styles: { ...defaultStyles },
overview: defaultSettings.overview,
init() { init() {
Object.assign(Settings, defaultSettings); Object.assign(Settings, defaultSettings);
}, },
@ -226,6 +241,8 @@ export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
delete save.theme; delete save.theme;
Object.assign(Settings.styles, save.styles); Object.assign(Settings.styles, save.styles);
delete save.styles; delete save.styles;
Object.assign(Settings.overview, save.overview);
delete save.overview;
Object.assign(Settings, save); Object.assign(Settings, save);
}, },
}; };

@ -5,8 +5,6 @@ import { BaseServer } from "../../Server/BaseServer";
import { getServerOnNetwork } from "../../Server/ServerHelpers"; import { getServerOnNetwork } from "../../Server/ServerHelpers";
import { GetServer } from "../../Server/AllServers"; import { GetServer } from "../../Server/AllServers";
import { Server } from "../../Server/Server"; import { Server } from "../../Server/Server";
import { Programs } from "src/Programs/Programs";
import { programsMetadata } from "src/Programs/data/ProgramsMetadata";
export function connect( export function connect(
terminal: ITerminal, terminal: ITerminal,

@ -52,6 +52,11 @@
</script> </script>
<% } %> <% } %>
<style> <style>
html, body {
margin: 0;
padding: 0;
height: 100%;
}
body { body {
background-color: black; background-color: black;
} }

@ -77,7 +77,6 @@ import { enterBitNode } from "../RedPill";
import { Context } from "./Context"; import { Context } from "./Context";
import { RecoveryMode, RecoveryRoot } from "./React/RecoveryRoot"; import { RecoveryMode, RecoveryRoot } from "./React/RecoveryRoot";
import { AchievementsRoot } from "../Achievements/AchievementsRoot"; import { AchievementsRoot } from "../Achievements/AchievementsRoot";
import { Settings } from "../Settings/Settings";
const htmlLocation = location; const htmlLocation = location;
@ -93,6 +92,10 @@ const useStyles = makeStyles((theme: Theme) =>
"-ms-overflow-style": "none" /* for Internet Explorer, Edge */, "-ms-overflow-style": "none" /* for Internet Explorer, Edge */,
"scrollbar-width": "none" /* for Firefox */, "scrollbar-width": "none" /* for Firefox */,
margin: theme.spacing(0), margin: theme.spacing(0),
flexGrow: 1,
display: "block",
padding: "8px",
minHeight: "100vh",
}, },
}), }),
); );
@ -187,7 +190,7 @@ export let Router: IRouter = {
}, },
toAchievements: () => { toAchievements: () => {
throw new Error("Router called before initialization"); throw new Error("Router called before initialization");
} },
}; };
function determineStartPage(player: IPlayer): Page { function determineStartPage(player: IPlayer): Page {
@ -198,7 +201,7 @@ function determineStartPage(player: IPlayer): Page {
export function GameRoot({ player, engine, terminal }: IProps): React.ReactElement { export function GameRoot({ player, engine, terminal }: IProps): React.ReactElement {
const classes = useStyles(); const classes = useStyles();
const [{files, vim}, setEditorOptions] = useState({files: {}, vim: false}) const [{ files, vim }, setEditorOptions] = useState({ files: {}, vim: false });
const [page, setPage] = useState(determineStartPage(player)); const [page, setPage] = useState(determineStartPage(player));
const setRerender = useState(0)[1]; const setRerender = useState(0)[1];
const [faction, setFaction] = useState<Faction>( const [faction, setFaction] = useState<Faction>(
@ -315,7 +318,7 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
mainPage = <BitverseRoot flume={flume} enter={enterBitNode} quick={quick} />; mainPage = <BitverseRoot flume={flume} enter={enterBitNode} quick={quick} />;
withSidebar = false; withSidebar = false;
withPopups = false; withPopups = false;
break break;
} }
case Page.Infiltration: { case Page.Infiltration: {
mainPage = <InfiltrationRoot location={location} />; mainPage = <InfiltrationRoot location={location} />;
@ -351,13 +354,15 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
break; break;
} }
case Page.ScriptEditor: { case Page.ScriptEditor: {
mainPage = <ScriptEditorRoot mainPage = (
files={files} <ScriptEditorRoot
hostname={player.getCurrentServer().hostname} files={files}
player={player} hostname={player.getCurrentServer().hostname}
router={Router} player={player}
vim={vim} router={Router}
/>; vim={vim}
/>
);
break; break;
} }
case Page.ActiveScripts: { case Page.ActiveScripts: {
@ -385,13 +390,15 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
break; break;
} }
case Page.Tutorial: { case Page.Tutorial: {
mainPage = <TutorialRoot mainPage = (
reactivateTutorial={() => { <TutorialRoot
prestigeAugmentation(); reactivateTutorial={() => {
Router.toTerminal(); prestigeAugmentation();
iTutorialStart(); Router.toTerminal();
}} iTutorialStart();
/>; }}
/>
);
break; break;
} }
case Page.DevMenu: { case Page.DevMenu: {
@ -419,18 +426,20 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
break; break;
} }
case Page.StockMarket: { case Page.StockMarket: {
mainPage = <StockMarketRoot mainPage = (
buyStockLong={buyStock} <StockMarketRoot
buyStockShort={shortStock} buyStockLong={buyStock}
cancelOrder={cancelOrder} buyStockShort={shortStock}
eventEmitterForReset={eventEmitterForUiReset} cancelOrder={cancelOrder}
initStockMarket={initStockMarketFnForReact} eventEmitterForReset={eventEmitterForUiReset}
p={player} initStockMarket={initStockMarketFnForReact}
placeOrder={placeOrder} p={player}
sellStockLong={sellStock} placeOrder={placeOrder}
sellStockShort={sellShort} sellStockLong={sellStock}
stockMarket={StockMarket} sellStockShort={sellShort}
/>; stockMarket={StockMarket}
/>
);
break; break;
} }
case Page.City: { case Page.City: {
@ -443,35 +452,39 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
break; break;
} }
case Page.Options: { case Page.Options: {
mainPage = <GameOptionsRoot mainPage = (
player={player} <GameOptionsRoot
save={() => saveObject.saveGame()} player={player}
export={() => { save={() => saveObject.saveGame()}
// Apply the export bonus before saving the game export={() => {
onExport(player); // Apply the export bonus before saving the game
saveObject.exportGame() onExport(player);
}} saveObject.exportGame();
forceKill={killAllScripts} }}
softReset={() => { forceKill={killAllScripts}
dialogBoxCreate("Soft Reset!"); softReset={() => {
prestigeAugmentation(); dialogBoxCreate("Soft Reset!");
Router.toTerminal(); prestigeAugmentation();
}} Router.toTerminal();
/>; }}
/>
);
break; break;
} }
case Page.Augmentations: { case Page.Augmentations: {
mainPage = <AugmentationsRoot mainPage = (
exportGameFn={() => { <AugmentationsRoot
// Apply the export bonus before saving the game exportGameFn={() => {
onExport(player); // Apply the export bonus before saving the game
saveObject.exportGame(); onExport(player);
}} saveObject.exportGame();
installAugmentationsFn={() => { }}
installAugmentations(); installAugmentationsFn={() => {
Router.toTerminal(); installAugmentations();
}} Router.toTerminal();
/>; }}
/>
);
break; break;
} }
case Page.Achievements: { case Page.Achievements: {
@ -484,7 +497,7 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
<Context.Player.Provider value={player}> <Context.Player.Provider value={player}>
<Context.Router.Provider value={Router}> <Context.Router.Provider value={Router}>
<SnackbarProvider> <SnackbarProvider>
<Overview> <Overview mode={ITutorial.isRunning ? "tutorial" : "overview"}>
{!ITutorial.isRunning ? ( {!ITutorial.isRunning ? (
<CharacterOverview save={() => saveObject.saveGame()} killScripts={killAllScripts} /> <CharacterOverview save={() => saveObject.saveGame()} killScripts={killAllScripts} />
) : ( ) : (
@ -494,19 +507,19 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
{withSidebar ? ( {withSidebar ? (
<Box display="flex" flexDirection="row" width="100%"> <Box display="flex" flexDirection="row" width="100%">
<SidebarRoot player={player} router={Router} page={page} /> <SidebarRoot player={player} router={Router} page={page} />
<Box className={classes.root} flexGrow={1} display="block" px={1} min-height="100vh"> <Box className={classes.root}>{mainPage}</Box>
{mainPage}
</Box>
</Box> </Box>
) : mainPage } ) : (
<Box className={classes.root}>{mainPage}</Box>
)}
<Unclickable /> <Unclickable />
{withPopups && ( {withPopups && (
<> <>
<LogBoxManager /> <LogBoxManager />
<AlertManager /> <AlertManager />
<PromptManager /> <PromptManager />
<InvitationModal /> <InvitationModal />
<Snackbar /> <Snackbar />
</> </>
)} )}
</SnackbarProvider> </SnackbarProvider>

@ -12,7 +12,6 @@ import { CopyableText } from "../React/CopyableText";
import ListItem from "@mui/material/ListItem"; import ListItem from "@mui/material/ListItem";
import EqualizerIcon from "@mui/icons-material/Equalizer"; import EqualizerIcon from "@mui/icons-material/Equalizer";
import LastPageIcon from "@mui/icons-material/LastPage"; import LastPageIcon from "@mui/icons-material/LastPage";
import VisibilityOffIcon from "@mui/icons-material/VisibilityOff";
import HelpIcon from "@mui/icons-material/Help"; import HelpIcon from "@mui/icons-material/Help";
import AccountTreeIcon from "@mui/icons-material/AccountTree"; import AccountTreeIcon from "@mui/icons-material/AccountTree";
import StorageIcon from "@mui/icons-material/Storage"; import StorageIcon from "@mui/icons-material/Storage";
@ -61,7 +60,7 @@ export function InteractiveTutorialRoot(): React.ReactElement {
This tutorial will show you the basics of the game. You may skip the tutorial at any time. This tutorial will show you the basics of the game. You may skip the tutorial at any time.
<br /> <br />
<br /> <br />
You can also click the eye symbol <VisibilityOffIcon /> to temporarily hide this tutorial. You can also collapse this panel to temporarily hide this tutorial.
</Typography> </Typography>
</> </>
), ),

@ -287,7 +287,9 @@ export function CharacterOverview({ save, killScripts }: IProps): React.ReactEle
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<StatsProgressOverviewCell progress={hackingProgress} color={theme.colors.hack} /> {!Settings.DisableOverviewProgressBars && (
<StatsProgressOverviewCell progress={hackingProgress} color={theme.colors.hack} />
)}
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cell }}> <TableCell component="th" scope="row" classes={{ root: classes.cell }}>
@ -314,7 +316,9 @@ export function CharacterOverview({ save, killScripts }: IProps): React.ReactEle
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<StatsProgressOverviewCell progress={strengthProgress} color={theme.colors.combat} /> {!Settings.DisableOverviewProgressBars && (
<StatsProgressOverviewCell progress={strengthProgress} color={theme.colors.combat} />
)}
</TableRow> </TableRow>
<TableRow> <TableRow>
@ -331,7 +335,9 @@ export function CharacterOverview({ save, killScripts }: IProps): React.ReactEle
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<StatsProgressOverviewCell progress={defenseProgress} color={theme.colors.combat} /> {!Settings.DisableOverviewProgressBars && (
<StatsProgressOverviewCell progress={defenseProgress} color={theme.colors.combat} />
)}
</TableRow> </TableRow>
<TableRow> <TableRow>
@ -348,7 +354,9 @@ export function CharacterOverview({ save, killScripts }: IProps): React.ReactEle
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<StatsProgressOverviewCell progress={dexterityProgress} color={theme.colors.combat} /> {!Settings.DisableOverviewProgressBars && (
<StatsProgressOverviewCell progress={dexterityProgress} color={theme.colors.combat} />
)}
</TableRow> </TableRow>
<TableRow> <TableRow>
@ -365,7 +373,9 @@ export function CharacterOverview({ save, killScripts }: IProps): React.ReactEle
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<StatsProgressOverviewCell progress={agilityProgress} color={theme.colors.combat} /> {!Settings.DisableOverviewProgressBars && (
<StatsProgressOverviewCell progress={agilityProgress} color={theme.colors.combat} />
)}
</TableRow> </TableRow>
<TableRow> <TableRow>
@ -382,7 +392,9 @@ export function CharacterOverview({ save, killScripts }: IProps): React.ReactEle
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<StatsProgressOverviewCell progress={charismaProgress} color={theme.colors.cha} /> {!Settings.DisableOverviewProgressBars && (
<StatsProgressOverviewCell progress={charismaProgress} color={theme.colors.cha} />
)}
</TableRow> </TableRow>
<Intelligence /> <Intelligence />

@ -8,8 +8,6 @@ import createStyles from "@mui/styles/createStyles";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import Slider from "@mui/material/Slider"; import Slider from "@mui/material/Slider";
import Grid from "@mui/material/Grid"; import Grid from "@mui/material/Grid";
import FormControlLabel from "@mui/material/FormControlLabel";
import Switch from "@mui/material/Switch";
import Select, { SelectChangeEvent } from "@mui/material/Select"; import Select, { SelectChangeEvent } from "@mui/material/Select";
import MenuItem from "@mui/material/MenuItem"; import MenuItem from "@mui/material/MenuItem";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
@ -35,6 +33,7 @@ import { SnackbarEvents } from "./Snackbar";
import { Settings } from "../../Settings/Settings"; import { Settings } from "../../Settings/Settings";
import { save, deleteGame } from "../../db"; import { save, deleteGame } from "../../db";
import { formatTime } from "../../utils/helpers/formatTime"; import { formatTime } from "../../utils/helpers/formatTime";
import { OptionSwitch } from "./OptionSwitch";
const useStyles = makeStyles((theme: Theme) => const useStyles = makeStyles((theme: Theme) =>
createStyles({ createStyles({
@ -68,27 +67,8 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
const [logSize, setLogSize] = useState(Settings.MaxLogCapacity); const [logSize, setLogSize] = useState(Settings.MaxLogCapacity);
const [portSize, setPortSize] = useState(Settings.MaxPortCapacity); const [portSize, setPortSize] = useState(Settings.MaxPortCapacity);
const [terminalSize, setTerminalSize] = useState(Settings.MaxTerminalCapacity); const [terminalSize, setTerminalSize] = useState(Settings.MaxTerminalCapacity);
const [autosaveInterval, setAutosaveInterval] = useState(Settings.AutosaveInterval); const [autosaveInterval, setAutosaveInterval] = useState(Settings.AutosaveInterval);
const [suppressMessages, setSuppressMessages] = useState(Settings.SuppressMessages);
const [suppressFactionInvites, setSuppressFactionInvites] = useState(Settings.SuppressFactionInvites);
const [suppressTravelConfirmations, setSuppressTravelConfirmations] = useState(Settings.SuppressTravelConfirmation);
const [suppressBuyAugmentationConfirmation, setSuppressBuyAugmentationConfirmation] = useState(
Settings.SuppressBuyAugmentationConfirmation,
);
const [suppressTIXPopup, setSuppressTIXPopup] = useState(Settings.SuppressTIXPopup);
const [suppressBladeburnerPopup, setSuppressBladeburnerPopup] = useState(Settings.SuppressBladeburnerPopup);
const [suppressSavedGameToast, setSuppresSavedGameToast] = useState(Settings.SuppressSavedGameToast);
const [disableHotkeys, setDisableHotkeys] = useState(Settings.DisableHotkeys);
const [disableASCIIArt, setDisableASCIIArt] = useState(Settings.DisableASCIIArt);
const [disableTextEffects, setDisableTextEffects] = useState(Settings.DisableTextEffects);
const [enableBashHotkeys, setEnableBashHotkeys] = useState(Settings.EnableBashHotkeys);
const [timestampFormat, setTimestampFormat] = useState(Settings.TimestampsFormat); const [timestampFormat, setTimestampFormat] = useState(Settings.TimestampsFormat);
const [saveGameOnFileSave, setSaveGameOnFileSave] = useState(Settings.SaveGameOnFileSave);
const [useIEC60027_2, setUseIEC60027_2] = useState(Settings.UseIEC60027_2);
const [locale, setLocale] = useState(Settings.Locale); const [locale, setLocale] = useState(Settings.Locale);
const [diagnosticOpen, setDiagnosticOpen] = useState(false); const [diagnosticOpen, setDiagnosticOpen] = useState(false);
const [deleteGameOpen, setDeleteOpen] = useState(false); const [deleteGameOpen, setDeleteOpen] = useState(false);
@ -123,76 +103,15 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
Settings.AutosaveInterval = newValue as number; Settings.AutosaveInterval = newValue as number;
} }
function handleSuppressMessagesChange(event: React.ChangeEvent<HTMLInputElement>): void {
setSuppressMessages(event.target.checked);
Settings.SuppressMessages = event.target.checked;
}
function handleSuppressFactionInvitesChange(event: React.ChangeEvent<HTMLInputElement>): void {
setSuppressFactionInvites(event.target.checked);
Settings.SuppressFactionInvites = event.target.checked;
}
function handleSuppressTravelConfirmationsChange(event: React.ChangeEvent<HTMLInputElement>): void {
setSuppressTravelConfirmations(event.target.checked);
Settings.SuppressTravelConfirmation = event.target.checked;
}
function handleSuppressBuyAugmentationConfirmationChange(event: React.ChangeEvent<HTMLInputElement>): void {
setSuppressBuyAugmentationConfirmation(event.target.checked);
Settings.SuppressBuyAugmentationConfirmation = event.target.checked;
}
function handleSuppressTIXPopupChange(event: React.ChangeEvent<HTMLInputElement>): void {
setSuppressTIXPopup(event.target.checked);
Settings.SuppressTIXPopup = event.target.checked;
}
function handleSuppressBladeburnerPopupChange(event: React.ChangeEvent<HTMLInputElement>): void {
setSuppressBladeburnerPopup(event.target.checked);
Settings.SuppressBladeburnerPopup = event.target.checked;
}
function handleSuppressSavedGameToastChange(event: React.ChangeEvent<HTMLInputElement>): void {
setSuppresSavedGameToast(event.target.checked);
Settings.SuppressSavedGameToast = event.target.checked;
}
function handleDisableHotkeysChange(event: React.ChangeEvent<HTMLInputElement>): void {
setDisableHotkeys(event.target.checked);
Settings.DisableHotkeys = event.target.checked;
}
function handleDisableASCIIArtChange(event: React.ChangeEvent<HTMLInputElement>): void {
setDisableASCIIArt(event.target.checked);
Settings.DisableASCIIArt = event.target.checked;
}
function handleUseIEC60027_2Change(event: React.ChangeEvent<HTMLInputElement>): void {
setUseIEC60027_2(event.target.checked);
Settings.UseIEC60027_2 = event.target.checked;
}
function handleDisableTextEffectsChange(event: React.ChangeEvent<HTMLInputElement>): void {
setDisableTextEffects(event.target.checked);
Settings.DisableTextEffects = event.target.checked;
}
function handleLocaleChange(event: SelectChangeEvent<string>): void { function handleLocaleChange(event: SelectChangeEvent<string>): void {
setLocale(event.target.value as string); setLocale(event.target.value as string);
Settings.Locale = event.target.value as string; Settings.Locale = event.target.value as string;
} }
function handleEnableBashHotkeysChange(event: React.ChangeEvent<HTMLInputElement>): void {
setEnableBashHotkeys(event.target.checked);
Settings.EnableBashHotkeys = event.target.checked;
}
function handleTimestampFormatChange(event: React.ChangeEvent<HTMLInputElement>): void { function handleTimestampFormatChange(event: React.ChangeEvent<HTMLInputElement>): void {
setTimestampFormat(event.target.value); setTimestampFormat(event.target.value);
Settings.TimestampsFormat = event.target.value; Settings.TimestampsFormat = event.target.value;
} }
function handleSaveGameOnFile(event: React.ChangeEvent<HTMLInputElement>): void {
setSaveGameOnFileSave(event.target.checked);
Settings.SaveGameOnFileSave = event.target.checked;
}
function startImport(): void { function startImport(): void {
if (!window.File || !window.FileReader || !window.FileList || !window.Blob) return; if (!window.File || !window.FileReader || !window.FileList || !window.Blob) return;
@ -392,200 +311,120 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
/> />
</ListItem> </ListItem>
<ListItem> <ListItem>
<FormControlLabel <OptionSwitch checked={Settings.SuppressMessages}
control={<Switch checked={suppressMessages} onChange={handleSuppressMessagesChange} />} onChange={(newValue) => Settings.SuppressMessages = newValue}
label={ text="Suppress story messages"
<Tooltip tooltip={<>
title={ If this is set, then any messages you receive will not appear as popups on the screen. They will
<Typography> still get sent to your home computer as '.msg' files and can be viewed with the 'cat' Terminal
If this is set, then any messages you receive will not appear as popups on the screen. They will command.
still get sent to your home computer as '.msg' files and can be viewed with the 'cat' Terminal </>} />
command.
</Typography>
}
>
<Typography>Suppress story messages</Typography>
</Tooltip>
}
/>
</ListItem> </ListItem>
<ListItem> <ListItem>
<FormControlLabel <OptionSwitch checked={Settings.SuppressFactionInvites}
control={<Switch checked={suppressFactionInvites} onChange={handleSuppressFactionInvitesChange} />} onChange={(newValue) => Settings.SuppressFactionInvites = newValue}
label={ text="Suppress faction invites"
<Tooltip tooltip={<>
title={ If this is set, then any faction invites you receive will not appear as popups on the screen.
<Typography> Your outstanding faction invites can be viewed in the 'Factions' page.
If this is set, then any faction invites you receive will not appear as popups on the screen. </>} />
Your outstanding faction invites can be viewed in the 'Factions' page.
</Typography>
}
>
<Typography>Suppress faction invites</Typography>
</Tooltip>
}
/>
</ListItem> </ListItem>
<ListItem> <ListItem>
<FormControlLabel <OptionSwitch checked={Settings.SuppressTravelConfirmation}
control={ onChange={(newValue) => Settings.SuppressTravelConfirmation = newValue}
<Switch checked={suppressTravelConfirmations} onChange={handleSuppressTravelConfirmationsChange} /> text="Suppress travel confirmations"
} tooltip={<>
label={ If this is set, the confirmation message before traveling will not show up. You will
<Tooltip automatically be deducted the travel cost as soon as you click.
title={ </>} />
<Typography>
If this is set, the confirmation message before traveling will not show up. You will
automatically be deducted the travel cost as soon as you click.
</Typography>
}
>
<Typography>Suppress travel confirmations</Typography>
</Tooltip>
}
/>
</ListItem> </ListItem>
<ListItem> <ListItem>
<FormControlLabel <OptionSwitch checked={Settings.SuppressBuyAugmentationConfirmation}
control={ onChange={(newValue) => Settings.SuppressBuyAugmentationConfirmation = newValue}
<Switch text="Suppress augmentations confirmation"
checked={suppressBuyAugmentationConfirmation} tooltip={<>
onChange={handleSuppressBuyAugmentationConfirmationChange} If this is set, the confirmation message before buying augmentation will not show up.
/> </>} />
}
label={
<Tooltip
title={
<Typography>
If this is set, the confirmation message before buying augmentation will not show up.
</Typography>
}
>
<Typography>Suppress augmentations confirmation</Typography>
</Tooltip>
}
/>
</ListItem> </ListItem>
<ListItem> <ListItem>
<FormControlLabel <OptionSwitch checked={Settings.SuppressTIXPopup}
control={<Switch checked={suppressTIXPopup} onChange={handleSuppressTIXPopupChange} />} onChange={(newValue) => Settings.SuppressTIXPopup = newValue}
label={ text="Suppress TIX messages"
<Tooltip tooltip={<>
title={<Typography>If this is set, the stock market will never create any popup.</Typography>} If this is set, the stock market will never create any popup.
> </>} />
<Typography>Suppress TIX messages</Typography>
</Tooltip>
}
/>
</ListItem> </ListItem>
{!!props.player.bladeburner && ( {!!props.player.bladeburner && (
<ListItem> <ListItem>
<FormControlLabel <OptionSwitch checked={Settings.SuppressBladeburnerPopup}
control={ onChange={(newValue) => Settings.SuppressBladeburnerPopup = newValue}
<Switch checked={suppressBladeburnerPopup} onChange={handleSuppressBladeburnerPopupChange} /> text="Suppress bladeburner popup"
} tooltip={<>
label={ If this is set, then having your Bladeburner actions interrupted by being busy with something
<Tooltip else will not display a popup message.
title={ </>} />
<Typography>
If this is set, then having your Bladeburner actions interrupted by being busy with something
else will not display a popup message.
</Typography>
}
>
<Typography>Suppress bladeburner popup</Typography>
</Tooltip>
}
/>
</ListItem> </ListItem>
)} )}
<ListItem> <ListItem>
<FormControlLabel <OptionSwitch checked={Settings.SuppressSavedGameToast}
control={<Switch checked={suppressSavedGameToast} onChange={handleSuppressSavedGameToastChange} />} onChange={(newValue) => Settings.SuppressSavedGameToast = newValue}
label={ text="Suppress Auto-Save Game Toast"
<Tooltip tooltip={<>
title={ If this is set, there will be no "Game Saved!" toast appearing after an auto-save.
<Typography>If this is set, there will be no "Game Saved!" toast appearing after an auto-save.</Typography> </>} />
}
>
<Typography>Suppress Auto-Save Game Toast</Typography>
</Tooltip>
}
/>
</ListItem> </ListItem>
<ListItem> <ListItem>
<FormControlLabel <OptionSwitch checked={Settings.DisableHotkeys}
control={<Switch checked={disableHotkeys} onChange={handleDisableHotkeysChange} />} onChange={(newValue) => Settings.DisableHotkeys = newValue}
label={ text="Disable hotkeys"
<Tooltip tooltip={<>
title={ If this is set, then most hotkeys (keyboard shortcuts) in the game are disabled. This includes
<Typography> Terminal commands, hotkeys to navigate between different parts of the game, and the "Save and
If this is set, then most hotkeys (keyboard shortcuts) in the game are disabled. This includes Close (Ctrl + b)" hotkey in the Text Editor.
Terminal commands, hotkeys to navigate between different parts of the game, and the "Save and </>} />
Close (Ctrl + b)" hotkey in the Text Editor.
</Typography>
}
>
<Typography>Disable hotkeys</Typography>
</Tooltip>
}
/>
</ListItem> </ListItem>
<ListItem> <ListItem>
<FormControlLabel <OptionSwitch checked={Settings.DisableASCIIArt}
control={<Switch checked={disableASCIIArt} onChange={handleDisableASCIIArtChange} />} onChange={(newValue) => Settings.DisableASCIIArt = newValue}
label={ text="Disable ascii art"
<Tooltip title={<Typography>If this is set all ASCII art will be disabled.</Typography>}> tooltip={<>
<Typography>Disable ascii art</Typography> If this is set all ASCII art will be disabled.
</Tooltip> </>} />
}
/>
</ListItem> </ListItem>
<ListItem> <ListItem>
<FormControlLabel <OptionSwitch checked={Settings.DisableTextEffects}
control={<Switch checked={disableTextEffects} onChange={handleDisableTextEffectsChange} />} onChange={(newValue) => Settings.DisableTextEffects = newValue}
label={ text="Disable text effects"
<Tooltip tooltip={<>
title={ If this is set, text effects will not be displayed. This can help if text is difficult to read
<Typography> in certain areas.
If this is set, text effects will not be displayed. This can help if text is difficult to read </>} />
in certain areas.
</Typography>
}
>
<Typography>Disable text effects</Typography>
</Tooltip>
}
/>
</ListItem>
<ListItem>
<FormControlLabel
control={<Switch checked={enableBashHotkeys} onChange={handleEnableBashHotkeysChange} />}
label={
<Tooltip
title={
<Typography>
Improved Bash emulation mode. Setting this to 1 enables several new Terminal shortcuts and
features that more closely resemble a real Bash-style shell. Note that when this mode is
enabled, the default browser shortcuts are overriden by the new Bash shortcuts.
</Typography>
}
>
<Typography>Enable bash hotkeys</Typography>
</Tooltip>
}
/>
</ListItem> </ListItem>
<ListItem> <ListItem>
<FormControlLabel <OptionSwitch checked={Settings.DisableOverviewProgressBars}
control={<Switch checked={useIEC60027_2} onChange={handleUseIEC60027_2Change} />} onChange={(newValue) => Settings.DisableOverviewProgressBars = newValue}
label={ text="Disable Overview Progress Bars"
<Tooltip title={<Typography>If this is set all references to memory will use GiB instead of GB, in accordance with IEC 60027-2.</Typography>}> tooltip={<>
<Typography>Use GiB instead of GB</Typography> If this is set, the progress bars in the character overview will be hidden.
</Tooltip> </>} />
} </ListItem>
/> <ListItem>
<OptionSwitch checked={Settings.EnableBashHotkeys}
onChange={(newValue) => Settings.EnableBashHotkeys = newValue}
text="Enable bash hotkeys"
tooltip={<>
Improved Bash emulation mode. Setting this to 1 enables several new Terminal shortcuts and
features that more closely resemble a real Bash-style shell. Note that when this mode is
enabled, the default browser shortcuts are overriden by the new Bash shortcuts.
</>} />
</ListItem>
<ListItem>
<OptionSwitch checked={Settings.UseIEC60027_2}
onChange={(newValue) => Settings.UseIEC60027_2 = newValue}
text="Use GiB instead of GB"
tooltip={<>
If this is set all references to memory will use GiB instead of GB, in accordance with IEC 60027-2.
</>} />
</ListItem> </ListItem>
<ListItem> <ListItem>
<Tooltip <Tooltip
@ -620,16 +459,12 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
</ListItem> </ListItem>
<ListItem> <ListItem>
<FormControlLabel <OptionSwitch checked={Settings.SaveGameOnFileSave}
control={<Switch checked={saveGameOnFileSave} onChange={handleSaveGameOnFile} />} onChange={(newValue) => Settings.SaveGameOnFileSave = newValue}
label={ text="Save game on file save"
<Tooltip tooltip={<>
title={<Typography>Save your game any time a file is saved in the script editor.</Typography>} Save your game any time a file is saved in the script editor.
> </>} />
<Typography>Save game on file save</Typography>
</Tooltip>
}
/>
</ListItem> </ListItem>
<ListItem> <ListItem>

@ -0,0 +1,30 @@
import { FormControlLabel, Switch, Tooltip, Typography } from "@mui/material";
import React, { useEffect, useState } from "react";
interface IProps {
checked: boolean;
onChange: (newValue: boolean, error?: string) => void;
text: React.ReactNode;
tooltip: React.ReactNode;
}
export function OptionSwitch({ checked, onChange, text, tooltip }: IProps): React.ReactElement {
const [value, setValue] = useState(checked);
function handleSwitchChange(event: React.ChangeEvent<HTMLInputElement>): void {
setValue(event.target.checked);
}
useEffect(() => onChange(value), [value]);
return (
<FormControlLabel
control={<Switch checked={value} onChange={handleSwitchChange} />}
label={
<Tooltip title={<Typography>{tooltip}</Typography>}>
<Typography>{text}</Typography>
</Tooltip>
}
/>
);
}

@ -1,21 +1,19 @@
import React, { useState } from "react"; import React, { useState, useEffect, useRef } from "react";
import Draggable, { DraggableEventHandler } from "react-draggable";
import makeStyles from "@mui/styles/makeStyles"; import makeStyles from "@mui/styles/makeStyles";
import Collapse from "@mui/material/Collapse"; import Collapse from "@mui/material/Collapse";
import Fab from "@mui/material/Fab";
import Paper from "@mui/material/Paper"; import Paper from "@mui/material/Paper";
import VisibilityOffIcon from "@mui/icons-material/VisibilityOff"; import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
import VisibilityIcon from "@mui/icons-material/Visibility"; import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp";
import EqualizerIcon from "@mui/icons-material/Equalizer";
import SchoolIcon from "@mui/icons-material/School";
import { use } from "../Context"; import { use } from "../Context";
import { Page } from "../Router"; import { Page } from "../Router";
import { Settings } from "../../Settings/Settings";
import { Box, Button, Typography } from "@mui/material";
import { debounce } from "lodash";
const useStyles = makeStyles({ const useStyles = makeStyles({
visibilityToggle: {
backgroundColor: "transparent",
position: "absolute",
top: "100%",
right: 0,
},
overviewContainer: { overviewContainer: {
position: "fixed", position: "fixed",
top: 0, top: 0,
@ -25,31 +23,116 @@ const useStyles = makeStyles({
justifyContent: "flex-end", justifyContent: "flex-end",
flexDirection: "column", flexDirection: "column",
}, },
header: {
cursor: "grab",
textAlign: "center",
display: "flex",
flexDirection: "row",
alignItems: "center",
},
visibilityToggle: {
padding: "2px",
minWidth: "inherit",
backgroundColor: "transparent",
border: "none",
"&:hover": {
backgroundColor: "rgba(255, 255, 255, 0.1)",
},
},
collapse: {
borderTop: `1px solid ${Settings.theme.welllight}`,
margin: "0 auto",
},
icon: {
fontSize: "24px",
},
}); });
interface IProps { interface IProps {
children: JSX.Element[] | JSX.Element | React.ReactElement[] | React.ReactElement; children: JSX.Element[] | JSX.Element | React.ReactElement[] | React.ReactElement;
mode: "tutorial" | "overview";
} }
export function Overview({ children }: IProps): React.ReactElement { export interface OverviewSettings {
const [open, setOpen] = useState(true); opened: boolean;
x: number;
y: number;
}
export function Overview({ children, mode }: IProps): React.ReactElement {
const draggableRef = useRef<HTMLDivElement>(null);
const [open, setOpen] = useState(Settings.overview.opened);
const [x, setX] = useState(Settings.overview.x);
const [y, setY] = useState(Settings.overview.y);
const classes = useStyles(); const classes = useStyles();
const router = use.Router(); const router = use.Router();
const CurrentIcon = open ? KeyboardArrowUpIcon : KeyboardArrowDownIcon;
const LeftIcon = mode === "tutorial" ? SchoolIcon : EqualizerIcon;
const header = mode === "tutorial" ? "Tutorial" : "Overview";
const handleStop: DraggableEventHandler = (e, data) => {
setX(data.x);
setY(data.y);
};
useEffect(() => {
Settings.overview = { x, y, opened: open };
}, [open, x, y]);
// Trigger fakeDrag once to make sure loaded data is not outside bounds
useEffect(() => fakeDrag(), []);
// And trigger fakeDrag when the window is resized
useEffect(() => {
window.addEventListener("resize", fakeDrag);
return () => {
window.removeEventListener("resize", fakeDrag);
};
}, []);
const fakeDrag = debounce((): void => {
const node = draggableRef?.current;
if (!node) return;
// No official way to trigger an onChange to recompute the bounds
// See: https://github.com/react-grid-layout/react-draggable/issues/363#issuecomment-947751127
triggerMouseEvent(node, "mouseover");
triggerMouseEvent(node, "mousedown");
triggerMouseEvent(document, "mousemove");
triggerMouseEvent(node, "mouseup");
triggerMouseEvent(node, "click");
}, 100);
const triggerMouseEvent = (node: HTMLDivElement | Document, eventType: string): void => {
const clickEvent = document.createEvent("MouseEvents");
clickEvent.initEvent(eventType, true, true);
node.dispatchEvent(clickEvent);
};
if (router.page() === Page.BitVerse || router.page() === Page.Loading || router.page() === Page.Recovery) if (router.page() === Page.BitVerse || router.page() === Page.Loading || router.page() === Page.Recovery)
return <></>; return <></>;
let icon;
if (open) {
icon = <VisibilityOffIcon color="primary" />;
} else {
icon = <VisibilityIcon color="primary" />;
}
return ( return (
<Paper square classes={{ root: classes.overviewContainer }}> <Draggable handle=".drag" bounds="body" onStop={handleStop} defaultPosition={{ x, y }}>
<Collapse in={open}>{children}</Collapse> <Paper className={classes.overviewContainer} square>
<Fab size="small" classes={{ root: classes.visibilityToggle }} onClick={() => setOpen((old) => !old)}> <Box className="drag" onDoubleClick={() => setOpen((old) => !old)} ref={draggableRef}>
{icon} <Box className={classes.header}>
</Fab> <LeftIcon color="secondary" className={classes.icon} sx={{ padding: "2px" }} />
</Paper> <Typography flexGrow={1} color="secondary">
{header}
</Typography>
<Button variant="text" size="small" className={classes.visibilityToggle}>
{<CurrentIcon className={classes.icon} color="secondary" onClick={() => setOpen((old) => !old)} />}
</Button>
</Box>
</Box>
<Collapse in={open} className={classes.collapse}>
{children}
</Collapse>
</Paper>
</Draggable>
); );
} }