From 38d5f3b364b51ee62bcd054131d1eae95b035fd7 Mon Sep 17 00:00:00 2001 From: catloversg <152669316+catloversg@users.noreply.github.com> Date: Fri, 27 Mar 2026 08:33:11 +0700 Subject: [PATCH] REFACTOR: Split Settings.ts to reduce number of imports (#2600) --- src/GameOptions/ui/RemoteAPIPage.tsx | 3 +- src/SaveObject.ts | 3 +- src/Settings/Settings.ts | 149 +-------------------------- src/Settings/SettingsUtils.ts | 141 +++++++++++++++++++++++++ 4 files changed, 150 insertions(+), 146 deletions(-) create mode 100644 src/Settings/SettingsUtils.ts diff --git a/src/GameOptions/ui/RemoteAPIPage.tsx b/src/GameOptions/ui/RemoteAPIPage.tsx index 453b59578..75a076e96 100644 --- a/src/GameOptions/ui/RemoteAPIPage.tsx +++ b/src/GameOptions/ui/RemoteAPIPage.tsx @@ -1,7 +1,8 @@ import React, { useState } from "react"; import { Button, TextField, Tooltip, Typography } from "@mui/material"; import { GameOptionsPage } from "./GameOptionsPage"; -import { isValidConnectionHostname, isValidConnectionPort, Settings } from "../../Settings/Settings"; +import { Settings } from "../../Settings/Settings"; +import { isValidConnectionHostname, isValidConnectionPort } from "../../Settings/SettingsUtils"; import { RemoteFileApiConnectionStatus } from "./RemoteFileApiConnectionStatus"; import { newRemoteFileApiConnection } from "../../RemoteFileAPI/RemoteFileAPI"; import { OptionSwitch } from "../../ui/React/OptionSwitch"; diff --git a/src/SaveObject.ts b/src/SaveObject.ts index a437142c8..1945d697b 100644 --- a/src/SaveObject.ts +++ b/src/SaveObject.ts @@ -35,6 +35,7 @@ import { giveExportBonus } from "./ExportBonus"; import { loadInfiltrations } from "./Infiltration/SaveLoadInfiltration"; import { InfiltrationState } from "./Infiltration/formulas/game"; import { hasDarknetAccess } from "./DarkNet/utils/darknetAuthUtils"; +import { loadSettings } from "./Settings/SettingsUtils"; /* SaveObject.js * Defines the object used to save/load games @@ -504,7 +505,7 @@ async function loadGame(saveData: SaveData): Promise { if (saveObj.SettingsSave) { try { // Try to set saved settings. - Settings.load(saveObj.SettingsSave); + loadSettings(saveObj.SettingsSave); } catch (e) { console.error("SettingsSave was present but an error occurred while loading:"); console.error(e); diff --git a/src/Settings/Settings.ts b/src/Settings/Settings.ts index c6ea3dbd0..2d6e271c8 100644 --- a/src/Settings/Settings.ts +++ b/src/Settings/Settings.ts @@ -1,78 +1,9 @@ -import { OwnedAugmentationsOrderSetting, PurchaseAugmentationsOrderSetting } from "./SettingEnums"; -import { defaultTheme } from "../Themes/Themes"; -import { defaultStyles } from "../Themes/Styles"; -import type { CursorStyle, CursorBlinking, WordWrapOptions, StickyScroll, Minimap } from "../ScriptEditor/ui/Options"; +import type { CursorBlinking, CursorStyle, Minimap, StickyScroll, WordWrapOptions } from "../ScriptEditor/ui/Options"; import { defaultMonacoTheme } from "../ScriptEditor/ui/themes"; -import { assertObject } from "../utils/TypeAssertion"; -import type { Result } from "@nsdefs"; -import { - assertAndSanitizeEditorTheme, - assertAndSanitizeKeyBindings, - assertAndSanitizeMainTheme, - assertAndSanitizeStyles, -} from "../JsonSchema/JSONSchemaAssertion"; -import { mergePlayerDefinedKeyBindings, type PlayerDefinedKeyBindingsType } from "../utils/KeyBindingUtils"; -import { toggleSuppressErrorModals } from "../ErrorHandling/ErrorState"; - -/** - * This function won't be able to catch **all** invalid hostnames. In order to validate a hostname properly, we need to - * import a good validation library or write one by ourselves. Considering that we only need to catch common mistakes, - * it's not worth the effort. - * - * Some invalid hostnames that we don't catch: - * - Invalid/missing TLD: "abc". - * - Use space character: "a a.com" - * - Use non-http schemes in the hostname: "ftp://a.com" - * - etc. - */ -export function isValidConnectionHostname(hostname: string): Result { - // Return a user-friendly error message. - if (hostname === "") { - return { - success: false, - message: "Hostname cannot be empty", - }; - } - /** - * We expect a hostname, but the player may mistakenly put other unexpected things. We will try to catch common mistakes: - * - Specify a scheme: http or https. - * - Specify a port. - * - Specify a pathname or search params. - */ - try { - // Check scheme. - if (hostname.startsWith("http://") || hostname.startsWith("https://")) { - return { - success: false, - message: "Do not specify scheme (e.g., http, https)", - }; - } - // Parse to a URL with a default scheme. - const url = new URL(`http://${hostname}`); - // Check port, pathname, and search params. - if (url.port !== "" || url.pathname !== "/" || url.search !== "") { - return { - success: false, - message: "Do not specify port, pathname, or search parameters", - }; - } - } catch (error) { - console.error(error); - return { - success: false, - message: `Invalid hostname: ${hostname}`, - }; - } - return { success: true }; -} - -export function isValidConnectionPort(port: number): Result { - // 0 is a special value for port. It's an invalid port, but the player can use it to disable RFA. - if (!Number.isFinite(port) || port < 0 || port > 65535) { - return { success: false, message: "Invalid port" }; - } - return { success: true }; -} +import { defaultStyles } from "../Themes/Styles"; +import { defaultTheme } from "../Themes/Themes"; +import type { PlayerDefinedKeyBindingsType } from "../utils/KeyBindingUtils"; +import { OwnedAugmentationsOrderSetting, PurchaseAugmentationsOrderSetting } from "./SettingEnums"; /** The current options the player has customized to their play style. */ export const Settings = { @@ -211,74 +142,4 @@ export const Settings = { KeyBindings: {} as PlayerDefinedKeyBindingsType, /** Whether to sync Steam achievements */ SyncSteamAchievements: true, - - load(saveString: string) { - const save: unknown = JSON.parse(saveString); - assertObject(save); - save.overview && Object.assign(Settings.overview, save.overview); - try { - // Sanitize theme data. Invalid theme data may crash the game or make it stuck in the loading page. - assertAndSanitizeMainTheme(save.theme); - Object.assign(Settings.theme, save.theme); - } catch (error) { - console.error(error); - } - try { - // Sanitize editor theme data. Invalid editor theme data may crash the game when the player opens the script editor. - assertAndSanitizeEditorTheme(save.EditorTheme); - Object.assign(Settings.EditorTheme, save.EditorTheme); - } catch (error) { - console.error(error); - } - try { - // Sanitize styles. - assertAndSanitizeStyles(save.styles); - Object.assign(Settings.styles, save.styles); - } catch (error) { - console.error(error); - } - /** - * KeyBindings data does not exist in old save files. Technically, this check is unnecessary. If KeyBindings is - * undefined, assertAndSanitizeKeyBindings will throw an error, and that error will be caught here. However, it - * means that there will be an error logged in the console every time the player loads an old save file, and this - * logged error is kind of a "false positive" one. - */ - if (save.KeyBindings !== undefined) { - try { - // Sanitize key bindings. - assertAndSanitizeKeyBindings(save.KeyBindings); - Object.assign(Settings.KeyBindings, save.KeyBindings); - } catch (error) { - console.error(error); - } - } - Object.assign(Settings, save, { - overview: Settings.overview, - theme: Settings.theme, - EditorTheme: Settings.EditorTheme, - styles: Settings.styles, - KeyBindings: Settings.KeyBindings, - }); - /** - * The hostname and port of RFA have not been validated properly, so the save data may contain invalid data. In that - * case, we set them to the default value. - */ - if (!isValidConnectionHostname(Settings.RemoteFileApiAddress).success) { - Settings.RemoteFileApiAddress = "localhost"; - } - if (!isValidConnectionPort(Settings.RemoteFileApiPort).success) { - Settings.RemoteFileApiPort = 0; - } - - // Merge Settings.KeyBindings with DefaultKeyBindings. - mergePlayerDefinedKeyBindings(Settings.KeyBindings); - - // Set up initial state for error modal suppression - toggleSuppressErrorModals(Settings.SuppressErrorModals, true); - - // Disable this feature for existing save files. - if (save.MonacoAutoSaveOnFocusChange === undefined) { - Settings.MonacoAutoSaveOnFocusChange = false; - } - }, }; diff --git a/src/Settings/SettingsUtils.ts b/src/Settings/SettingsUtils.ts new file mode 100644 index 000000000..1bff6a42e --- /dev/null +++ b/src/Settings/SettingsUtils.ts @@ -0,0 +1,141 @@ +import type { Result } from "@nsdefs"; +import { toggleSuppressErrorModals } from "../ErrorHandling/ErrorState"; +import { + assertAndSanitizeEditorTheme, + assertAndSanitizeKeyBindings, + assertAndSanitizeMainTheme, + assertAndSanitizeStyles, +} from "../JsonSchema/JSONSchemaAssertion"; +import { mergePlayerDefinedKeyBindings } from "../utils/KeyBindingUtils"; +import { assertObject } from "../utils/TypeAssertion"; +import { Settings } from "./Settings"; + +/** + * This function won't be able to catch **all** invalid hostnames. In order to validate a hostname properly, we need to + * import a good validation library or write one by ourselves. Considering that we only need to catch common mistakes, + * it's not worth the effort. + * + * Some invalid hostnames that we don't catch: + * - Invalid/missing TLD: "abc". + * - Use space character: "a a.com" + * - Use non-http schemes in the hostname: "ftp://a.com" + * - etc. + */ +export function isValidConnectionHostname(hostname: string): Result { + // Return a user-friendly error message. + if (hostname === "") { + return { + success: false, + message: "Hostname cannot be empty", + }; + } + /** + * We expect a hostname, but the player may mistakenly put other unexpected things. We will try to catch common mistakes: + * - Specify a scheme: http or https. + * - Specify a port. + * - Specify a pathname or search params. + */ + try { + // Check scheme. + if (hostname.startsWith("http://") || hostname.startsWith("https://")) { + return { + success: false, + message: "Do not specify scheme (e.g., http, https)", + }; + } + // Parse to a URL with a default scheme. + const url = new URL(`http://${hostname}`); + // Check port, pathname, and search params. + if (url.port !== "" || url.pathname !== "/" || url.search !== "") { + return { + success: false, + message: "Do not specify port, pathname, or search parameters", + }; + } + } catch (error) { + console.error(error); + return { + success: false, + message: `Invalid hostname: ${hostname}`, + }; + } + return { success: true }; +} + +export function isValidConnectionPort(port: number): Result { + // 0 is a special value for port. It's an invalid port, but the player can use it to disable RFA. + if (!Number.isFinite(port) || port < 0 || port > 65535) { + return { success: false, message: "Invalid port" }; + } + return { success: true }; +} + +export function loadSettings(saveString: string) { + const save: unknown = JSON.parse(saveString); + assertObject(save); + save.overview && Object.assign(Settings.overview, save.overview); + try { + // Sanitize theme data. Invalid theme data may crash the game or make it stuck in the loading page. + assertAndSanitizeMainTheme(save.theme); + Object.assign(Settings.theme, save.theme); + } catch (error) { + console.error(error); + } + try { + // Sanitize editor theme data. Invalid editor theme data may crash the game when the player opens the script editor. + assertAndSanitizeEditorTheme(save.EditorTheme); + Object.assign(Settings.EditorTheme, save.EditorTheme); + } catch (error) { + console.error(error); + } + try { + // Sanitize styles. + assertAndSanitizeStyles(save.styles); + Object.assign(Settings.styles, save.styles); + } catch (error) { + console.error(error); + } + /** + * KeyBindings data does not exist in old save files. Technically, this check is unnecessary. If KeyBindings is + * undefined, assertAndSanitizeKeyBindings will throw an error, and that error will be caught here. However, it + * means that there will be an error logged in the console every time the player loads an old save file, and this + * logged error is kind of a "false positive" one. + */ + if (save.KeyBindings !== undefined) { + try { + // Sanitize key bindings. + assertAndSanitizeKeyBindings(save.KeyBindings); + Object.assign(Settings.KeyBindings, save.KeyBindings); + } catch (error) { + console.error(error); + } + } + Object.assign(Settings, save, { + overview: Settings.overview, + theme: Settings.theme, + EditorTheme: Settings.EditorTheme, + styles: Settings.styles, + KeyBindings: Settings.KeyBindings, + }); + /** + * The hostname and port of RFA have not been validated properly, so the save data may contain invalid data. In that + * case, we set them to the default value. + */ + if (!isValidConnectionHostname(Settings.RemoteFileApiAddress).success) { + Settings.RemoteFileApiAddress = "localhost"; + } + if (!isValidConnectionPort(Settings.RemoteFileApiPort).success) { + Settings.RemoteFileApiPort = 0; + } + + // Merge Settings.KeyBindings with DefaultKeyBindings. + mergePlayerDefinedKeyBindings(Settings.KeyBindings); + + // Set up initial state for error modal suppression + toggleSuppressErrorModals(Settings.SuppressErrorModals, true); + + // Disable this feature for existing save files. + if (save.MonacoAutoSaveOnFocusChange === undefined) { + Settings.MonacoAutoSaveOnFocusChange = false; + } +}