CODEBASE: Validate theme, editor theme, and styles (#1789)

This commit is contained in:
catloversg
2025-01-09 10:20:05 +07:00
committed by GitHub
parent 320c852386
commit 0f9144a059
27 changed files with 969 additions and 213 deletions
+5 -1
View File
@@ -9619,7 +9619,6 @@ interface InvestmentOffer {
* @public
*/
interface UserInterfaceTheme {
[key: string]: string | undefined;
primarylight: string;
primary: string;
primarydark: string;
@@ -9653,6 +9652,11 @@ interface UserInterfaceTheme {
backgroundprimary: string;
backgroundsecondary: string;
button: string;
maplocation: string;
bnlvl0: string;
bnlvl1: string;
bnlvl2: string;
bnlvl3: string;
}
/**
+1 -2
View File
@@ -2,7 +2,7 @@ import type { ContentFilePath } from "../Paths/ContentFile";
import { EventEmitter } from "../utils/EventEmitter";
import * as monaco from "monaco-editor";
import { loadThemes, makeTheme, sanitizeTheme } from "./ui/themes";
import { loadThemes, makeTheme } from "./ui/themes";
import { Settings } from "../Settings/Settings";
import { NetscriptExtra } from "../NetscriptFunctions/Extra";
import * as enums from "../Enums";
@@ -132,7 +132,6 @@ export class ScriptEditor {
});
// Load themes
loadThemes(monaco.editor.defineTheme);
sanitizeTheme(Settings.EditorTheme);
monaco.editor.defineTheme("customTheme", makeTheme(Settings.EditorTheme));
}
}
+9 -11
View File
@@ -12,6 +12,7 @@ import { OptionSwitch } from "../../ui/React/OptionSwitch";
import { defaultMonacoTheme } from "./themes";
import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { assertAndSanitizeEditorTheme } from "../../JsonSchema/JSONSchemaAssertion";
type ColorEditorProps = {
label: string;
@@ -74,21 +75,18 @@ export function ThemeEditorModal(props: ThemeEditorProps): React.ReactElement {
}
function onThemeChange(event: React.ChangeEvent<HTMLInputElement>): void {
let themeData: unknown;
try {
const importedTheme = JSON.parse(event.target.value) as typeof Settings.EditorTheme;
if (importedTheme == null) {
throw new Error("Theme data must not be null or undefined.");
}
if (typeof importedTheme !== "object") {
throw new Error(`Theme data is invalid.`);
}
Settings.EditorTheme = importedTheme;
props.onChange();
themeData = JSON.parse(event.target.value);
assertAndSanitizeEditorTheme(themeData);
} catch (error) {
console.error(`Theme data is invalid. Data: ${event.target.value}.`);
console.error(error);
dialogBoxCreate(`Invalid theme. ${error}`);
console.error("Theme data is invalid. Data:", event.target.value);
dialogBoxCreate(`Invalid theme. Errors: ${error}.`);
return;
}
Object.assign(Settings.EditorTheme, themeData);
props.onChange();
}
const onResetToDefault = () => {
+1 -2
View File
@@ -12,7 +12,7 @@ import Typography from "@mui/material/Typography";
import SettingsIcon from "@mui/icons-material/Settings";
import { makeTheme, sanitizeTheme } from "./themes";
import { makeTheme } from "./themes";
import { Modal } from "../../ui/React/Modal";
import { Page } from "../../ui/Router";
@@ -54,7 +54,6 @@ export function Toolbar({ editor, onSave }: IProps) {
};
const onThemeChange = () => {
sanitizeTheme(Settings.EditorTheme);
monaco.editor.defineTheme("customTheme", makeTheme(Settings.EditorTheme));
};
+6 -42
View File
@@ -1,10 +1,13 @@
import type { editor } from "monaco-editor";
import { getRecordKeys } from "../../Types/Record";
import { Settings } from "../../Settings/Settings";
type DefineThemeFn = typeof editor.defineTheme;
export const validEditorThemeBases = ["vs", "vs-dark", "hc-black", "hc-light"] as const;
/**
* If we change this interface, we must change EditorThemeSchema.
*/
export interface IScriptEditorTheme {
base: "vs" | "vs-dark" | "hc-black";
base: (typeof validEditorThemeBases)[number];
inherit: boolean;
common: {
accent: string;
@@ -67,45 +70,6 @@ export const defaultMonacoTheme: IScriptEditorTheme = {
},
};
// Regex used for token color validation
// https://github.com/microsoft/vscode/blob/973684056e67153952f495fce93bf50d0ec0b892/src/vs/editor/common/languages/supports/tokenization.ts#L153
const colorRegExp = /^#?([0-9A-Fa-f]{6})([0-9A-Fa-f]{2})?$/;
// Recursively sanitize the theme data to prevent errors
// Invalid data will be replaced with FF0000 (bright red)
export const sanitizeTheme = (theme: IScriptEditorTheme): void => {
if (typeof theme !== "object") {
Settings.EditorTheme = structuredClone(defaultMonacoTheme);
return;
}
for (const themeKey of getRecordKeys(theme)) {
if (typeof theme[themeKey] !== "object") {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete theme[themeKey];
}
switch (themeKey) {
case "base":
if (!["vs-dark", "vs"].includes(theme.base)) theme.base = "vs-dark";
continue;
case "inherit":
if (typeof theme.inherit !== "boolean") theme.inherit = true;
continue;
}
const block = theme[themeKey];
const repairBlock = <T extends Record<string, unknown>>(block: T) => {
for (const [blockKey, blockValue] of Object.entries(block) as [keyof T, unknown][]) {
if (!blockValue || (typeof blockValue !== "string" && typeof blockValue !== "object"))
(block[blockKey] as string) = "FF0000";
else if (typeof blockValue === "object") repairBlock(blockValue as Record<string, unknown>);
else if (!blockValue.match(colorRegExp)) (block[blockKey] as string) = "FF0000";
}
};
// Type assertion is to something less specific.
repairBlock(block);
}
};
export function makeTheme(theme: IScriptEditorTheme): editor.IStandaloneThemeData {
const themeRules = [
{