Merge pull request #2506 from MartinFournier/feature/styles-settings

Add fontFamily & lineHeight settings
This commit is contained in:
hydroflame 2022-01-09 12:58:27 -05:00 committed by GitHub
commit 9c33e27996
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 204 additions and 5 deletions

@ -1,6 +1,7 @@
import { ISelfInitializer, ISelfLoading } from "../types"; 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 { WordWrapOptions } from '../ScriptEditor/ui/Options'; import { WordWrapOptions } from '../ScriptEditor/ui/Options';
/** /**
@ -115,6 +116,11 @@ interface IDefaultSettings {
*/ */
theme: ITheme; theme: ITheme;
/*
* Interface styles
*/
styles: IStyleSettings;
/* /*
* Use GiB instead of GB * Use GiB instead of GB
*/ */
@ -171,6 +177,7 @@ export const defaultSettings: IDefaultSettings = {
UseIEC60027_2: false, UseIEC60027_2: false,
theme: defaultTheme, theme: defaultTheme,
styles: defaultStyles,
}; };
/** /**
@ -209,6 +216,7 @@ export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
MonacoWordWrap: 'off', MonacoWordWrap: 'off',
theme: { ...defaultTheme }, theme: { ...defaultTheme },
styles: { ...defaultStyles },
init() { init() {
Object.assign(Settings, defaultSettings); Object.assign(Settings, defaultSettings);
}, },
@ -216,6 +224,8 @@ export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
const save = JSON.parse(saveString); const save = JSON.parse(saveString);
Object.assign(Settings.theme, save.theme); Object.assign(Settings.theme, save.theme);
delete save.theme; delete save.theme;
Object.assign(Settings.styles, save.styles);
delete save.styles;
Object.assign(Settings, save); Object.assign(Settings, save);
}, },
}; };

11
src/Settings/Styles.ts Normal file

@ -0,0 +1,11 @@
import React from "react";
export interface IStyleSettings {
fontFamily: React.CSSProperties["fontFamily"];
lineHeight: React.CSSProperties["lineHeight"];
}
export const defaultStyles: IStyleSettings = {
lineHeight: 1.5,
fontFamily: "Lucida Console, Lucida Sans Unicode, Fira Mono, Consolas, Courier New, Courier, monospace, Times New Roman"
}

@ -77,6 +77,7 @@ 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;

@ -28,6 +28,8 @@ import { FileDiagnosticModal } from "../../Diagnostic/FileDiagnosticModal";
import { dialogBoxCreate } from "./DialogBox"; import { dialogBoxCreate } from "./DialogBox";
import { ConfirmationModal } from "./ConfirmationModal"; import { ConfirmationModal } from "./ConfirmationModal";
import { ThemeEditorModal } from "./ThemeEditorModal"; import { ThemeEditorModal } from "./ThemeEditorModal";
import { StyleEditorModal } from "./StyleEditorModal";
import { SnackbarEvents } from "./Snackbar"; import { SnackbarEvents } from "./Snackbar";
import { Settings } from "../../Settings/Settings"; import { Settings } from "../../Settings/Settings";
@ -91,6 +93,7 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
const [diagnosticOpen, setDiagnosticOpen] = useState(false); const [diagnosticOpen, setDiagnosticOpen] = useState(false);
const [deleteGameOpen, setDeleteOpen] = useState(false); const [deleteGameOpen, setDeleteOpen] = useState(false);
const [themeEditorOpen, setThemeEditorOpen] = useState(false); const [themeEditorOpen, setThemeEditorOpen] = useState(false);
const [styleEditorOpen, setStyleEditorOpen] = useState(false);
const [softResetOpen, setSoftResetOpen] = useState(false); const [softResetOpen, setSoftResetOpen] = useState(false);
const [importSaveOpen, setImportSaveOpen] = useState(false); const [importSaveOpen, setImportSaveOpen] = useState(false);
const [importData, setImportData] = useState<ImportData | null>(null); const [importData, setImportData] = useState<ImportData | null>(null);
@ -756,8 +759,6 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
onConfirm={props.softReset} onConfirm={props.softReset}
confirmationText={"This will perform the same action as installing Augmentations, are you sure?"} confirmationText={"This will perform the same action as installing Augmentations, are you sure?"}
/> />
</Box>
<Box>
<Tooltip <Tooltip
title={ title={
<Typography> <Typography>
@ -768,7 +769,10 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
> >
<Button onClick={() => setDiagnosticOpen(true)}>Diagnose files</Button> <Button onClick={() => setDiagnosticOpen(true)}>Diagnose files</Button>
</Tooltip> </Tooltip>
</Box>
<Box>
<Button onClick={() => setThemeEditorOpen(true)}>Theme editor</Button> <Button onClick={() => setThemeEditorOpen(true)}>Theme editor</Button>
<Button onClick={() => setStyleEditorOpen(true)}>Style editor</Button>
</Box> </Box>
<Box> <Box>
<Link href="https://github.com/danielyxie/bitburner/issues/new" target="_blank"> <Link href="https://github.com/danielyxie/bitburner/issues/new" target="_blank">
@ -805,6 +809,7 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
confirmationText={"Really delete your game? (It's permanent!)"} confirmationText={"Really delete your game? (It's permanent!)"}
/> />
<ThemeEditorModal open={themeEditorOpen} onClose={() => setThemeEditorOpen(false)} /> <ThemeEditorModal open={themeEditorOpen} onClose={() => setThemeEditorOpen(false)} />
<StyleEditorModal open={styleEditorOpen} onClose={() => setStyleEditorOpen(false)} />
</div> </div>
); );
} }

@ -0,0 +1,157 @@
import React, { useEffect, useState } from "react";
import { Modal } from "./Modal";
import Button from "@mui/material/Button";
import ButtonGroup from "@mui/material/ButtonGroup";
import Typography from "@mui/material/Typography";
import Paper from "@mui/material/Paper";
import TextField from "@mui/material/TextField";
import ReplyIcon from "@mui/icons-material/Reply";
import SaveIcon from '@mui/icons-material/Save';
import { ThemeEvents } from "./Theme";
import { Settings } from "../../Settings/Settings";
import { IStyleSettings, defaultStyles } from "../../Settings/Styles";
import { Tooltip } from "@mui/material";
interface IProps {
open: boolean;
onClose: () => void;
}
interface FontFamilyProps {
value: React.CSSProperties["fontFamily"];
onChange: (newValue: React.CSSProperties["fontFamily"], error?: string) => void;
refreshId: number;
}
function FontFamilyField({ value, onChange, refreshId } : FontFamilyProps): React.ReactElement {
const [errorText, setErrorText] = useState<string | undefined>();
const [fontFamily, setFontFamily] = useState<React.CSSProperties["fontFamily"]>(value);
function update(newValue: React.CSSProperties["fontFamily"]): void {
setFontFamily(newValue);
if (!newValue) {
setErrorText('Must have a value');
} else {
setErrorText('');
}
}
function onTextChange(event: React.ChangeEvent<HTMLInputElement>): void {
update(event.target.value);
}
useEffect(() => onChange(fontFamily, errorText), [fontFamily]);
useEffect(() => update(value), [refreshId]);
return (
<TextField
sx={{ my: 1 }}
label={"Font-Family"}
error={!!errorText}
value={fontFamily}
helperText={errorText}
onChange={onTextChange}
fullWidth
/>
)
}
interface LineHeightProps {
value: React.CSSProperties["lineHeight"];
onChange: (newValue: React.CSSProperties["lineHeight"], error?: string) => void;
refreshId: number;
}
function LineHeightField({ value, onChange, refreshId } : LineHeightProps): React.ReactElement {
const [errorText, setErrorText] = useState<string | undefined>();
const [lineHeight, setLineHeight] = useState<React.CSSProperties["lineHeight"]>(value);
function update(newValue: React.CSSProperties["lineHeight"]): void {
setLineHeight(newValue);
if (!newValue) {
setErrorText('Must have a value');
} else if (isNaN(Number(newValue))) {
setErrorText('Must be a number');
} else {
setErrorText('');
}
}
function onTextChange(event: React.ChangeEvent<HTMLInputElement>): void {
update(event.target.value);
}
useEffect(() => onChange(lineHeight, errorText), [lineHeight]);
useEffect(() => update(value), [refreshId]);
return (
<TextField
sx={{ my: 1 }}
label={"Line Height"}
error={!!errorText}
value={lineHeight}
helperText={errorText}
onChange={onTextChange}
/>
)
}
export function StyleEditorModal(props: IProps): React.ReactElement {
const [refreshId, setRefreshId] = useState<number>(0);
const [error, setError] = useState<string | undefined>();
const [customStyle, setCustomStyle] = useState<IStyleSettings>({
...Settings.styles,
});
function persistToSettings(styles: IStyleSettings): void {
Object.assign(Settings.styles, styles);
ThemeEvents.emit();
}
function saveStyles(): void {
persistToSettings(customStyle);
}
function setDefaults(): void {
const styles = {...defaultStyles}
setCustomStyle(styles);
persistToSettings(styles);
setRefreshId(refreshId + 1);
}
function update(styles: IStyleSettings, errorMessage?: string): void {
setError(errorMessage);
if (!errorMessage) {
setCustomStyle(styles);
}
}
return (
<Modal open={props.open} onClose={props.onClose}>
<Typography variant="h6">Styles Editor</Typography>
<Typography>
WARNING: Changing styles <strong>may mess up</strong> the interface. Drastic changes are <strong>NOT recommended</strong>.
</Typography>
<Paper sx={{ p: 2, my: 2 }}>
<FontFamilyField value={customStyle.fontFamily} refreshId={refreshId}
onChange={(value, error) => update({ ...customStyle, fontFamily: value }, error)} />
<br />
<LineHeightField value={customStyle.lineHeight} refreshId={refreshId}
onChange={(value, error) => update({ ...customStyle, lineHeight: value }, error)} />
<br />
<ButtonGroup sx={{ my: 1}}>
<Button onClick={setDefaults} startIcon={<ReplyIcon />} color="secondary" variant="outlined">
Revert to Defaults
</Button>
<Tooltip title={"Save styles to settings"}>
<Button onClick={saveStyles} endIcon={<SaveIcon />} color={error ? "error" : "primary"} disabled={!!error}>
Save Modifications
</Button>
</Tooltip>
</ButtonGroup>
</Paper>
</Modal>
);
}

@ -107,8 +107,7 @@ export function refreshTheme(): void {
}, },
}, },
typography: { typography: {
fontFamily: fontFamily: Settings.styles.fontFamily,
"Lucida Console, Lucida Sans Unicode, Fira Mono, Consolas, Courier New, Courier, monospace, Times New Roman",
button: { button: {
textTransform: "none", textTransform: "none",
}, },
@ -160,6 +159,17 @@ export function refreshTheme(): void {
}, },
}, },
}, },
MuiButtonGroup: {
styleOverrides: {
root: {
'& .MuiButton-root:not(:last-of-type)': {
marginRight: '1px',
}
}
}
},
MuiButton: { MuiButton: {
styleOverrides: { styleOverrides: {
root: { root: {
@ -193,6 +203,11 @@ export function refreshTheme(): void {
defaultProps: { defaultProps: {
color: "primary", color: "primary",
}, },
styleOverrides: {
root: {
lineHeight: Settings.styles.lineHeight,
}
}
}, },
MuiMenu: { MuiMenu: {
styleOverrides: { styleOverrides: {