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
dist
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",
"endOfLine": "lf",
"tabWidth": 2,
"printWidth": 120
}

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

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

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

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

@ -12,7 +12,6 @@ import { CopyableText } from "../React/CopyableText";
import ListItem from "@mui/material/ListItem";
import EqualizerIcon from "@mui/icons-material/Equalizer";
import LastPageIcon from "@mui/icons-material/LastPage";
import VisibilityOffIcon from "@mui/icons-material/VisibilityOff";
import HelpIcon from "@mui/icons-material/Help";
import AccountTreeIcon from "@mui/icons-material/AccountTree";
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.
<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>
</>
),

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

@ -8,8 +8,6 @@ import createStyles from "@mui/styles/createStyles";
import Typography from "@mui/material/Typography";
import Slider from "@mui/material/Slider";
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 MenuItem from "@mui/material/MenuItem";
import Button from "@mui/material/Button";
@ -35,6 +33,7 @@ import { SnackbarEvents } from "./Snackbar";
import { Settings } from "../../Settings/Settings";
import { save, deleteGame } from "../../db";
import { formatTime } from "../../utils/helpers/formatTime";
import { OptionSwitch } from "./OptionSwitch";
const useStyles = makeStyles((theme: Theme) =>
createStyles({
@ -68,27 +67,8 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
const [logSize, setLogSize] = useState(Settings.MaxLogCapacity);
const [portSize, setPortSize] = useState(Settings.MaxPortCapacity);
const [terminalSize, setTerminalSize] = useState(Settings.MaxTerminalCapacity);
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 [saveGameOnFileSave, setSaveGameOnFileSave] = useState(Settings.SaveGameOnFileSave);
const [useIEC60027_2, setUseIEC60027_2] = useState(Settings.UseIEC60027_2);
const [locale, setLocale] = useState(Settings.Locale);
const [diagnosticOpen, setDiagnosticOpen] = useState(false);
const [deleteGameOpen, setDeleteOpen] = useState(false);
@ -123,76 +103,15 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
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 {
setLocale(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 {
setTimestampFormat(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 {
if (!window.File || !window.FileReader || !window.FileList || !window.Blob) return;
@ -392,200 +311,120 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
/>
</ListItem>
<ListItem>
<FormControlLabel
control={<Switch checked={suppressMessages} onChange={handleSuppressMessagesChange} />}
label={
<Tooltip
title={
<Typography>
<OptionSwitch checked={Settings.SuppressMessages}
onChange={(newValue) => Settings.SuppressMessages = newValue}
text="Suppress story messages"
tooltip={<>
If this is set, then any messages you receive will not appear as popups on the screen. They will
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>
<FormControlLabel
control={<Switch checked={suppressFactionInvites} onChange={handleSuppressFactionInvitesChange} />}
label={
<Tooltip
title={
<Typography>
<OptionSwitch checked={Settings.SuppressFactionInvites}
onChange={(newValue) => Settings.SuppressFactionInvites = newValue}
text="Suppress faction invites"
tooltip={<>
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>
<FormControlLabel
control={
<Switch checked={suppressTravelConfirmations} onChange={handleSuppressTravelConfirmationsChange} />
}
label={
<Tooltip
title={
<Typography>
<OptionSwitch checked={Settings.SuppressTravelConfirmation}
onChange={(newValue) => Settings.SuppressTravelConfirmation = newValue}
text="Suppress travel confirmations"
tooltip={<>
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>
<FormControlLabel
control={
<Switch
checked={suppressBuyAugmentationConfirmation}
onChange={handleSuppressBuyAugmentationConfirmationChange}
/>
}
label={
<Tooltip
title={
<Typography>
<OptionSwitch checked={Settings.SuppressBuyAugmentationConfirmation}
onChange={(newValue) => Settings.SuppressBuyAugmentationConfirmation = newValue}
text="Suppress augmentations confirmation"
tooltip={<>
If this is set, the confirmation message before buying augmentation will not show up.
</Typography>
}
>
<Typography>Suppress augmentations confirmation</Typography>
</Tooltip>
}
/>
</>} />
</ListItem>
<ListItem>
<FormControlLabel
control={<Switch checked={suppressTIXPopup} onChange={handleSuppressTIXPopupChange} />}
label={
<Tooltip
title={<Typography>If this is set, the stock market will never create any popup.</Typography>}
>
<Typography>Suppress TIX messages</Typography>
</Tooltip>
}
/>
<OptionSwitch checked={Settings.SuppressTIXPopup}
onChange={(newValue) => Settings.SuppressTIXPopup = newValue}
text="Suppress TIX messages"
tooltip={<>
If this is set, the stock market will never create any popup.
</>} />
</ListItem>
{!!props.player.bladeburner && (
<ListItem>
<FormControlLabel
control={
<Switch checked={suppressBladeburnerPopup} onChange={handleSuppressBladeburnerPopupChange} />
}
label={
<Tooltip
title={
<Typography>
<OptionSwitch checked={Settings.SuppressBladeburnerPopup}
onChange={(newValue) => Settings.SuppressBladeburnerPopup = newValue}
text="Suppress bladeburner popup"
tooltip={<>
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>
<FormControlLabel
control={<Switch checked={suppressSavedGameToast} onChange={handleSuppressSavedGameToastChange} />}
label={
<Tooltip
title={
<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>
}
/>
<OptionSwitch checked={Settings.SuppressSavedGameToast}
onChange={(newValue) => Settings.SuppressSavedGameToast = newValue}
text="Suppress Auto-Save Game Toast"
tooltip={<>
If this is set, there will be no "Game Saved!" toast appearing after an auto-save.
</>} />
</ListItem>
<ListItem>
<FormControlLabel
control={<Switch checked={disableHotkeys} onChange={handleDisableHotkeysChange} />}
label={
<Tooltip
title={
<Typography>
<OptionSwitch checked={Settings.DisableHotkeys}
onChange={(newValue) => Settings.DisableHotkeys = newValue}
text="Disable hotkeys"
tooltip={<>
If this is set, then most hotkeys (keyboard shortcuts) in the game are disabled. This includes
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>
<FormControlLabel
control={<Switch checked={disableASCIIArt} onChange={handleDisableASCIIArtChange} />}
label={
<Tooltip title={<Typography>If this is set all ASCII art will be disabled.</Typography>}>
<Typography>Disable ascii art</Typography>
</Tooltip>
}
/>
<OptionSwitch checked={Settings.DisableASCIIArt}
onChange={(newValue) => Settings.DisableASCIIArt = newValue}
text="Disable ascii art"
tooltip={<>
If this is set all ASCII art will be disabled.
</>} />
</ListItem>
<ListItem>
<FormControlLabel
control={<Switch checked={disableTextEffects} onChange={handleDisableTextEffectsChange} />}
label={
<Tooltip
title={
<Typography>
<OptionSwitch checked={Settings.DisableTextEffects}
onChange={(newValue) => Settings.DisableTextEffects = newValue}
text="Disable text effects"
tooltip={<>
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>
<OptionSwitch checked={Settings.DisableOverviewProgressBars}
onChange={(newValue) => Settings.DisableOverviewProgressBars = newValue}
text="Disable Overview Progress Bars"
tooltip={<>
If this is set, the progress bars in the character overview will be hidden.
</>} />
</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.
</Typography>
}
>
<Typography>Enable bash hotkeys</Typography>
</Tooltip>
}
/>
</>} />
</ListItem>
<ListItem>
<FormControlLabel
control={<Switch checked={useIEC60027_2} onChange={handleUseIEC60027_2Change} />}
label={
<Tooltip title={<Typography>If this is set all references to memory will use GiB instead of GB, in accordance with IEC 60027-2.</Typography>}>
<Typography>Use GiB instead of GB</Typography>
</Tooltip>
}
/>
<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>
<Tooltip
@ -620,16 +459,12 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
</ListItem>
<ListItem>
<FormControlLabel
control={<Switch checked={saveGameOnFileSave} onChange={handleSaveGameOnFile} />}
label={
<Tooltip
title={<Typography>Save your game any time a file is saved in the script editor.</Typography>}
>
<Typography>Save game on file save</Typography>
</Tooltip>
}
/>
<OptionSwitch checked={Settings.SaveGameOnFileSave}
onChange={(newValue) => Settings.SaveGameOnFileSave = newValue}
text="Save game on file save"
tooltip={<>
Save your game any time a file is saved in the script editor.
</>} />
</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 Collapse from "@mui/material/Collapse";
import Fab from "@mui/material/Fab";
import Paper from "@mui/material/Paper";
import VisibilityOffIcon from "@mui/icons-material/VisibilityOff";
import VisibilityIcon from "@mui/icons-material/Visibility";
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
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 { Page } from "../Router";
import { Settings } from "../../Settings/Settings";
import { Box, Button, Typography } from "@mui/material";
import { debounce } from "lodash";
const useStyles = makeStyles({
visibilityToggle: {
backgroundColor: "transparent",
position: "absolute",
top: "100%",
right: 0,
},
overviewContainer: {
position: "fixed",
top: 0,
@ -25,31 +23,116 @@ const useStyles = makeStyles({
justifyContent: "flex-end",
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 {
children: JSX.Element[] | JSX.Element | React.ReactElement[] | React.ReactElement;
mode: "tutorial" | "overview";
}
export function Overview({ children }: IProps): React.ReactElement {
const [open, setOpen] = useState(true);
export interface OverviewSettings {
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 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)
return <></>;
let icon;
if (open) {
icon = <VisibilityOffIcon color="primary" />;
} else {
icon = <VisibilityIcon color="primary" />;
}
return (
<Paper square classes={{ root: classes.overviewContainer }}>
<Collapse in={open}>{children}</Collapse>
<Fab size="small" classes={{ root: classes.visibilityToggle }} onClick={() => setOpen((old) => !old)}>
{icon}
</Fab>
<Draggable handle=".drag" bounds="body" onStop={handleStop} defaultPosition={{ x, y }}>
<Paper className={classes.overviewContainer} square>
<Box className="drag" onDoubleClick={() => setOpen((old) => !old)} ref={draggableRef}>
<Box className={classes.header}>
<LeftIcon color="secondary" className={classes.icon} sx={{ padding: "2px" }} />
<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>
);
}