diff --git a/frontend/__tests__/commandline/util.spec.ts b/frontend/__tests__/commandline/util.spec.ts index 27e7b988df62..0cab39f0e3b6 100644 --- a/frontend/__tests__/commandline/util.spec.ts +++ b/frontend/__tests__/commandline/util.spec.ts @@ -1,17 +1,17 @@ -//import type { ConfigMetadata } from "../../src/ts/config-metadata"; +//import type { ConfigMetadata } from "../../src/ts/config/metadata"; import { describe, it, expect, afterAll, vi } from "vitest"; import * as Util from "../../src/ts/commandline/util"; import type { CommandlineConfigMetadata } from "../../src/ts/commandline/commandline-metadata"; import type { ConfigKey } from "@monkeytype/schemas/configs"; -import type { ConfigMetadata } from "../../src/ts/config-metadata"; +import type { ConfigMetadata } from "../../src/ts/config/metadata"; import { z, ZodSchema } from "zod"; const buildCommandForConfigKey = Util.__testing._buildCommandForConfigKey; describe("CommandlineUtils", () => { - vi.mock("../../src/ts/config-metadata", () => ({ configMetadata: [] })); + vi.mock("../../src/ts/config/metadata", () => ({ configMetadata: [] })); vi.mock("../../src/ts/commandline/commandline-metadata", () => ({ commandlineConfigMetadata: [], })); diff --git a/frontend/__tests__/controllers/preset-controller.spec.ts b/frontend/__tests__/controllers/preset-controller.spec.ts index 24f21a517194..baef3596b82f 100644 --- a/frontend/__tests__/controllers/preset-controller.spec.ts +++ b/frontend/__tests__/controllers/preset-controller.spec.ts @@ -2,7 +2,11 @@ import { describe, it, expect, beforeEach, vi } from "vitest"; import * as PresetController from "../../src/ts/controllers/preset-controller"; import { Preset } from "@monkeytype/schemas/presets"; import * as DB from "../../src/ts/db"; -import * as UpdateConfig from "../../src/ts/config"; +import { setConfig } from "../../src/ts/config/setters"; +import { Config } from "../../src/ts/config/store"; +import * as Lifecycle from "../../src/ts/config/lifecycle"; +import * as ConfigUtils from "../../src/ts/config/utils"; +import * as Persistence from "../../src/ts/config/persistence"; import * as Notifications from "../../src/ts/states/notifications"; import * as TestLogic from "../../src/ts/test/test-logic"; import * as TagController from "../../src/ts/controllers/tag-controller"; @@ -16,13 +20,13 @@ describe("PresetController", () => { // })); const dbGetSnapshotMock = vi.spyOn(DB, "getSnapshot"); - const configApplyMock = vi.spyOn(UpdateConfig, "applyConfig"); + const configApplyMock = vi.spyOn(Lifecycle, "applyConfig"); const configSaveFullConfigMock = vi.spyOn( - UpdateConfig, + Persistence, "saveFullConfigToLocalStorage", ); const configGetConfigChangesMock = vi.spyOn( - UpdateConfig, + ConfigUtils, "getConfigChanges", ); const notificationAddMock = vi.spyOn( @@ -111,8 +115,8 @@ describe("PresetController", () => { settingGroups: ["test"], }); - UpdateConfig.setConfig("numbers", true); - const oldConfig = structuredClone(UpdateConfig.default); + setConfig("numbers", true); + const oldConfig = structuredClone(Config); //WHEN await PresetController.apply(preset._id); diff --git a/frontend/__tests__/controllers/url-handler.spec.ts b/frontend/__tests__/controllers/url-handler.spec.ts index 43681e2713dc..10b5804969cf 100644 --- a/frontend/__tests__/controllers/url-handler.spec.ts +++ b/frontend/__tests__/controllers/url-handler.spec.ts @@ -1,7 +1,7 @@ import { describe, it, expect, beforeEach, vi } from "vitest"; import { Difficulty, Mode, Mode2 } from "@monkeytype/schemas/shared"; import { compressToURI } from "lz-ts"; -import * as UpdateConfig from "../../src/ts/config"; +import * as UpdateConfig from "../../src/ts/config/setters"; import * as Notifications from "../../src/ts/states/notifications"; import * as TestLogic from "../../src/ts/test/test-logic"; import * as TestState from "../../src/ts/test/test-state"; diff --git a/frontend/__tests__/input/helpers/fail-or-finish.spec.ts b/frontend/__tests__/input/helpers/fail-or-finish.spec.ts index c45977d7e1b9..2893e07d0dcb 100644 --- a/frontend/__tests__/input/helpers/fail-or-finish.spec.ts +++ b/frontend/__tests__/input/helpers/fail-or-finish.spec.ts @@ -4,7 +4,7 @@ import { checkIfFailedDueToDifficulty, checkIfFinished, } from "../../../src/ts/input/helpers/fail-or-finish"; -import { __testing } from "../../../src/ts/config"; +import { __testing } from "../../../src/ts/config/testing"; import * as Misc from "../../../src/ts/utils/misc"; import * as TestLogic from "../../../src/ts/test/test-logic"; import * as Strings from "../../../src/ts/utils/strings"; diff --git a/frontend/__tests__/input/helpers/validation.spec.ts b/frontend/__tests__/input/helpers/validation.spec.ts index 08920a5a1465..94b3b2416230 100644 --- a/frontend/__tests__/input/helpers/validation.spec.ts +++ b/frontend/__tests__/input/helpers/validation.spec.ts @@ -3,7 +3,7 @@ import { isCharCorrect, shouldInsertSpaceCharacter, } from "../../../src/ts/input/helpers/validation"; -import { __testing } from "../../../src/ts/config"; +import { __testing } from "../../../src/ts/config/testing"; import * as FunboxList from "../../../src/ts/test/funbox/list"; import * as Strings from "../../../src/ts/utils/strings"; diff --git a/frontend/__tests__/root/config-metadata.spec.ts b/frontend/__tests__/root/config-metadata.spec.ts index 01af2874b82d..d8db8f348d75 100644 --- a/frontend/__tests__/root/config-metadata.spec.ts +++ b/frontend/__tests__/root/config-metadata.spec.ts @@ -1,9 +1,10 @@ import { describe, it, expect, afterAll, vi } from "vitest"; -import { configMetadata } from "../../src/ts/config-metadata"; -import * as Config from "../../src/ts/config"; +import { configMetadata } from "../../src/ts/config/metadata"; +import { __testing } from "../../src/ts/config/testing"; +import { setConfig } from "../../src/ts/config/setters"; import { ConfigKey, Config as ConfigType } from "@monkeytype/schemas/configs"; -const { replaceConfig, getConfig } = Config.__testing; +const { replaceConfig, getConfig } = __testing; type TestsByConfig = Partial<{ [K in keyof ConfigType]: (T & { value: ConfigType[K] })[]; @@ -138,7 +139,7 @@ describe("ConfigMeta", () => { replaceConfig(given ?? {}); //WHEN - Config.setConfig(key, value as any); + setConfig(key, value as any); //THEN expect(getConfig()).toMatchObject(expected); @@ -175,7 +176,7 @@ describe("ConfigMeta", () => { replaceConfig(given ?? {}); //WHEN - const applied = Config.setConfig(key, value as any); + const applied = setConfig(key, value as any); //THEN expect(applied).toEqual(!fail); @@ -335,7 +336,7 @@ describe("ConfigMeta", () => { replaceConfig(given); //WHEN - Config.setConfig(key, value as any); + setConfig(key, value as any); //THEN expect(getConfig()).toMatchObject(expected ?? {}); diff --git a/frontend/__tests__/root/config.spec.ts b/frontend/__tests__/root/config.spec.ts index fd738d9a9d2e..90eb7ca8fa44 100644 --- a/frontend/__tests__/root/config.spec.ts +++ b/frontend/__tests__/root/config.spec.ts @@ -1,5 +1,8 @@ import { describe, it, expect, beforeEach, afterAll, vi } from "vitest"; -import * as Config from "../../src/ts/config"; +import * as Config from "../../src/ts/config/setters"; +import * as Lifecycle from "../../src/ts/config/lifecycle"; +import * as ConfigUtils from "../../src/ts/config/utils"; +import { __testing } from "../../src/ts/config/testing"; import * as Misc from "../../src/ts/utils/misc"; import * as Env from "../../src/ts/utils/env"; import { @@ -8,11 +11,11 @@ import { CaretStyleSchema, } from "@monkeytype/schemas/configs"; import * as FunboxValidation from "../../src/ts/test/funbox/funbox-validation"; -import * as ConfigValidation from "../../src/ts/config-validation"; +import * as ConfigValidation from "../../src/ts/config/validation"; import * as ConfigEvent from "../../src/ts/observables/config-event"; import * as ApeConfig from "../../src/ts/ape/config"; import * as Notifications from "../../src/ts/states/notifications"; -const { replaceConfig, getConfig } = Config.__testing; +const { replaceConfig, getConfig } = __testing; describe("Config", () => { const isDevEnvironmentMock = vi.spyOn(Env, "isDevEnvironment"); @@ -281,7 +284,7 @@ describe("Config", () => { replaceConfig({ mode: "words", }); - await Config.applyConfig({ + await Lifecycle.applyConfig({ numbers: true, punctuation: true, }); @@ -326,7 +329,7 @@ describe("Config", () => { ]; it.each(testCases)("$display", async ({ value, expected }) => { - await Config.applyConfig(value); + await Lifecycle.applyConfig(value); const config = getConfig(); const applied = Object.fromEntries( @@ -363,7 +366,7 @@ describe("Config", () => { ]; it.each(testCases)("$display", async ({ value, expected }) => { - await Config.applyConfig(value); + await Lifecycle.applyConfig(value); const config = getConfig(); const applied = Object.fromEntries( Object.entries(config).filter(([key]) => @@ -378,8 +381,8 @@ describe("Config", () => { replaceConfig({ numbers: true, }); - await Config.applyConfig({ - ...Config.getConfigChanges(), + await Lifecycle.applyConfig({ + ...ConfigUtils.getConfigChanges(), punctuation: true, }); const config = getConfig(); @@ -390,7 +393,7 @@ describe("Config", () => { replaceConfig({ minWpm: "off", }); - await Config.applyConfig({ + await Lifecycle.applyConfig({ minWpmCustomSpeed: 100, }); const config = getConfig(); @@ -402,7 +405,7 @@ describe("Config", () => { replaceConfig({ minWpm: "off", }); - await Config.applyConfig({ + await Lifecycle.applyConfig({ minWpm: "custom", minWpmCustomSpeed: 100, }); @@ -413,7 +416,7 @@ describe("Config", () => { it("should keep the keymap off when applying keymapLayout", async () => { replaceConfig({}); - await Config.applyConfig({ + await Lifecycle.applyConfig({ keymapLayout: "qwerty", }); const config = getConfig(); diff --git a/frontend/__tests__/test/british-english.spec.ts b/frontend/__tests__/test/british-english.spec.ts index 12a7e53f4915..e52bf74131f6 100644 --- a/frontend/__tests__/test/british-english.spec.ts +++ b/frontend/__tests__/test/british-english.spec.ts @@ -1,6 +1,6 @@ import { describe, it, expect, beforeEach } from "vitest"; import { replace } from "../../src/ts/test/british-english"; -import Config from "../../src/ts/config"; +import { Config } from "../../src/ts/config/store"; describe("british-english", () => { describe("replace", () => { diff --git a/frontend/__tests__/utils/config.spec.ts b/frontend/__tests__/utils/config.spec.ts index 085cab2cf46c..3c29fde39b9d 100644 --- a/frontend/__tests__/utils/config.spec.ts +++ b/frontend/__tests__/utils/config.spec.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from "vitest"; import { getDefaultConfig } from "../../src/ts/constants/default-config"; -import { migrateConfig } from "../../src/ts/utils/config"; +import { migrateConfig } from "../../src/ts/config/utils"; import { PartialConfig } from "@monkeytype/schemas/configs"; const defaultConfig = getDefaultConfig(); diff --git a/frontend/package.json b/frontend/package.json index 3f446bd6b3b1..2a081b30f334 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -30,7 +30,7 @@ "@monkeytype/funbox": "workspace:*", "@monkeytype/schemas": "workspace:*", "@monkeytype/util": "workspace:*", - "@sentry/browser": "9.14.0", + "@sentry/browser": "10.44.0", "@sentry/vite-plugin": "3.3.1", "@solid-devtools/overlay": "0.33.5", "@solid-primitives/refs": "1.1.2", @@ -107,7 +107,7 @@ "oxlint": "1.50.0", "oxlint-tsgolint": "0.14.2", "postcss": "8.5.8", - "sass": "1.98.0", + "sass": "1.70.0", "solid-devtools": "0.34.5", "solid-js": "1.9.10", "subset-font": "2.3.0", diff --git a/frontend/src/ts/auth.tsx b/frontend/src/ts/auth.tsx index 5b25cd7c4fb8..5fc6c39b58d5 100644 --- a/frontend/src/ts/auth.tsx +++ b/frontend/src/ts/auth.tsx @@ -10,7 +10,7 @@ import { import Ape from "./ape"; import { showRegisterCaptchaModal } from "./components/modals/RegisterCaptchaModal"; -import { updateFromServer as updateConfigFromServer } from "./config"; +import { updateFromServer as updateConfigFromServer } from "./config/remote"; import * as DB from "./db"; import { isAuthAvailable, diff --git a/frontend/src/ts/commandline/commandline-metadata.ts b/frontend/src/ts/commandline/commandline-metadata.ts index a1cdb9c2d1a2..9744c9ee16ab 100644 --- a/frontend/src/ts/commandline/commandline-metadata.ts +++ b/frontend/src/ts/commandline/commandline-metadata.ts @@ -5,7 +5,7 @@ import { getLanguageDisplayString } from "../utils/strings"; import * as ModesNotice from "../elements/modes-notice"; import { isAuthenticated } from "../firebase"; import { areUnsortedArraysEqual } from "../utils/arrays"; -import Config from "../config"; +import { Config } from "../config/store"; import { get as getTypingSpeedUnit } from "../utils/typing-speed-units"; import { getActivePage } from "../states/core"; import { Fonts } from "../constants/fonts"; diff --git a/frontend/src/ts/commandline/commandline.ts b/frontend/src/ts/commandline/commandline.ts index 61959f04b023..baddf180a06d 100644 --- a/frontend/src/ts/commandline/commandline.ts +++ b/frontend/src/ts/commandline/commandline.ts @@ -1,6 +1,6 @@ import * as Focus from "../test/focus"; import * as CommandlineLists from "./lists"; -import Config from "../config"; +import { Config } from "../config/store"; import * as AnalyticsController from "../controllers/analytics-controller"; import * as ThemeController from "../controllers/theme-controller"; import { clearFontPreview } from "../ui"; diff --git a/frontend/src/ts/commandline/lists.ts b/frontend/src/ts/commandline/lists.ts index 0a59523967b3..2af52dec0463 100644 --- a/frontend/src/ts/commandline/lists.ts +++ b/frontend/src/ts/commandline/lists.ts @@ -16,7 +16,8 @@ import LoadChallengeCommands, { update as updateLoadChallengeCommands, } from "./lists/load-challenge"; -import Config, { applyConfigFromJson, setConfig } from "../config"; +import { Config } from "../config/store"; +import { setConfig } from "../config/setters"; import * as getErrorMessage from "../utils/error"; import * as JSONData from "../utils/json-data"; import { randomizeTheme } from "../controllers/theme-controller"; @@ -39,6 +40,7 @@ import { hideFpsCounter, showFpsCounter, } from "../components/layout/overlays/FpsCounter"; +import { applyConfigFromJson } from "../config/lifecycle"; const challengesPromise = JSONData.getChallengeList(); challengesPromise diff --git a/frontend/src/ts/commandline/lists/add-or-remove-theme-to-favorites.ts b/frontend/src/ts/commandline/lists/add-or-remove-theme-to-favorites.ts index f311f8fc05bd..c687c693e142 100644 --- a/frontend/src/ts/commandline/lists/add-or-remove-theme-to-favorites.ts +++ b/frontend/src/ts/commandline/lists/add-or-remove-theme-to-favorites.ts @@ -1,5 +1,6 @@ import { ThemeName } from "@monkeytype/schemas/configs"; -import Config, { setConfig } from "../../config"; +import { Config } from "../../config/store"; +import { setConfig } from "../../config/setters"; import { randomTheme } from "../../controllers/theme-controller"; import { Command } from "../types"; diff --git a/frontend/src/ts/commandline/lists/background-filter.ts b/frontend/src/ts/commandline/lists/background-filter.ts index 2958b4447e61..01bd3ecfdc02 100644 --- a/frontend/src/ts/commandline/lists/background-filter.ts +++ b/frontend/src/ts/commandline/lists/background-filter.ts @@ -1,4 +1,5 @@ -import Config, { setConfig } from "../../config"; +import { Config } from "../../config/store"; +import { setConfig } from "../../config/setters"; import { Command, CommandsSubgroup } from "../types"; const subgroup: CommandsSubgroup = { diff --git a/frontend/src/ts/commandline/lists/bail-out.ts b/frontend/src/ts/commandline/lists/bail-out.ts index c750312f2ac6..319701b3c6df 100644 --- a/frontend/src/ts/commandline/lists/bail-out.ts +++ b/frontend/src/ts/commandline/lists/bail-out.ts @@ -1,4 +1,4 @@ -import Config from "../../config"; +import { Config } from "../../config/store"; import * as CustomText from "../../test/custom-text"; import * as TestLogic from "../../test/test-logic"; import * as TestState from "../../test/test-state"; diff --git a/frontend/src/ts/commandline/lists/custom-background.ts b/frontend/src/ts/commandline/lists/custom-background.ts index 3f4624410657..795f20929695 100644 --- a/frontend/src/ts/commandline/lists/custom-background.ts +++ b/frontend/src/ts/commandline/lists/custom-background.ts @@ -4,7 +4,8 @@ import FileStorage from "../../utils/file-storage"; import { applyCustomBackground } from "../../controllers/theme-controller"; import { updateUI } from "../../elements/settings/custom-background-picker"; import { showNoticeNotification } from "../../states/notifications"; -import Config, { setConfig } from "../../config"; +import { Config } from "../../config/store"; +import { setConfig } from "../../config/setters"; const fromMeta = buildCommandForConfigKey("customBackground"); diff --git a/frontend/src/ts/commandline/lists/custom-themes-list.ts b/frontend/src/ts/commandline/lists/custom-themes-list.ts index fe112c258ac9..aacf492df125 100644 --- a/frontend/src/ts/commandline/lists/custom-themes-list.ts +++ b/frontend/src/ts/commandline/lists/custom-themes-list.ts @@ -1,4 +1,4 @@ -import { setConfig } from "../../config"; +import { setConfig } from "../../config/setters"; import { isAuthenticated } from "../../firebase"; import * as DB from "../../db"; import * as ThemeController from "../../controllers/theme-controller"; diff --git a/frontend/src/ts/commandline/lists/font-family.ts b/frontend/src/ts/commandline/lists/font-family.ts index 3ef441151cda..acfc08ca9a37 100644 --- a/frontend/src/ts/commandline/lists/font-family.ts +++ b/frontend/src/ts/commandline/lists/font-family.ts @@ -4,8 +4,8 @@ import FileStorage from "../../utils/file-storage"; import { applyFontFamily } from "../../controllers/theme-controller"; import { updateUI } from "../../elements/settings/custom-font-picker"; import { showNoticeNotification } from "../../states/notifications"; -import Config, { setConfig } from "../../config"; - +import { Config } from "../../config/store"; +import { setConfig } from "../../config/setters"; const fromMeta = buildCommandForConfigKey("fontFamily"); if (fromMeta.subgroup) { diff --git a/frontend/src/ts/commandline/lists/min-burst.ts b/frontend/src/ts/commandline/lists/min-burst.ts index bf8480f3987b..1152e476c68e 100644 --- a/frontend/src/ts/commandline/lists/min-burst.ts +++ b/frontend/src/ts/commandline/lists/min-burst.ts @@ -1,4 +1,5 @@ -import Config, { setConfig } from "../../config"; +import { Config } from "../../config/store"; +import { setConfig } from "../../config/setters"; import { get as getTypingSpeedUnit } from "../../utils/typing-speed-units"; import { Command, CommandsSubgroup } from "../types"; diff --git a/frontend/src/ts/commandline/lists/quote-favorites.ts b/frontend/src/ts/commandline/lists/quote-favorites.ts index adb329968f6a..ecfe6bf8876b 100644 --- a/frontend/src/ts/commandline/lists/quote-favorites.ts +++ b/frontend/src/ts/commandline/lists/quote-favorites.ts @@ -1,4 +1,4 @@ -import Config from "../../config"; +import { Config } from "../../config/store"; import QuotesController, { Quote } from "../../controllers/quotes-controller"; import { showErrorNotification, diff --git a/frontend/src/ts/commandline/lists/result-screen.ts b/frontend/src/ts/commandline/lists/result-screen.ts index 0ab77c979907..1debee3ac804 100644 --- a/frontend/src/ts/commandline/lists/result-screen.ts +++ b/frontend/src/ts/commandline/lists/result-screen.ts @@ -8,7 +8,7 @@ import { import * as TestInput from "../../test/test-input"; import * as TestState from "../../test/test-state"; import * as TestWords from "../../test/test-words"; -import Config from "../../config"; +import { Config } from "../../config/store"; import * as PractiseWords from "../../test/practise-words"; import { Command, CommandsSubgroup } from "../types"; import * as TestScreenshot from "../../test/test-screenshot"; diff --git a/frontend/src/ts/commandline/lists/tags.ts b/frontend/src/ts/commandline/lists/tags.ts index 41d676c6576b..33a18be8abf9 100644 --- a/frontend/src/ts/commandline/lists/tags.ts +++ b/frontend/src/ts/commandline/lists/tags.ts @@ -2,7 +2,7 @@ import * as DB from "../../db"; import * as EditTagsPopup from "../../modals/edit-tag"; import * as ModesNotice from "../../elements/modes-notice"; import * as TagController from "../../controllers/tag-controller"; -import Config from "../../config"; +import { Config } from "../../config/store"; import * as PaceCaret from "../../test/pace-caret"; import { isAuthenticated } from "../../firebase"; import { Command, CommandsSubgroup } from "../types"; diff --git a/frontend/src/ts/commandline/lists/themes.ts b/frontend/src/ts/commandline/lists/themes.ts index ec4d4d296008..0b4874f72bdb 100644 --- a/frontend/src/ts/commandline/lists/themes.ts +++ b/frontend/src/ts/commandline/lists/themes.ts @@ -1,4 +1,5 @@ -import Config, { setConfig } from "../../config"; +import { Config } from "../../config/store"; +import { setConfig } from "../../config/setters"; import { capitalizeFirstLetterOfEachWord } from "../../utils/strings"; import * as ThemeController from "../../controllers/theme-controller"; import { Command, CommandsSubgroup } from "../types"; diff --git a/frontend/src/ts/commandline/util.ts b/frontend/src/ts/commandline/util.ts index 714233901f33..ec65be67ff1f 100644 --- a/frontend/src/ts/commandline/util.ts +++ b/frontend/src/ts/commandline/util.ts @@ -1,5 +1,6 @@ -import Config, { setConfig } from "../config"; -import { ConfigMetadata, configMetadata } from "../config-metadata"; +import { Config } from "../config/store"; +import { setConfig } from "../config/setters"; +import { ConfigMetadata, configMetadata } from "../config/metadata"; import { capitalizeFirstLetter } from "../utils/strings"; import { CommandlineConfigMetadata, diff --git a/frontend/src/ts/components/layout/footer/Keytips.tsx b/frontend/src/ts/components/layout/footer/Keytips.tsx index 071a972bd304..3c703888f71f 100644 --- a/frontend/src/ts/components/layout/footer/Keytips.tsx +++ b/frontend/src/ts/components/layout/footer/Keytips.tsx @@ -1,6 +1,6 @@ import { JSXElement, Show } from "solid-js"; -import { getConfig } from "../../../states/config"; +import { getConfig } from "../../../config/store"; import { getFocus } from "../../../states/core"; import { Conditional } from "../../common/Conditional"; diff --git a/frontend/src/ts/components/layout/footer/ThemeIndicator.tsx b/frontend/src/ts/components/layout/footer/ThemeIndicator.tsx index 58902d1b2ca8..9b91c232e84b 100644 --- a/frontend/src/ts/components/layout/footer/ThemeIndicator.tsx +++ b/frontend/src/ts/components/layout/footer/ThemeIndicator.tsx @@ -1,6 +1,7 @@ import { JSXElement, Show } from "solid-js"; -import Config, { setConfig } from "../../../config"; +import { setConfig } from "../../../config/setters"; +import { Config } from "../../../config/store"; import { isAuthenticated } from "../../../firebase"; import { getThemeIndicator, diff --git a/frontend/src/ts/components/pages/AboutPage.tsx b/frontend/src/ts/components/pages/AboutPage.tsx index 11e26d157e1a..3296d2ebf3e6 100644 --- a/frontend/src/ts/components/pages/AboutPage.tsx +++ b/frontend/src/ts/components/pages/AboutPage.tsx @@ -1,6 +1,7 @@ import { useQuery } from "@tanstack/solid-query"; import { For, JSXElement, Show } from "solid-js"; +import { getConfig } from "../../config/store"; import { queryClient } from "../../queries"; import { getContributorsQueryOptions, @@ -8,7 +9,6 @@ import { getSupportersQueryOptions, getTypingStatsQueryOptions, } from "../../queries/public"; -import { getConfig } from "../../states/config"; import { getActivePage } from "../../states/core"; import { showModal } from "../../states/modals"; import { getTheme } from "../../states/theme"; diff --git a/frontend/src/ts/components/pages/leaderboard/Table.tsx b/frontend/src/ts/components/pages/leaderboard/Table.tsx index 8a74b8c0a5ea..27c1d8cb8b68 100644 --- a/frontend/src/ts/components/pages/leaderboard/Table.tsx +++ b/frontend/src/ts/components/pages/leaderboard/Table.tsx @@ -7,10 +7,10 @@ import { format as dateFormat } from "date-fns/format"; import { formatDistanceToNow } from "date-fns/formatDistanceToNow"; import { Accessor, createMemo, JSXElement } from "solid-js"; +import { getConfig } from "../../../config/store"; import { isFriend } from "../../../db"; import { createEffectOn } from "../../../hooks/effects"; import { bp, BreakpointKey } from "../../../states/breakpoints"; -import { getConfig } from "../../../states/config"; import { getUserId } from "../../../states/core"; import { cn } from "../../../utils/cn"; import { secondsToString } from "../../../utils/date-and-time"; diff --git a/frontend/src/ts/components/pages/leaderboard/UserRank.tsx b/frontend/src/ts/components/pages/leaderboard/UserRank.tsx index 6baa112c1b1c..77ab276bea08 100644 --- a/frontend/src/ts/components/pages/leaderboard/UserRank.tsx +++ b/frontend/src/ts/components/pages/leaderboard/UserRank.tsx @@ -5,7 +5,7 @@ import { import { formatDuration, intervalToDuration } from "date-fns"; import { createMemo, JSXElement, Match, Show, Switch } from "solid-js"; -import { getConfig } from "../../../states/config"; +import { getConfig } from "../../../config/store"; import { Formatting } from "../../../utils/format"; import { Conditional } from "../../common/Conditional"; import { Fa } from "../../common/Fa"; diff --git a/frontend/src/ts/components/pages/profile/UserProfile.tsx b/frontend/src/ts/components/pages/profile/UserProfile.tsx index f88517e95d1d..3bc907fb4b1e 100644 --- a/frontend/src/ts/components/pages/profile/UserProfile.tsx +++ b/frontend/src/ts/components/pages/profile/UserProfile.tsx @@ -6,8 +6,8 @@ import { import { formatDate } from "date-fns/format"; import { createMemo, For, JSXElement, Show } from "solid-js"; +import { getConfig } from "../../../config/store"; import * as PbTablesModal from "../../../modals/pb-tables"; -import { getConfig } from "../../../states/config"; import { Formatting } from "../../../utils/format"; import { formatTopPercentage } from "../../../utils/misc"; import { Button } from "../../common/Button"; diff --git a/frontend/src/ts/config.ts b/frontend/src/ts/config.ts deleted file mode 100644 index a890466ec441..000000000000 --- a/frontend/src/ts/config.ts +++ /dev/null @@ -1,422 +0,0 @@ -import { - showNoticeNotification, - showErrorNotification, - showSuccessNotification, -} from "./states/notifications"; -import { isConfigValueValid } from "./config-validation"; -import * as ConfigEvent from "./observables/config-event"; -import { debounce } from "throttle-debounce"; -import { - canSetConfigWithCurrentFunboxes, - canSetFunboxWithConfig, -} from "./test/funbox/funbox-validation"; -import { - isObject, - promiseWithResolvers, - triggerResize, - typedKeys, -} from "./utils/misc"; -import * as ConfigSchemas from "@monkeytype/schemas/configs"; -import { Config, FunboxName } from "@monkeytype/schemas/configs"; -import { LocalStorageWithSchema } from "./utils/local-storage-with-schema"; -import { migrateConfig } from "./utils/config"; -import { getDefaultConfig } from "./constants/default-config"; -import { parseWithSchema as parseJsonWithSchema } from "@monkeytype/util/json"; -import { ZodSchema } from "zod"; -import * as TestState from "./test/test-state"; -import { ConfigMetadataObject, configMetadata } from "./config-metadata"; -import { setAccountButtonSpinner } from "./states/header"; -import { deleteConfig, saveConfig } from "./ape/config"; -import Ape from "./ape"; -import { SnapshotInitError } from "./db"; - -const configLS = new LocalStorageWithSchema({ - key: "config", - schema: ConfigSchemas.ConfigSchema, - fallback: getDefaultConfig(), - migrate: (value, _issues) => { - if (!isObject(value)) { - return getDefaultConfig(); - } - //todo maybe send a full config to db so that it removes legacy values - - return migrateConfig(value); - }, -}); - -let config: Config = { - ...getDefaultConfig(), -}; - -let configToSend: Partial = {}; -const saveToDatabase = debounce(1000, () => { - if (Object.keys(configToSend).length > 0) { - setAccountButtonSpinner(true); - void saveConfig(configToSend).finally(() => { - setAccountButtonSpinner(false); - }); - } - configToSend = {} as Config; -}); - -function saveToLocalStorage( - key: keyof Config, - nosave = false, - noDbCheck = false, -): void { - if (nosave) return; - configLS.set(config); - if (!noDbCheck) { - //@ts-expect-error this is fine - configToSend[key] = config[key]; - saveToDatabase(); - } -} - -export function saveFullConfigToLocalStorage(noDbCheck = false): void { - console.log("saving full config to localStorage"); - configLS.set(config); - if (!noDbCheck) { - setAccountButtonSpinner(true); - void saveConfig(config).finally(() => { - setAccountButtonSpinner(false); - }); - } -} - -function isConfigChangeBlocked(): boolean { - if (TestState.isActive && config.funbox.includes("no_quit")) { - showNoticeNotification( - "No quit funbox is active. Please finish the test.", - { - important: true, - }, - ); - return true; - } - return false; -} - -export function setConfig( - key: T, - value: Config[T], - options?: { - nosave?: boolean; - }, -): boolean { - const metadata = configMetadata[key] as ConfigMetadataObject[T]; - if (metadata === undefined) { - throw new Error(`Config metadata for key "${key}" is not defined.`); - } - - if (metadata.overrideValue) { - value = metadata.overrideValue({ - value, - currentValue: config[key], - currentConfig: config, - }); - } - - const previousValue = config[key]; - - if ( - metadata.changeRequiresRestart && - TestState.isActive && - config.funbox.includes("no_quit") - ) { - showNoticeNotification( - "No quit funbox is active. Please finish the test.", - { - important: true, - }, - ); - console.warn( - `Could not set config key "${key}" with value "${JSON.stringify( - value, - )}" - no quit funbox active.`, - ); - return false; - } - - if (metadata.isBlocked?.({ value, currentConfig: config })) { - console.warn( - `Could not set config key "${key}" with value "${JSON.stringify( - value, - )}" - blocked.`, - ); - return false; - } - - const schema = ConfigSchemas.ConfigSchema.shape[key] as ZodSchema; - - if (!isConfigValueValid(metadata.displayString ?? key, value, schema)) { - console.warn( - `Could not set config key "${key}" with value "${JSON.stringify( - value, - )}" - invalid value.`, - ); - return false; - } - - if (!canSetConfigWithCurrentFunboxes(key, value, config.funbox)) { - console.warn( - `Could not set config key "${key}" with value "${JSON.stringify( - value, - )}" - funbox conflict.`, - ); - return false; - } - - if (metadata.overrideConfig) { - const targetConfig = metadata.overrideConfig({ - value, - currentConfig: config, - }); - - for (const targetKey of typedKeys(targetConfig)) { - const targetValue = targetConfig[ - targetKey - ] as ConfigSchemas.Config[keyof typeof configMetadata]; - - if (config[targetKey] === targetValue) { - continue; // no need to set if the value is already the same - } - - const set = setConfig(targetKey, targetValue, options); - if (!set) { - throw new Error( - `Failed to set config key "${targetKey}" with value "${targetValue}" for ${metadata.displayString} config override.`, - ); - } - } - } - - config[key] = value; - if (!options?.nosave) saveToLocalStorage(key, options?.nosave); - - // @ts-expect-error i can't figure this out - ConfigEvent.dispatch({ - key: key, - newValue: value, - nosave: options?.nosave ?? false, - previousValue: previousValue as Config[T], - }); - - if (metadata.triggerResize && !options?.nosave) { - triggerResize(); - } - - metadata.afterSet?.({ - nosave: options?.nosave ?? false, - currentConfig: config, - }); - return true; -} - -export function toggleFunbox(funbox: FunboxName, nosave?: boolean): boolean { - if (isConfigChangeBlocked()) return false; - - if (!canSetFunboxWithConfig(funbox, config)) { - return false; - } - - const previousValue = config.funbox; - - let newConfig: FunboxName[] = config.funbox; - - if (newConfig.includes(funbox)) { - newConfig = newConfig.filter((it) => it !== funbox); - } else { - newConfig.push(funbox); - newConfig.sort(); - } - - if (!isConfigValueValid("funbox", newConfig, ConfigSchemas.FunboxSchema)) { - return false; - } - - config.funbox = newConfig; - saveToLocalStorage("funbox", nosave); - ConfigEvent.dispatch({ - key: "funbox", - newValue: config.funbox, - nosave, - previousValue, - }); - - return true; -} - -export function setQuoteLengthAll(nosave?: boolean): boolean { - return setConfig("quoteLength", [0, 1, 2, 3], { - nosave, - }); -} - -const lastConfigsToApply: Set = new Set([ - "keymapMode", - "minWpm", - "minAcc", - "minBurst", - "paceCaret", - "quoteLength", //quote length sets mode, - "words", - "time", - "mode", // mode sets punctuation and numbers - "numbers", - "punctuation", - "funbox", -]); - -export async function applyConfig( - partialConfig: Partial, -): Promise { - if (partialConfig === undefined || partialConfig === null) return; - - //migrate old values if needed, remove additional keys and merge with default config - const fullConfig: Config = migrateConfig(partialConfig); - - ConfigEvent.dispatch({ key: "fullConfigChange" }); - - const defaultConfig = getDefaultConfig(); - for (const key of typedKeys(fullConfig)) { - //@ts-expect-error this is fine, both are of type config - config[key] = defaultConfig[key]; - } - - const configKeysToReset: (keyof Config)[] = []; - - const firstKeys = typedKeys(fullConfig).filter( - (key) => !lastConfigsToApply.has(key), - ); - - for (const configKey of [...firstKeys, ...lastConfigsToApply]) { - const configValue = fullConfig[configKey]; - - const set = setConfig(configKey, configValue, { nosave: true }); - - if (!set) { - configKeysToReset.push(configKey); - } - } - - for (const key of configKeysToReset) { - saveToLocalStorage(key); - } - - ConfigEvent.dispatch({ key: "fullConfigChangeFinished" }); -} - -export async function resetConfig(): Promise { - await applyConfig(getDefaultConfig()); - await deleteConfig(); - saveFullConfigToLocalStorage(true); -} - -export async function loadFromLocalStorage(): Promise { - console.log("loading localStorage config"); - const newConfig = configLS.get(); - if (newConfig === undefined) { - await resetConfig(); - } else { - await applyConfig(newConfig); - saveFullConfigToLocalStorage(true); - } - loadDone(); -} - -export function getConfigChanges(): Partial { - const configChanges: Partial = {}; - typedKeys(config) - .filter((key) => { - return config[key] !== getDefaultConfig()[key]; - }) - .forEach((key) => { - //@ts-expect-error this is fine - configChanges[key] = config[key]; - }); - return configChanges; -} - -export async function applyConfigFromJson(json: string): Promise { - try { - const parsedConfig = parseJsonWithSchema( - json, - ConfigSchemas.PartialConfigSchema.strip(), - { - migrate: (value) => { - if (Array.isArray(value)) { - throw new Error("Invalid config"); - } - return migrateConfig(value); - }, - }, - ); - await applyConfig(parsedConfig); - saveFullConfigToLocalStorage(); - showSuccessNotification("Done"); - } catch (e) { - console.error(e); - showErrorNotification("Failed to import settings", { error: e }); - } -} - -export async function updateFromServer(): Promise { - const remoteConfig = await getRemoteConfig(); - - const areConfigsEqual = - JSON.stringify(config) === JSON.stringify(remoteConfig); - - if (config === undefined || !areConfigsEqual) { - console.log( - "no local config or local and db configs are different - applying db", - ); - await applyConfig(remoteConfig); - saveFullConfigToLocalStorage(true); - } -} - -async function getRemoteConfig(): Promise { - const response = await Ape.configs.get(); - - if (response.status !== 200) { - throw new SnapshotInitError( - `${response.body.message} (config)`, - response.status, - ); - } - - const configData = response.body.data; - if (configData !== null && "config" in configData) { - throw new Error( - "Config data is not in the correct format. Please refresh the page or contact support.", - ); - } - - if (configData === undefined || configData === null) { - return { - ...getDefaultConfig(), - }; - } else { - return migrateConfig(configData); - } -} - -const { promise: configLoadPromise, resolve: loadDone } = - promiseWithResolvers(); - -export const getConfig = (): Config => config; -export { configLoadPromise }; -export default config; -export const __testing = { - configMetadata, - replaceConfig: (setConfig: Partial): void => { - const newConfig = { ...getDefaultConfig(), ...setConfig }; - for (const key of Object.keys(config)) { - Reflect.deleteProperty(config, key); - } - Object.assign(config, newConfig); - configToSend = {} as Config; - }, - getConfig: () => config, -}; diff --git a/frontend/src/ts/config/lifecycle.ts b/frontend/src/ts/config/lifecycle.ts new file mode 100644 index 000000000000..636c38481bb6 --- /dev/null +++ b/frontend/src/ts/config/lifecycle.ts @@ -0,0 +1,123 @@ +import * as ConfigSchemas from "@monkeytype/schemas/configs"; +import { parseWithSchema as parseJsonWithSchema } from "@monkeytype/util/json"; +import { + showSuccessNotification, + showErrorNotification, +} from "../states/notifications"; +import { + configLS, + saveToLocalStorage, + saveFullConfigToLocalStorage, +} from "./persistence"; +import { Config, setConfigStore } from "./store"; +import { getDefaultConfig } from "../constants/default-config"; +import * as ConfigEvent from "../observables/config-event"; +import { migrateConfig } from "./utils"; +import { promiseWithResolvers, typedKeys } from "../utils/misc"; +import { setConfig } from "./setters"; +import { deleteConfig } from "../ape/config"; +import { reconcile } from "solid-js/store"; + +export async function applyConfigFromJson(json: string): Promise { + try { + const parsedConfig = parseJsonWithSchema( + json, + ConfigSchemas.PartialConfigSchema.strip(), + { + migrate: (value) => { + if (Array.isArray(value)) { + throw new Error("Invalid config"); + } + return migrateConfig(value); + }, + }, + ); + await applyConfig(parsedConfig); + saveFullConfigToLocalStorage(); + showSuccessNotification("Done"); + } catch (e) { + console.error(e); + showErrorNotification("Failed to import settings", { error: e }); + } +} + +export async function loadFromLocalStorage(): Promise { + console.log("loading localStorage config"); + const newConfig = configLS.get(); + if (newConfig === undefined) { + await resetConfig(); + } else { + await applyConfig(newConfig); + saveFullConfigToLocalStorage(true); + } + loadDone(); +} + +const lastConfigsToApply: Set = new Set([ + "keymapMode", + "minWpm", + "minAcc", + "minBurst", + "paceCaret", + "quoteLength", //quote length sets mode, + "words", + "time", + "mode", // mode sets punctuation and numbers + "numbers", + "punctuation", + "funbox", +]); + +export async function applyConfig( + partialConfig: Partial, +): Promise { + if (partialConfig === undefined || partialConfig === null) return; + + //migrate old values if needed, remove additional keys and merge with default config + const fullConfig: ConfigSchemas.Config = migrateConfig(partialConfig); + + ConfigEvent.dispatch({ key: "fullConfigChange" }); + + const defaultConfig = getDefaultConfig(); + for (const key of typedKeys(fullConfig)) { + //@ts-expect-error this is fine, both are of type config + Config[key] = defaultConfig[key]; + } + + const configKeysToReset: (keyof ConfigSchemas.Config)[] = []; + + const firstKeys = typedKeys(fullConfig).filter( + (key) => !lastConfigsToApply.has(key), + ); + + for (const configKey of [...firstKeys, ...lastConfigsToApply]) { + const configValue = fullConfig[configKey]; + + const set = setConfig(configKey, configValue, { + nosave: true, + partOfFullConfigChange: true, + }); + + if (!set) { + configKeysToReset.push(configKey); + } + } + + for (const key of configKeysToReset) { + saveToLocalStorage(key); + } + + ConfigEvent.dispatch({ key: "fullConfigChangeFinished" }); + setConfigStore(reconcile(Config)); +} + +export async function resetConfig(): Promise { + await applyConfig(getDefaultConfig()); + await deleteConfig(); + saveFullConfigToLocalStorage(true); +} + +const { promise: configLoadPromise, resolve: loadDone } = + promiseWithResolvers(); + +export { configLoadPromise }; diff --git a/frontend/src/ts/config-metadata.ts b/frontend/src/ts/config/metadata.ts similarity index 98% rename from frontend/src/ts/config-metadata.ts rename to frontend/src/ts/config/metadata.ts index 120f4d34da23..d3947ae9bec6 100644 --- a/frontend/src/ts/config-metadata.ts +++ b/frontend/src/ts/config/metadata.ts @@ -1,14 +1,14 @@ import { checkCompatibility } from "@monkeytype/funbox"; -import * as DB from "./db"; -import { showNoticeNotification } from "./states/notifications"; -import { isAuthenticated } from "./firebase"; -import { canSetFunboxWithConfig } from "./test/funbox/funbox-validation"; -import { reloadAfter } from "./utils/misc"; -import { isDevEnvironment } from "./utils/env"; +import * as DB from "../db"; +import { showNoticeNotification } from "../states/notifications"; +import { isAuthenticated } from "../firebase"; +import { canSetFunboxWithConfig } from "../test/funbox/funbox-validation"; +import { reloadAfter } from "../utils/misc"; +import { isDevEnvironment } from "../utils/env"; import * as ConfigSchemas from "@monkeytype/schemas/configs"; import { roundTo1 } from "@monkeytype/util/numbers"; -import { capitalizeFirstLetter } from "./utils/strings"; -import { getDefaultConfig } from "./constants/default-config"; +import { capitalizeFirstLetter } from "../utils/strings"; +import { getDefaultConfig } from "../constants/default-config"; // type SetBlock = { // [K in keyof ConfigSchemas.Config]?: ConfigSchemas.Config[K][]; // }; diff --git a/frontend/src/ts/config/persistence.ts b/frontend/src/ts/config/persistence.ts new file mode 100644 index 000000000000..30914714f2e5 --- /dev/null +++ b/frontend/src/ts/config/persistence.ts @@ -0,0 +1,66 @@ +import { Config as ConfigSchema } from "@monkeytype/schemas/configs"; +import { saveConfig } from "../ape/config"; +import { setAccountButtonSpinner } from "../states/header"; +import { Config } from "./store"; +import * as ConfigSchemas from "@monkeytype/schemas/configs"; +import { getDefaultConfig } from "../constants/default-config"; +import { migrateConfig } from "./utils"; +import { LocalStorageWithSchema } from "../utils/local-storage-with-schema"; +import { isObject } from "../utils/misc"; +import { debounce } from "throttle-debounce"; + +let configToSend: Partial = {}; + +export const configLS = new LocalStorageWithSchema({ + key: "config", + schema: ConfigSchemas.ConfigSchema, + fallback: getDefaultConfig(), + migrate: (value, _issues) => { + if (!isObject(value)) { + return getDefaultConfig(); + } + //todo maybe send a full config to db so that it removes legacy values + return migrateConfig(value); + }, +}); + +export function saveToLocalStorage( + key: keyof ConfigSchema, + nosave = false, + noDbCheck = false, +): void { + if (nosave) return; + configLS.set(Config); + if (!noDbCheck) { + //@ts-expect-error this is fine + configToSend[key] = Config[key]; + saveToDatabase(); + } +} + +export function saveFullConfigToLocalStorage(noDbCheck = false): void { + console.log("saving full config to localStorage"); + configLS.set(Config); + if (!noDbCheck) { + setAccountButtonSpinner(true); + void saveConfig(Config).finally(() => { + setAccountButtonSpinner(false); + }); + } +} + +const saveToDatabase = debounce(1000, () => { + if (Object.keys(configToSend).length > 0) { + setAccountButtonSpinner(true); + void saveConfig(configToSend).finally(() => { + setAccountButtonSpinner(false); + }); + } + configToSend = {}; +}); + +export function resetPendingConfigSync( + newConfigToSend: Partial, +): void { + configToSend = newConfigToSend; +} diff --git a/frontend/src/ts/config/remote.ts b/frontend/src/ts/config/remote.ts new file mode 100644 index 000000000000..2fb74f71400b --- /dev/null +++ b/frontend/src/ts/config/remote.ts @@ -0,0 +1,50 @@ +import * as ConfigSchemas from "@monkeytype/schemas/configs"; + +import { migrateConfig } from "./utils"; +import { applyConfig } from "./lifecycle"; +import { saveFullConfigToLocalStorage } from "./persistence"; +import Ape from "../ape"; +import { SnapshotInitError } from "../db"; +import { getDefaultConfig } from "../constants/default-config"; +import { Config } from "./store"; + +export async function updateFromServer(): Promise { + const remoteConfig = await getRemoteConfig(); + + const areConfigsEqual = + JSON.stringify(Config) === JSON.stringify(remoteConfig); + + if (Config === undefined || !areConfigsEqual) { + console.log( + "no local config or local and db configs are different - applying db", + ); + await applyConfig(remoteConfig); + saveFullConfigToLocalStorage(true); + } +} + +async function getRemoteConfig(): Promise { + const response = await Ape.configs.get(); + + if (response.status !== 200) { + throw new SnapshotInitError( + `${response.body.message} (config)`, + response.status, + ); + } + + const configData = response.body.data; + if (configData !== null && "config" in configData) { + throw new Error( + "Config data is not in the correct format. Please refresh the page or contact support.", + ); + } + + if (configData === undefined || configData === null) { + return { + ...getDefaultConfig(), + }; + } else { + return migrateConfig(configData); + } +} diff --git a/frontend/src/ts/config/setters.ts b/frontend/src/ts/config/setters.ts new file mode 100644 index 000000000000..84fb44e08dd2 --- /dev/null +++ b/frontend/src/ts/config/setters.ts @@ -0,0 +1,184 @@ +import * as ConfigSchemas from "@monkeytype/schemas/configs"; +import { ZodType as ZodSchema } from "zod"; +import { saveToLocalStorage } from "../config/persistence"; +import { configMetadata, ConfigMetadataObject } from "./metadata"; +import { isConfigValueValid } from "./validation"; +import * as ConfigEvent from "../observables/config-event"; +import { showNoticeNotification } from "../states/notifications"; +import { + canSetConfigWithCurrentFunboxes, + canSetFunboxWithConfig, +} from "../test/funbox/funbox-validation"; +import * as TestState from "../test/test-state"; +import { typedKeys, triggerResize } from "../utils/misc"; +import { Config, setConfigStore } from "./store"; +import { FunboxName } from "@monkeytype/schemas/configs"; + +export function setConfig( + key: T, + value: ConfigSchemas.Config[T], + options?: { + nosave?: boolean; + partOfFullConfigChange?: boolean; + }, +): boolean { + const metadata = configMetadata[key] as ConfigMetadataObject[T]; + if (metadata === undefined) { + throw new Error(`Config metadata for key "${key}" is not defined.`); + } + + if (metadata.overrideValue) { + value = metadata.overrideValue({ + value, + currentValue: Config[key], + currentConfig: Config, + }); + } + + const previousValue = Config[key]; + + if ( + metadata.changeRequiresRestart && + TestState.isActive && + Config.funbox.includes("no_quit") + ) { + showNoticeNotification( + "No quit funbox is active. Please finish the test.", + { + important: true, + }, + ); + console.warn( + `Could not set config key "${key}" with value "${JSON.stringify( + value, + )}" - no quit funbox active.`, + ); + return false; + } + + if (metadata.isBlocked?.({ value, currentConfig: Config })) { + console.warn( + `Could not set config key "${key}" with value "${JSON.stringify( + value, + )}" - blocked.`, + ); + return false; + } + + const schema = ConfigSchemas.ConfigSchema.shape[key] as ZodSchema; + + if (!isConfigValueValid(metadata.displayString ?? key, value, schema)) { + console.warn( + `Could not set config key "${key}" with value "${JSON.stringify( + value, + )}" - invalid value.`, + ); + return false; + } + + if (!canSetConfigWithCurrentFunboxes(key, value, Config.funbox)) { + console.warn( + `Could not set config key "${key}" with value "${JSON.stringify( + value, + )}" - funbox conflict.`, + ); + return false; + } + + if (metadata.overrideConfig) { + const targetConfig = metadata.overrideConfig({ + value, + currentConfig: Config, + }); + + for (const targetKey of typedKeys(targetConfig)) { + const targetValue = targetConfig[ + targetKey + ] as ConfigSchemas.Config[keyof typeof configMetadata]; + + if (Config[targetKey] === targetValue) { + continue; // no need to set if the value is already the same + } + + const set = setConfig(targetKey, targetValue, options); + if (!set) { + throw new Error( + `Failed to set config key "${targetKey}" with value "${targetValue}" for ${metadata.displayString} config override.`, + ); + } + } + } + + Config[key] = value; + if (!options?.nosave) saveToLocalStorage(key, options?.nosave); + + // @ts-expect-error i can't figure this out + ConfigEvent.dispatch({ + key: key, + newValue: value, + nosave: options?.nosave ?? false, + previousValue: previousValue as ConfigSchemas.Config[T], + }); + + if (!options?.partOfFullConfigChange) { + setConfigStore(key, value); + } + + if (metadata.triggerResize && !options?.nosave) { + triggerResize(); + } + + metadata.afterSet?.({ + nosave: options?.nosave ?? false, + currentConfig: Config, + }); + return true; +} + +export function setQuoteLengthAll(nosave?: boolean): boolean { + return setConfig("quoteLength", [0, 1, 2, 3], { + nosave, + }); +} + +export function toggleFunbox(funbox: FunboxName, nosave?: boolean): boolean { + if (TestState.isActive && Config.funbox.includes("no_quit")) { + showNoticeNotification( + "No quit funbox is active. Please finish the test.", + { + important: true, + }, + ); + return false; + } + + if (!canSetFunboxWithConfig(funbox, Config)) { + return false; + } + + const previousValue = Config.funbox; + + let newConfig: FunboxName[] = Config.funbox; + + if (newConfig.includes(funbox)) { + newConfig = newConfig.filter((it) => it !== funbox); + } else { + newConfig.push(funbox); + newConfig.sort(); + } + + if (!isConfigValueValid("funbox", newConfig, ConfigSchemas.FunboxSchema)) { + return false; + } + + Config.funbox = newConfig; + saveToLocalStorage("funbox", nosave); + ConfigEvent.dispatch({ + key: "funbox", + newValue: Config.funbox, + nosave, + previousValue, + }); + + return true; +} diff --git a/frontend/src/ts/config/store.ts b/frontend/src/ts/config/store.ts new file mode 100644 index 000000000000..1e73844649c3 --- /dev/null +++ b/frontend/src/ts/config/store.ts @@ -0,0 +1,10 @@ +import type { Config as ConfigSchema } from "@monkeytype/schemas/configs"; +import { getDefaultConfig } from "../constants/default-config"; +import { createStore } from "solid-js/store"; + +export const Config: ConfigSchema = { + ...getDefaultConfig(), +}; + +export const [getConfig, setConfigStore] = + createStore(getDefaultConfig()); diff --git a/frontend/src/ts/config/testing.ts b/frontend/src/ts/config/testing.ts new file mode 100644 index 000000000000..48ef33e8ce8b --- /dev/null +++ b/frontend/src/ts/config/testing.ts @@ -0,0 +1,18 @@ +import type { Config as ConfigSchema } from "@monkeytype/schemas/configs"; +import { configMetadata } from "./metadata"; +import { getDefaultConfig } from "../constants/default-config"; +import { resetPendingConfigSync } from "./persistence"; +import { Config } from "./store"; + +export const __testing = { + configMetadata, + replaceConfig: (setConfig: Partial): void => { + const newConfig = { ...getDefaultConfig(), ...setConfig }; + for (const key of Object.keys(Config)) { + Reflect.deleteProperty(Config, key); + } + Object.assign(Config, newConfig); + resetPendingConfigSync({}); + }, + getConfig: () => Config, +}; diff --git a/frontend/src/ts/utils/config.ts b/frontend/src/ts/config/utils.ts similarity index 90% rename from frontend/src/ts/utils/config.ts rename to frontend/src/ts/config/utils.ts index c8ccd6feac54..9621b2e717a3 100644 --- a/frontend/src/ts/utils/config.ts +++ b/frontend/src/ts/config/utils.ts @@ -1,25 +1,26 @@ -import { - Config, +import type { + Config as ConfigSchema, ConfigValue, PartialConfig, FunboxName, } from "@monkeytype/schemas/configs"; -import { typedKeys } from "./misc"; -import { sanitize } from "./sanitize"; +import { typedKeys } from "../utils/misc"; +import { sanitize } from "../utils/sanitize"; import * as ConfigSchemas from "@monkeytype/schemas/configs"; import { getDefaultConfig } from "../constants/default-config"; +import { Config } from "./store"; /** * migrates possible outdated config and merges with the default config values * @param config partial or possible outdated config * @returns */ -export function migrateConfig(config: PartialConfig | object): Config { +export function migrateConfig(config: PartialConfig | object): ConfigSchema { return mergeWithDefaultConfig(sanitizeConfig(replaceLegacyValues(config))); } -function mergeWithDefaultConfig(config: PartialConfig): Config { +function mergeWithDefaultConfig(config: PartialConfig): ConfigSchema { const defaultConfig = getDefaultConfig(); - const mergedConfig = {} as Config; + const mergedConfig = {} as ConfigSchema; for (const key of typedKeys(defaultConfig)) { const newValue = config[key] ?? (defaultConfig[key] as ConfigValue); //@ts-expect-error cant be bothered to deal with this @@ -220,3 +221,16 @@ function replaceLegacyValues( return configObj; } + +export function getConfigChanges(): Partial { + const configChanges: Partial = {}; + typedKeys(Config) + .filter((key) => { + return Config[key] !== getDefaultConfig()[key]; + }) + .forEach((key) => { + //@ts-expect-error this is fine + configChanges[key] = Config[key]; + }); + return configChanges; +} diff --git a/frontend/src/ts/config-validation.ts b/frontend/src/ts/config/validation.ts similarity index 90% rename from frontend/src/ts/config-validation.ts rename to frontend/src/ts/config/validation.ts index 0249262523a3..f39d6cd28a1f 100644 --- a/frontend/src/ts/config-validation.ts +++ b/frontend/src/ts/config/validation.ts @@ -1,6 +1,6 @@ -import { showErrorNotification } from "./states/notifications"; +import { showErrorNotification } from "../states/notifications"; import { ZodSchema, z } from "zod"; -import * as Sentry from "./sentry"; +import * as Sentry from "../sentry"; // function isConfigKeyValid(name: string): boolean { // if (name === null || name === undefined || name === "") return false; @@ -34,6 +34,7 @@ export function isConfigValueValid( return isValid; } + export function isConfigValueValidBoolean(key: string, val: boolean): boolean { return isConfigValueValid(key, val, z.boolean()); } diff --git a/frontend/src/ts/controllers/ad-controller.ts b/frontend/src/ts/controllers/ad-controller.ts index 0c56463804eb..85bd1efe6255 100644 --- a/frontend/src/ts/controllers/ad-controller.ts +++ b/frontend/src/ts/controllers/ad-controller.ts @@ -1,7 +1,7 @@ /* oxlint-disable no-unsafe-member-access */ import { debounce } from "throttle-debounce"; import * as ConfigEvent from "../observables/config-event"; -import Config from "../config"; +import { Config } from "../config/store"; import * as TestState from "../test/test-state"; import * as EG from "./eg-ad-controller"; import * as PW from "./pw-ad-controller"; diff --git a/frontend/src/ts/controllers/challenge-controller.ts b/frontend/src/ts/controllers/challenge-controller.ts index 403bf63518ac..acb7864f0c29 100644 --- a/frontend/src/ts/controllers/challenge-controller.ts +++ b/frontend/src/ts/controllers/challenge-controller.ts @@ -7,7 +7,9 @@ import { } from "../states/notifications"; import * as CustomText from "../test/custom-text"; import * as Funbox from "../test/funbox/funbox"; -import Config, { setConfig } from "../config"; + +import { Config } from "../config/store"; +import { setConfig } from "../config/setters"; import * as ConfigEvent from "../observables/config-event"; import * as TestState from "../test/test-state"; diff --git a/frontend/src/ts/controllers/chart-controller.ts b/frontend/src/ts/controllers/chart-controller.ts index febeef2c76bb..afc75ff6c85a 100644 --- a/frontend/src/ts/controllers/chart-controller.ts +++ b/frontend/src/ts/controllers/chart-controller.ts @@ -59,7 +59,7 @@ Chart.defaults.elements.line.fill = "origin"; import "chartjs-adapter-date-fns"; import { format } from "date-fns/format"; -import Config from "../config"; +import { Config } from "../config/store"; import * as ConfigEvent from "../observables/config-event"; import * as TestInput from "../test/test-input"; import * as DateTime from "../utils/date-and-time"; diff --git a/frontend/src/ts/controllers/preset-controller.ts b/frontend/src/ts/controllers/preset-controller.ts index e752891ab369..fce7a6c02732 100644 --- a/frontend/src/ts/controllers/preset-controller.ts +++ b/frontend/src/ts/controllers/preset-controller.ts @@ -1,5 +1,7 @@ import { Preset } from "@monkeytype/schemas/presets"; -import Config, { applyConfig, saveFullConfigToLocalStorage } from "../config"; + +import { Config } from "../config/store"; +import { applyConfig } from "../config/lifecycle"; import * as DB from "../db"; import { showNoticeNotification, @@ -8,6 +10,7 @@ import { import * as TestLogic from "../test/test-logic"; import * as TagController from "./tag-controller"; import { SnapshotPreset } from "../constants/default-snapshot"; +import { saveFullConfigToLocalStorage } from "../config/persistence"; export async function apply(_id: string): Promise { const snapshot = DB.getSnapshot(); diff --git a/frontend/src/ts/controllers/pw-ad-controller.ts b/frontend/src/ts/controllers/pw-ad-controller.ts index d94c142d4a52..5b17c9841466 100644 --- a/frontend/src/ts/controllers/pw-ad-controller.ts +++ b/frontend/src/ts/controllers/pw-ad-controller.ts @@ -3,7 +3,7 @@ // oxlint-disable ban-ts-comment //@ts-nocheck too many errors from 3rd party ad code -import Config from "../config"; +import { Config } from "../config/store"; import { getActivePage } from "../states/core"; import * as TestState from "../test/test-state"; diff --git a/frontend/src/ts/controllers/sound-controller.ts b/frontend/src/ts/controllers/sound-controller.ts index 89725400d6b7..b3103789a720 100644 --- a/frontend/src/ts/controllers/sound-controller.ts +++ b/frontend/src/ts/controllers/sound-controller.ts @@ -1,4 +1,4 @@ -import Config from "../config"; +import { Config } from "../config/store"; import * as ConfigEvent from "../observables/config-event"; import { randomElementFromArray } from "../utils/arrays"; import { randomIntFromRange } from "@monkeytype/util/numbers"; diff --git a/frontend/src/ts/controllers/theme-controller.ts b/frontend/src/ts/controllers/theme-controller.ts index 5fdfa1e3a5ae..bca26542ad9d 100644 --- a/frontend/src/ts/controllers/theme-controller.ts +++ b/frontend/src/ts/controllers/theme-controller.ts @@ -1,6 +1,8 @@ import * as Arrays from "../utils/arrays"; import { isColorDark, isColorLight } from "../utils/colors"; -import Config, { setConfig } from "../config"; + +import { Config } from "../config/store"; +import { setConfig } from "../config/setters"; import * as BackgroundFilter from "../elements/custom-background-filter"; import * as ConfigEvent from "../observables/config-event"; import * as DB from "../db"; diff --git a/frontend/src/ts/controllers/url-handler.tsx b/frontend/src/ts/controllers/url-handler.tsx index d5e4bef53438..acd5a586828b 100644 --- a/frontend/src/ts/controllers/url-handler.tsx +++ b/frontend/src/ts/controllers/url-handler.tsx @@ -21,7 +21,8 @@ import { decompressFromURI } from "lz-ts"; import { z } from "zod"; import Ape from "../ape"; -import Config, { setConfig } from "../config"; +import { setConfig } from "../config/setters"; +import { Config } from "../config/store"; import * as DB from "../db"; import * as AuthEvent from "../observables/auth-event"; import { showLoaderBar, hideLoaderBar } from "../states/loader-bar"; diff --git a/frontend/src/ts/elements/account/result-filters.ts b/frontend/src/ts/elements/account/result-filters.ts index 25906eb85106..35410cc996b7 100644 --- a/frontend/src/ts/elements/account/result-filters.ts +++ b/frontend/src/ts/elements/account/result-filters.ts @@ -1,7 +1,7 @@ import * as Misc from "../../utils/misc"; import * as Strings from "../../utils/strings"; import * as DB from "../../db"; -import Config from "../../config"; +import { Config } from "../../config/store"; import { showNoticeNotification, showErrorNotification, diff --git a/frontend/src/ts/elements/caret.ts b/frontend/src/ts/elements/caret.ts index 836a1d047421..ff11ed3a983f 100644 --- a/frontend/src/ts/elements/caret.ts +++ b/frontend/src/ts/elements/caret.ts @@ -1,5 +1,5 @@ import { CaretStyle } from "@monkeytype/schemas/configs"; -import Config from "../config"; +import { Config } from "../config/store"; import * as TestWords from "../test/test-words"; import { getTotalInlineMargin } from "../utils/misc"; import { isWordRightToLeft } from "../utils/strings"; diff --git a/frontend/src/ts/elements/custom-background-filter.ts b/frontend/src/ts/elements/custom-background-filter.ts index 5fc35e809b79..21f5e9973d89 100644 --- a/frontend/src/ts/elements/custom-background-filter.ts +++ b/frontend/src/ts/elements/custom-background-filter.ts @@ -1,5 +1,5 @@ import { CustomBackgroundFilter } from "@monkeytype/schemas/configs"; -import { setConfig } from "../config"; +import { setConfig } from "../config/setters"; import * as ConfigEvent from "../observables/config-event"; import { debounce } from "throttle-debounce"; import { qs, qsr } from "../utils/dom"; diff --git a/frontend/src/ts/elements/input-validation.ts b/frontend/src/ts/elements/input-validation.ts index d6c7008ce47c..a870c322810d 100644 --- a/frontend/src/ts/elements/input-validation.ts +++ b/frontend/src/ts/elements/input-validation.ts @@ -6,7 +6,8 @@ import { ConfigSchema, Config as ConfigType, } from "@monkeytype/schemas/configs"; -import Config, { setConfig } from "../config"; +import { Config } from "../config/store"; +import { setConfig } from "../config/setters"; import { showSuccessNotification } from "../states/notifications"; import { ElementWithUtils } from "../utils/dom"; import { Validation, ValidationResult } from "../types/validation"; diff --git a/frontend/src/ts/elements/keymap.ts b/frontend/src/ts/elements/keymap.ts index 267e5f5982cf..114acdb8f8e3 100644 --- a/frontend/src/ts/elements/keymap.ts +++ b/frontend/src/ts/elements/keymap.ts @@ -1,4 +1,4 @@ -import Config from "../config"; +import { Config } from "../config/store"; import * as ConfigEvent from "../observables/config-event"; import * as KeymapEvent from "../observables/keymap-event"; import * as Misc from "../utils/misc"; diff --git a/frontend/src/ts/elements/last-10-average.ts b/frontend/src/ts/elements/last-10-average.ts index 6e2e846638c8..df296a69afdd 100644 --- a/frontend/src/ts/elements/last-10-average.ts +++ b/frontend/src/ts/elements/last-10-average.ts @@ -1,7 +1,7 @@ import * as DB from "../db"; import * as Misc from "../utils/misc"; import * as Numbers from "@monkeytype/util/numbers"; -import Config from "../config"; +import { Config } from "../config/store"; import * as TestWords from "../test/test-words"; let averageWPM = 0; diff --git a/frontend/src/ts/elements/modes-notice.ts b/frontend/src/ts/elements/modes-notice.ts index d4fda472c3d3..a1ef87f20660 100644 --- a/frontend/src/ts/elements/modes-notice.ts +++ b/frontend/src/ts/elements/modes-notice.ts @@ -2,7 +2,7 @@ import * as PaceCaret from "../test/pace-caret"; import * as TestState from "../test/test-state"; import * as DB from "../db"; import * as Last10Average from "../elements/last-10-average"; -import Config from "../config"; +import { Config } from "../config/store"; import * as TestWords from "../test/test-words"; import * as ConfigEvent from "../observables/config-event"; import { isAuthenticated } from "../firebase"; diff --git a/frontend/src/ts/elements/monkey-power.ts b/frontend/src/ts/elements/monkey-power.ts index 6730e5653b83..71d2bd9a9064 100644 --- a/frontend/src/ts/elements/monkey-power.ts +++ b/frontend/src/ts/elements/monkey-power.ts @@ -1,5 +1,5 @@ import * as SlowTimer from "../legacy-states/slow-timer"; -import Config from "../config"; +import { Config } from "../config/store"; import { isSafeNumber } from "@monkeytype/util/numbers"; import { requestDebouncedAnimationFrame } from "../utils/debounced-animation-frame"; import { ElementWithUtils, qsr } from "../utils/dom"; diff --git a/frontend/src/ts/elements/settings/settings-group.ts b/frontend/src/ts/elements/settings/settings-group.ts index 2a2e6bb6289b..aaea67ca5ec8 100644 --- a/frontend/src/ts/elements/settings/settings-group.ts +++ b/frontend/src/ts/elements/settings/settings-group.ts @@ -1,6 +1,6 @@ import { Config as ConfigType, ConfigKey } from "@monkeytype/schemas/configs"; - -import Config, { setConfig } from "../../config"; +import { Config } from "../../config/store"; +import { setConfig } from "../../config/setters"; import { showErrorNotification } from "../../states/notifications"; import SlimSelect from "slim-select"; import { debounce } from "throttle-debounce"; diff --git a/frontend/src/ts/elements/settings/theme-picker.ts b/frontend/src/ts/elements/settings/theme-picker.ts index 95cabf236e62..5e88f52b0972 100644 --- a/frontend/src/ts/elements/settings/theme-picker.ts +++ b/frontend/src/ts/elements/settings/theme-picker.ts @@ -1,4 +1,5 @@ -import Config, { setConfig, saveFullConfigToLocalStorage } from "../../config"; +import { Config } from "../../config/store"; +import { setConfig } from "../../config/setters"; import * as ThemeController from "../../controllers/theme-controller"; import * as Misc from "../../utils/misc"; import * as Colors from "../../utils/colors"; @@ -17,6 +18,7 @@ import { captureException } from "../../sentry"; import { ColorName, ThemesList, ThemeWithName } from "../../constants/themes"; import { qs, qsa, qsr } from "../../utils/dom"; import { getTheme, updateThemeColor } from "../../states/theme"; +import { saveFullConfigToLocalStorage } from "../../config/persistence"; export const sortedThemes: ThemeWithName[] = [...ThemesList].sort((a, b) => { const b1 = Colors.hexToHSL(a.bg); diff --git a/frontend/src/ts/event-handlers/global.ts b/frontend/src/ts/event-handlers/global.ts index f7b3806d7a91..34262c190d5c 100644 --- a/frontend/src/ts/event-handlers/global.ts +++ b/frontend/src/ts/event-handlers/global.ts @@ -1,6 +1,6 @@ import * as Misc from "../utils/misc"; import * as PageTransition from "../legacy-states/page-transition"; -import Config from "../config"; +import { Config } from "../config/store"; import * as TestWords from "../test/test-words"; import * as Commandline from "../commandline/commandline"; import { showErrorNotification } from "../states/notifications"; diff --git a/frontend/src/ts/event-handlers/test.ts b/frontend/src/ts/event-handlers/test.ts index 4ebd783417f6..1fc30c519a2b 100644 --- a/frontend/src/ts/event-handlers/test.ts +++ b/frontend/src/ts/event-handlers/test.ts @@ -1,6 +1,6 @@ import * as Commandline from "../commandline/commandline"; import * as CustomWordAmount from "../modals/custom-word-amount"; -import Config from "../config"; +import { Config } from "../config/store"; import * as DB from "../db"; import * as EditResultTagsModal from "../modals/edit-result-tags"; import * as MobileTestConfigModal from "../modals/mobile-test-config"; diff --git a/frontend/src/ts/index.ts b/frontend/src/ts/index.ts index 6a8a46646450..3dfc3859b2d0 100644 --- a/frontend/src/ts/index.ts +++ b/frontend/src/ts/index.ts @@ -14,7 +14,7 @@ import * as DB from "./db"; import "./ui"; import "./elements/settings/account-settings-notice"; import "./controllers/ad-controller"; -import Config, { loadFromLocalStorage } from "./config"; +import { Config } from "./config/store"; import * as TestStats from "./test/test-stats"; import * as Replay from "./test/replay"; import * as TestTimer from "./test/test-timer"; @@ -44,6 +44,7 @@ import { qs, qsa, qsr } from "./utils/dom"; import { mountComponents } from "./components/mount"; import "./ready"; import { setVersion } from "./states/core"; +import { loadFromLocalStorage } from "./config/lifecycle"; // Lock Math.random Object.defineProperty(Math, "random", { diff --git a/frontend/src/ts/input/handlers/before-delete.ts b/frontend/src/ts/input/handlers/before-delete.ts index 79fb28d77d50..12784dfdacb0 100644 --- a/frontend/src/ts/input/handlers/before-delete.ts +++ b/frontend/src/ts/input/handlers/before-delete.ts @@ -1,4 +1,4 @@ -import Config from "../../config"; +import { Config } from "../../config/store"; import * as TestInput from "../../test/test-input"; import * as TestState from "../../test/test-state"; import * as TestWords from "../../test/test-words"; diff --git a/frontend/src/ts/input/handlers/before-insert-text.ts b/frontend/src/ts/input/handlers/before-insert-text.ts index 158771e3d7b6..d06b90e35da1 100644 --- a/frontend/src/ts/input/handlers/before-insert-text.ts +++ b/frontend/src/ts/input/handlers/before-insert-text.ts @@ -1,4 +1,4 @@ -import Config from "../../config"; +import { Config } from "../../config/store"; import * as TestInput from "../../test/test-input"; import * as TestState from "../../test/test-state"; import * as TestUI from "../../test/test-ui"; diff --git a/frontend/src/ts/input/handlers/delete.ts b/frontend/src/ts/input/handlers/delete.ts index b6551c643636..0ee4213d8e3f 100644 --- a/frontend/src/ts/input/handlers/delete.ts +++ b/frontend/src/ts/input/handlers/delete.ts @@ -4,7 +4,7 @@ import * as TestInput from "../../test/test-input"; import { getInputElementValue, setInputElementValue } from "../input-element"; import * as Replay from "../../test/replay"; -import Config from "../../config"; +import { Config } from "../../config/store"; import { goToPreviousWord } from "../helpers/word-navigation"; import { DeleteInputType } from "../helpers/input-type"; diff --git a/frontend/src/ts/input/handlers/insert-text.ts b/frontend/src/ts/input/handlers/insert-text.ts index 9a8e5212278a..190783c66227 100644 --- a/frontend/src/ts/input/handlers/insert-text.ts +++ b/frontend/src/ts/input/handlers/insert-text.ts @@ -20,7 +20,7 @@ import { isFunboxActiveWithProperty, } from "../../test/funbox/list"; import * as Replay from "../../test/replay"; -import Config from "../../config"; +import { Config } from "../../config/store"; import * as KeymapEvent from "../../observables/keymap-event"; import * as WeakSpot from "../../test/weak-spot"; import * as CompositionState from "../../legacy-states/composition"; diff --git a/frontend/src/ts/input/handlers/keydown.ts b/frontend/src/ts/input/handlers/keydown.ts index cea4ffb1cefc..d27373dff7ff 100644 --- a/frontend/src/ts/input/handlers/keydown.ts +++ b/frontend/src/ts/input/handlers/keydown.ts @@ -1,4 +1,4 @@ -import Config from "../../config"; +import { Config } from "../../config/store"; import * as TestInput from "../../test/test-input"; import * as TestLogic from "../../test/test-logic"; import { getCharFromEvent } from "../../test/layout-emulator"; diff --git a/frontend/src/ts/input/handlers/keyup.ts b/frontend/src/ts/input/handlers/keyup.ts index 995331c8265f..97c3321db8ee 100644 --- a/frontend/src/ts/input/handlers/keyup.ts +++ b/frontend/src/ts/input/handlers/keyup.ts @@ -1,4 +1,4 @@ -import Config from "../../config"; +import { Config } from "../../config/store"; import * as TestInput from "../../test/test-input"; import * as Monkey from "../../test/monkey"; diff --git a/frontend/src/ts/input/helpers/fail-or-finish.ts b/frontend/src/ts/input/helpers/fail-or-finish.ts index 4b89e0768d5c..6c096a42c10b 100644 --- a/frontend/src/ts/input/helpers/fail-or-finish.ts +++ b/frontend/src/ts/input/helpers/fail-or-finish.ts @@ -1,4 +1,4 @@ -import Config from "../../config"; +import { Config } from "../../config/store"; import { whorf } from "../../utils/misc"; /** diff --git a/frontend/src/ts/input/helpers/validation.ts b/frontend/src/ts/input/helpers/validation.ts index 408b5416eba7..862bc80132bc 100644 --- a/frontend/src/ts/input/helpers/validation.ts +++ b/frontend/src/ts/input/helpers/validation.ts @@ -1,4 +1,4 @@ -import Config from "../../config"; +import { Config } from "../../config/store"; import { isSpace } from "../../utils/strings"; /** diff --git a/frontend/src/ts/input/helpers/word-navigation.ts b/frontend/src/ts/input/helpers/word-navigation.ts index ce57130ec818..fdb0e298bca2 100644 --- a/frontend/src/ts/input/helpers/word-navigation.ts +++ b/frontend/src/ts/input/helpers/word-navigation.ts @@ -1,4 +1,4 @@ -import Config from "../../config"; +import { Config } from "../../config/store"; import * as TestInput from "../../test/test-input"; import * as TestUI from "../../test/test-ui"; import * as PaceCaret from "../../test/pace-caret"; diff --git a/frontend/src/ts/modals/custom-test-duration.ts b/frontend/src/ts/modals/custom-test-duration.ts index a7285a4233ce..16fe8551f931 100644 --- a/frontend/src/ts/modals/custom-test-duration.ts +++ b/frontend/src/ts/modals/custom-test-duration.ts @@ -1,4 +1,5 @@ -import Config, { setConfig } from "../config"; +import { Config } from "../config/store"; +import { setConfig } from "../config/setters"; import * as TestLogic from "../test/test-logic"; import { showNoticeNotification } from "../states/notifications"; import AnimatedModal, { ShowOptions } from "../utils/animated-modal"; diff --git a/frontend/src/ts/modals/custom-text.ts b/frontend/src/ts/modals/custom-text.ts index 88a0cc9ba040..d9b1c8679c3e 100644 --- a/frontend/src/ts/modals/custom-text.ts +++ b/frontend/src/ts/modals/custom-text.ts @@ -2,7 +2,9 @@ import * as CustomText from "../test/custom-text"; import * as CustomTextState from "../legacy-states/custom-text-name"; import * as TestLogic from "../test/test-logic"; import * as ChallengeController from "../controllers/challenge-controller"; -import Config, { setConfig } from "../config"; + +import { Config } from "../config/store"; +import { setConfig } from "../config/setters"; import * as Strings from "../utils/strings"; import * as WordFilterPopup from "./word-filter"; import * as CustomGeneratorPopup from "./custom-generator"; diff --git a/frontend/src/ts/modals/custom-word-amount.ts b/frontend/src/ts/modals/custom-word-amount.ts index 4ab797f6b45f..6e4fd7aee5ca 100644 --- a/frontend/src/ts/modals/custom-word-amount.ts +++ b/frontend/src/ts/modals/custom-word-amount.ts @@ -1,4 +1,5 @@ -import Config, { setConfig } from "../config"; +import { Config } from "../config/store"; +import { setConfig } from "../config/setters"; import * as TestLogic from "../test/test-logic"; import { showNoticeNotification } from "../states/notifications"; import AnimatedModal, { ShowOptions } from "../utils/animated-modal"; diff --git a/frontend/src/ts/modals/edit-preset.ts b/frontend/src/ts/modals/edit-preset.ts index 0fc66bc70373..de8d82d4b574 100644 --- a/frontend/src/ts/modals/edit-preset.ts +++ b/frontend/src/ts/modals/edit-preset.ts @@ -1,7 +1,5 @@ import Ape from "../ape"; import * as DB from "../db"; -import * as Config from "../config"; - import { showLoaderBar, hideLoaderBar } from "../states/loader-bar"; import * as Settings from "../pages/settings"; import { @@ -26,7 +24,8 @@ import { getDefaultConfig } from "../constants/default-config"; import { SnapshotPreset } from "../constants/default-snapshot"; import { ValidatedHtmlInputElement } from "../elements/input-validation"; import { ElementWithUtils } from "../utils/dom"; -import { configMetadata } from "../config-metadata"; +import { configMetadata } from "../config/metadata"; +import { getConfigChanges as getConfigChangesFromConfig } from "../config/utils"; const state = { presetType: "full" as PresetType, @@ -388,8 +387,8 @@ function getActiveSettingGroupsFromState(): ConfigGroupName[] { function getConfigChanges(): Partial { const activeConfigChanges = state.presetType === "partial" - ? getPartialConfigChanges(Config.getConfigChanges()) - : Config.getConfigChanges(); + ? getPartialConfigChanges(getConfigChangesFromConfig()) + : getConfigChangesFromConfig(); const tags = DB.getSnapshot()?.tags ?? []; const activeTagIds: string[] = tags diff --git a/frontend/src/ts/modals/import-export-settings.ts b/frontend/src/ts/modals/import-export-settings.ts index 1a28159b646b..f2be415871d1 100644 --- a/frontend/src/ts/modals/import-export-settings.ts +++ b/frontend/src/ts/modals/import-export-settings.ts @@ -1,4 +1,4 @@ -import { applyConfigFromJson } from "../config"; +import { applyConfigFromJson } from "../config/lifecycle"; import AnimatedModal from "../utils/animated-modal"; type State = { diff --git a/frontend/src/ts/modals/mini-result-chart.ts b/frontend/src/ts/modals/mini-result-chart.ts index 14a090d215d9..e30925d79b62 100644 --- a/frontend/src/ts/modals/mini-result-chart.ts +++ b/frontend/src/ts/modals/mini-result-chart.ts @@ -1,8 +1,7 @@ import { ChartData } from "@monkeytype/schemas/results"; import AnimatedModal from "../utils/animated-modal"; import * as ChartController from "../controllers/chart-controller"; -import Config from "../config"; - +import { Config } from "../config/store"; function updateData(data: ChartData): void { // let data = filteredResults[filteredId].chartData; let labels = []; diff --git a/frontend/src/ts/modals/mobile-test-config.ts b/frontend/src/ts/modals/mobile-test-config.ts index ba6bceeb639f..7dd85ccc5d79 100644 --- a/frontend/src/ts/modals/mobile-test-config.ts +++ b/frontend/src/ts/modals/mobile-test-config.ts @@ -1,5 +1,6 @@ import * as TestLogic from "../test/test-logic"; -import Config, { setConfig, setQuoteLengthAll } from "../config"; +import { Config } from "../config/store"; +import { setConfig, setQuoteLengthAll } from "../config/setters"; import * as CustomWordAmountPopup from "./custom-word-amount"; import * as CustomTestDurationPopup from "./custom-test-duration"; import * as QuoteSearchModal from "./quote-search"; diff --git a/frontend/src/ts/modals/pb-tables.ts b/frontend/src/ts/modals/pb-tables.ts index a55c96204c9c..fabd0ace6ab7 100644 --- a/frontend/src/ts/modals/pb-tables.ts +++ b/frontend/src/ts/modals/pb-tables.ts @@ -1,7 +1,7 @@ import * as DB from "../db"; import { format } from "date-fns/format"; import { getLanguageDisplayString } from "../utils/strings"; -import Config from "../config"; +import { Config } from "../config/store"; import Format from "../singletons/format"; import AnimatedModal from "../utils/animated-modal"; import { Mode, Mode2, PersonalBest } from "@monkeytype/schemas/shared"; diff --git a/frontend/src/ts/modals/quote-report.ts b/frontend/src/ts/modals/quote-report.ts index 0ec75aae44ef..d977becbe5a6 100644 --- a/frontend/src/ts/modals/quote-report.ts +++ b/frontend/src/ts/modals/quote-report.ts @@ -1,7 +1,6 @@ import { ElementWithUtils, qsr } from "../utils/dom"; import Ape from "../ape"; -import Config from "../config"; - +import { Config } from "../config/store"; import { showLoaderBar, hideLoaderBar } from "../states/loader-bar"; import { showNoticeNotification, diff --git a/frontend/src/ts/modals/quote-search.ts b/frontend/src/ts/modals/quote-search.ts index 234f72578356..1b7d6d910a76 100644 --- a/frontend/src/ts/modals/quote-search.ts +++ b/frontend/src/ts/modals/quote-search.ts @@ -1,4 +1,5 @@ -import Config, { setConfig } from "../config"; +import { Config } from "../config/store"; +import { setConfig } from "../config/setters"; import * as DB from "../db"; import { showNoticeNotification, diff --git a/frontend/src/ts/modals/quote-submit.ts b/frontend/src/ts/modals/quote-submit.ts index b158ac63c7f6..e75854ee6182 100644 --- a/frontend/src/ts/modals/quote-submit.ts +++ b/frontend/src/ts/modals/quote-submit.ts @@ -9,7 +9,7 @@ import { } from "../states/notifications"; import * as CaptchaController from "../controllers/captcha-controller"; import * as Strings from "../utils/strings"; -import Config from "../config"; +import { Config } from "../config/store"; import SlimSelect from "slim-select"; import AnimatedModal, { ShowOptions } from "../utils/animated-modal"; import { CharacterCounter } from "../elements/character-counter"; diff --git a/frontend/src/ts/modals/share-custom-theme.ts b/frontend/src/ts/modals/share-custom-theme.ts index 8aa1a738fb39..b95c1251e12b 100644 --- a/frontend/src/ts/modals/share-custom-theme.ts +++ b/frontend/src/ts/modals/share-custom-theme.ts @@ -1,5 +1,5 @@ import * as ThemeController from "../controllers/theme-controller"; -import Config from "../config"; +import { Config } from "../config/store"; import { showNoticeNotification, showSuccessNotification, diff --git a/frontend/src/ts/modals/share-test-settings.ts b/frontend/src/ts/modals/share-test-settings.ts index 101d54245a29..86d3324a9ca4 100644 --- a/frontend/src/ts/modals/share-test-settings.ts +++ b/frontend/src/ts/modals/share-test-settings.ts @@ -1,4 +1,4 @@ -import Config from "../config"; +import { Config } from "../config/store"; import { currentQuote } from "../test/test-words"; import { getMode2 } from "../utils/misc"; import * as CustomText from "../test/custom-text"; diff --git a/frontend/src/ts/modals/simple-modals.ts b/frontend/src/ts/modals/simple-modals.ts index 16875cdf10e4..873b785ea424 100644 --- a/frontend/src/ts/modals/simple-modals.ts +++ b/frontend/src/ts/modals/simple-modals.ts @@ -1,7 +1,8 @@ import Ape from "../ape"; import * as AccountController from "../auth"; import * as DB from "../db"; -import { resetConfig, setConfig } from "../config"; +import { resetConfig } from "../config/lifecycle"; +import { setConfig } from "../config/setters"; import { showNoticeNotification } from "../states/notifications"; import * as Settings from "../pages/settings"; import * as ThemePicker from "../elements/settings/theme-picker"; diff --git a/frontend/src/ts/pages/account.ts b/frontend/src/ts/pages/account.ts index 6d15a69be241..080ab29e4336 100644 --- a/frontend/src/ts/pages/account.ts +++ b/frontend/src/ts/pages/account.ts @@ -1,7 +1,9 @@ import * as DB from "../db"; import * as ResultFilters from "../elements/account/result-filters"; import * as ChartController from "../controllers/chart-controller"; -import Config, { setConfig } from "../config"; + +import { Config } from "../config/store"; +import { setConfig } from "../config/setters"; import * as MiniResultChartModal from "../modals/mini-result-chart"; import * as Focus from "../test/focus"; import * as TodayTracker from "../test/today-tracker"; diff --git a/frontend/src/ts/pages/settings.ts b/frontend/src/ts/pages/settings.ts index 43ee21f7ef0c..a40605db8e8e 100644 --- a/frontend/src/ts/pages/settings.ts +++ b/frontend/src/ts/pages/settings.ts @@ -1,5 +1,8 @@ import SettingsGroup from "../elements/settings/settings-group"; -import Config, { setConfig, configLoadPromise } from "../config"; + +import { Config } from "../config/store"; +import { configLoadPromise } from "../config/lifecycle"; +import { setConfig } from "../config/setters"; import * as Sound from "../controllers/sound-controller"; import * as Misc from "../utils/misc"; import * as Strings from "../utils/strings"; @@ -1065,7 +1068,7 @@ export const page = new PageWithUrlParams({ }, beforeShow: async (options): Promise => { Skeleton.append("pageSettings", "main"); - await configLoadPromise; + await configLoadPromise; //todo: is this actually needed here if we await it in ready? await fillSettingsPage(); await update(); // theme UI updates manually to avoid duplication diff --git a/frontend/src/ts/ready.ts b/frontend/src/ts/ready.ts index 2d0ef5a46493..9acc9a79e53e 100644 --- a/frontend/src/ts/ready.ts +++ b/frontend/src/ts/ready.ts @@ -4,7 +4,7 @@ import * as MerchBanner from "./elements/merch-banner"; //@ts-expect-error no types for this package import Konami from "konami"; import * as ServerConfiguration from "./ape/server-configuration"; -import { configLoadPromise } from "./config"; +import { configLoadPromise } from "./config/lifecycle"; import { authPromise } from "./firebase"; import { animate } from "animejs"; import { onDOMReady, qs } from "./utils/dom"; diff --git a/frontend/src/ts/singletons/format.ts b/frontend/src/ts/singletons/format.ts index ae447cb31db8..f1943de48c65 100644 --- a/frontend/src/ts/singletons/format.ts +++ b/frontend/src/ts/singletons/format.ts @@ -1,4 +1,3 @@ import { Formatting } from "../utils/format"; -import Config from "../config"; - +import { Config } from "../config/store"; export default new Formatting(Config); diff --git a/frontend/src/ts/states/config.ts b/frontend/src/ts/states/config.ts deleted file mode 100644 index b86f6d24f006..000000000000 --- a/frontend/src/ts/states/config.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { subscribe } from "../observables/config-event"; -import { Config as ConfigType } from "@monkeytype/schemas/configs"; -import { createStore } from "solid-js/store"; -import { getDefaultConfig } from "../constants/default-config"; -import * as Config from "../config"; - -const [getConfig, setConfigStore] = createStore(getDefaultConfig()); -export { getConfig }; - -let fullConfigChange = false; -subscribe(({ key, newValue }) => { - if (key === "fullConfigChange") { - fullConfigChange = true; - } else if (key === "fullConfigChangeFinished") { - fullConfigChange = false; - setConfigStore(Config.getConfig()); - } else if (fullConfigChange) { - return; - } else { - setConfigStore(key, newValue); - } -}); diff --git a/frontend/src/ts/test/break-ligatures.ts b/frontend/src/ts/test/break-ligatures.ts index 8ce3ea569677..7d6c5b1d19a4 100644 --- a/frontend/src/ts/test/break-ligatures.ts +++ b/frontend/src/ts/test/break-ligatures.ts @@ -1,4 +1,4 @@ -import Config from "../config"; +import { Config } from "../config/store"; import { ElementWithUtils } from "../utils/dom"; function canBreak(wordEl: ElementWithUtils): boolean { diff --git a/frontend/src/ts/test/british-english.ts b/frontend/src/ts/test/british-english.ts index 3875d82e3bb8..35d0bf7e1dca 100644 --- a/frontend/src/ts/test/british-english.ts +++ b/frontend/src/ts/test/british-english.ts @@ -1,4 +1,4 @@ -import Config from "../config"; +import { Config } from "../config/store"; import britishEnglishReplacements from "../constants/british-english"; import { capitalizeFirstLetterOfEachWord } from "../utils/strings"; diff --git a/frontend/src/ts/test/caps-warning.ts b/frontend/src/ts/test/caps-warning.ts index df67b4501b40..962fed5acc4f 100644 --- a/frontend/src/ts/test/caps-warning.ts +++ b/frontend/src/ts/test/caps-warning.ts @@ -1,4 +1,4 @@ -import Config from "../config"; +import { Config } from "../config/store"; import { qsr } from "../utils/dom"; import { onCapsLockChange } from "@leonabcd123/modern-caps-lock"; diff --git a/frontend/src/ts/test/caret.ts b/frontend/src/ts/test/caret.ts index 0e48fb60531a..04a2fd7dc74a 100644 --- a/frontend/src/ts/test/caret.ts +++ b/frontend/src/ts/test/caret.ts @@ -1,4 +1,4 @@ -import Config from "../config"; +import { Config } from "../config/store"; import * as TestInput from "./test-input"; import * as TestState from "../test/test-state"; import { subscribe } from "../observables/config-event"; diff --git a/frontend/src/ts/test/funbox/funbox-functions.ts b/frontend/src/ts/test/funbox/funbox-functions.ts index cd1a1d17d19b..db4927a82797 100644 --- a/frontend/src/ts/test/funbox/funbox-functions.ts +++ b/frontend/src/ts/test/funbox/funbox-functions.ts @@ -1,6 +1,7 @@ import { FunboxWordsFrequency, Wordset } from "../wordset"; import * as GetText from "../../utils/generate"; -import Config, { setConfig, toggleFunbox } from "../../config"; +import { Config } from "../../config/store"; +import { setConfig, toggleFunbox } from "../../config/setters"; import * as Misc from "../../utils/misc"; import * as Strings from "../../utils/strings"; import { randomIntFromRange } from "@monkeytype/util/numbers"; diff --git a/frontend/src/ts/test/funbox/funbox-memory.ts b/frontend/src/ts/test/funbox/funbox-memory.ts index 5fc953eeb945..605fa49aab9f 100644 --- a/frontend/src/ts/test/funbox/funbox-memory.ts +++ b/frontend/src/ts/test/funbox/funbox-memory.ts @@ -1,6 +1,6 @@ import { Config, ConfigKey, ConfigValue } from "@monkeytype/schemas/configs"; -import { setConfig } from "../../config"; +import { setConfig } from "../../config/setters"; type SetFunction = (param: T, nosave?: boolean) => boolean; diff --git a/frontend/src/ts/test/funbox/funbox.ts b/frontend/src/ts/test/funbox/funbox.ts index e3443d0ce35d..716723ea3449 100644 --- a/frontend/src/ts/test/funbox/funbox.ts +++ b/frontend/src/ts/test/funbox/funbox.ts @@ -4,10 +4,11 @@ import { } from "../../states/notifications"; import * as JSONData from "../../utils/json-data"; import * as Strings from "../../utils/strings"; -import Config, { - setConfig, +import { Config } from "../../config/store"; +import { toggleFunbox as configToggleFunbox, -} from "../../config"; + setConfig, +} from "../../config/setters"; import * as MemoryTimer from "./memory-funbox-timer"; import * as FunboxMemory from "./funbox-memory"; import { HighlightMode, FunboxName } from "@monkeytype/schemas/configs"; diff --git a/frontend/src/ts/test/funbox/list.ts b/frontend/src/ts/test/funbox/list.ts index 88220d8badb8..6447d89eb3fc 100644 --- a/frontend/src/ts/test/funbox/list.ts +++ b/frontend/src/ts/test/funbox/list.ts @@ -1,4 +1,4 @@ -import Config from "../../config"; +import { Config } from "../../config/store"; import { FunboxMetadata, getFunboxObject, diff --git a/frontend/src/ts/test/layout-emulator.ts b/frontend/src/ts/test/layout-emulator.ts index 29449b0b8abc..4b8593780ae0 100644 --- a/frontend/src/ts/test/layout-emulator.ts +++ b/frontend/src/ts/test/layout-emulator.ts @@ -1,4 +1,4 @@ -import Config from "../config"; +import { Config } from "../config/store"; import * as JSONData from "../utils/json-data"; import { capsState } from "./caps-warning"; import { showErrorNotification } from "../states/notifications"; diff --git a/frontend/src/ts/test/live-acc.ts b/frontend/src/ts/test/live-acc.ts index e780c94f5a76..cda6654997a2 100644 --- a/frontend/src/ts/test/live-acc.ts +++ b/frontend/src/ts/test/live-acc.ts @@ -1,4 +1,4 @@ -import Config from "../config"; +import { Config } from "../config/store"; import * as TestState from "../test/test-state"; import * as ConfigEvent from "../observables/config-event"; import { applyReducedMotion } from "../utils/misc"; diff --git a/frontend/src/ts/test/live-burst.ts b/frontend/src/ts/test/live-burst.ts index 38170e179ece..3f1b6c308d64 100644 --- a/frontend/src/ts/test/live-burst.ts +++ b/frontend/src/ts/test/live-burst.ts @@ -1,4 +1,4 @@ -import Config from "../config"; +import { Config } from "../config/store"; import * as TestState from "../test/test-state"; import * as ConfigEvent from "../observables/config-event"; import Format from "../singletons/format"; diff --git a/frontend/src/ts/test/live-speed.ts b/frontend/src/ts/test/live-speed.ts index fdc4d0cf027a..a35090a2beac 100644 --- a/frontend/src/ts/test/live-speed.ts +++ b/frontend/src/ts/test/live-speed.ts @@ -1,4 +1,4 @@ -import Config from "../config"; +import { Config } from "../config/store"; import * as TestState from "./test-state"; import * as ConfigEvent from "../observables/config-event"; import Format from "../singletons/format"; diff --git a/frontend/src/ts/test/monkey.ts b/frontend/src/ts/test/monkey.ts index 713b8002f1ce..2299f2df752b 100644 --- a/frontend/src/ts/test/monkey.ts +++ b/frontend/src/ts/test/monkey.ts @@ -1,5 +1,5 @@ import { mapRange } from "@monkeytype/util/numbers"; -import Config from "../config"; +import { Config } from "../config/store"; import * as ConfigEvent from "../observables/config-event"; import * as TestState from "../test/test-state"; import * as KeyConverter from "../utils/key-converter"; diff --git a/frontend/src/ts/test/out-of-focus.ts b/frontend/src/ts/test/out-of-focus.ts index 05d4ee05db5b..3a7e295bf16f 100644 --- a/frontend/src/ts/test/out-of-focus.ts +++ b/frontend/src/ts/test/out-of-focus.ts @@ -1,5 +1,5 @@ import * as Misc from "../utils/misc"; -import Config from "../config"; +import { Config } from "../config/store"; import { qs, qsa } from "../utils/dom"; const outOfFocusTimeouts: (number | NodeJS.Timeout)[] = []; diff --git a/frontend/src/ts/test/pace-caret.ts b/frontend/src/ts/test/pace-caret.ts index 0319f9a8e270..5dd45a69ba84 100644 --- a/frontend/src/ts/test/pace-caret.ts +++ b/frontend/src/ts/test/pace-caret.ts @@ -1,5 +1,5 @@ import * as TestWords from "./test-words"; -import Config from "../config"; +import { Config } from "../config/store"; import * as DB from "../db"; import * as Misc from "../utils/misc"; import * as TestState from "./test-state"; diff --git a/frontend/src/ts/test/practise-words.ts b/frontend/src/ts/test/practise-words.ts index e4ddbb55b652..f84b178bfed7 100644 --- a/frontend/src/ts/test/practise-words.ts +++ b/frontend/src/ts/test/practise-words.ts @@ -1,6 +1,8 @@ import * as TestWords from "./test-words"; import { showNoticeNotification } from "../states/notifications"; -import Config, { setConfig } from "../config"; + +import { Config } from "../config/store"; +import { setConfig } from "../config/setters"; import * as CustomText from "./custom-text"; import * as TestInput from "./test-input"; import * as ConfigEvent from "../observables/config-event"; diff --git a/frontend/src/ts/test/replay.ts b/frontend/src/ts/test/replay.ts index b6dee822a5dc..05a19e649155 100644 --- a/frontend/src/ts/test/replay.ts +++ b/frontend/src/ts/test/replay.ts @@ -1,9 +1,8 @@ -import config from "../config"; import * as Sound from "../controllers/sound-controller"; import * as TestInput from "./test-input"; import * as Arrays from "../utils/arrays"; import { qs, qsr } from "../utils/dom"; - +import { Config } from "../config/store"; type ReplayAction = | "correctLetter" | "incorrectLetter" @@ -91,7 +90,7 @@ export function pauseReplay(): void { function playSound(error = false): void { if (error) { - if (config.playSoundOnError !== "off") { + if (Config.playSoundOnError !== "off") { void Sound.playError(); } else { void Sound.playClick(); diff --git a/frontend/src/ts/test/result.ts b/frontend/src/ts/test/result.ts index 565e263c6d95..98d63f85f2eb 100644 --- a/frontend/src/ts/test/result.ts +++ b/frontend/src/ts/test/result.ts @@ -1,6 +1,8 @@ //TODO: use Format import { Chart, type PluginChartOptions } from "chart.js"; -import Config, { setConfig } from "../config"; + +import { Config } from "../config/store"; +import { setConfig } from "../config/setters"; import * as AdController from "../controllers/ad-controller"; import * as ChartController from "../controllers/chart-controller"; import QuotesController, { Quote } from "../controllers/quotes-controller"; diff --git a/frontend/src/ts/test/shift-tracker.ts b/frontend/src/ts/test/shift-tracker.ts index 9e9120e21c37..cb803bf0532a 100644 --- a/frontend/src/ts/test/shift-tracker.ts +++ b/frontend/src/ts/test/shift-tracker.ts @@ -1,4 +1,4 @@ -import Config from "../config"; +import { Config } from "../config/store"; import { Keycode } from "../constants/keys"; import * as KeyConverter from "../utils/key-converter"; diff --git a/frontend/src/ts/test/test-config.ts b/frontend/src/ts/test/test-config.ts index 391b96eca2e9..3a1ea2b6e679 100644 --- a/frontend/src/ts/test/test-config.ts +++ b/frontend/src/ts/test/test-config.ts @@ -1,6 +1,6 @@ import { ConfigValue, QuoteLength } from "@monkeytype/schemas/configs"; import { Mode } from "@monkeytype/schemas/shared"; -import Config from "../config"; +import { Config } from "../config/store"; import * as ConfigEvent from "../observables/config-event"; import { getActivePage } from "../states/core"; import { applyReducedMotion } from "../utils/misc"; diff --git a/frontend/src/ts/test/test-input.ts b/frontend/src/ts/test/test-input.ts index 4d7a9373139b..8b18f17efd61 100644 --- a/frontend/src/ts/test/test-input.ts +++ b/frontend/src/ts/test/test-input.ts @@ -1,7 +1,7 @@ import { lastElementFromArray } from "../utils/arrays"; import { mean, roundTo2 } from "@monkeytype/util/numbers"; import * as TestState from "./test-state"; -import Config from "../config"; +import { Config } from "../config/store"; import { getInputElementValue } from "../input/input-element"; const keysToTrack = new Set([ diff --git a/frontend/src/ts/test/test-logic.ts b/frontend/src/ts/test/test-logic.ts index 294ac32ea28d..fc56ef4aa938 100644 --- a/frontend/src/ts/test/test-logic.ts +++ b/frontend/src/ts/test/test-logic.ts @@ -1,6 +1,5 @@ import Ape from "../ape"; import * as TestUI from "./test-ui"; -import Config, { setConfig, setQuoteLengthAll, toggleFunbox } from "../config"; import * as Strings from "../utils/strings"; import * as Misc from "../utils/misc"; import * as Arrays from "../utils/arrays"; @@ -72,7 +71,8 @@ import { debounce } from "throttle-debounce"; import * as Time from "../legacy-states/time"; import { qs } from "../utils/dom"; import { setAccountButtonSpinner } from "../states/header"; - +import { Config } from "../config/store"; +import { setQuoteLengthAll, toggleFunbox, setConfig } from "../config/setters"; let failReason = ""; export async function syncNotSignedInLastResult(uid: string): Promise { diff --git a/frontend/src/ts/test/test-stats.ts b/frontend/src/ts/test/test-stats.ts index 558a197acfde..16078cf172c1 100644 --- a/frontend/src/ts/test/test-stats.ts +++ b/frontend/src/ts/test/test-stats.ts @@ -1,5 +1,5 @@ import Hangul from "hangul-js"; -import Config from "../config"; +import { Config } from "../config/store"; import * as Strings from "../utils/strings"; import * as TestInput from "./test-input"; import * as TestWords from "./test-words"; diff --git a/frontend/src/ts/test/test-timer.ts b/frontend/src/ts/test/test-timer.ts index 612f48ab8d9b..1ce1314d1039 100644 --- a/frontend/src/ts/test/test-timer.ts +++ b/frontend/src/ts/test/test-timer.ts @@ -1,7 +1,8 @@ //most of the code is thanks to //https://stackoverflow.com/questions/29971898/how-to-create-an-accurate-timer-in-javascript -import Config, { setConfig } from "../config"; +import { Config } from "../config/store"; +import { setConfig } from "../config/setters"; import * as CustomText from "./custom-text"; import * as TimerProgress from "./timer-progress"; import * as LiveSpeed from "./live-speed"; diff --git a/frontend/src/ts/test/test-ui.ts b/frontend/src/ts/test/test-ui.ts index ae7034e234a3..ef58d4823ba1 100644 --- a/frontend/src/ts/test/test-ui.ts +++ b/frontend/src/ts/test/test-ui.ts @@ -2,7 +2,9 @@ import { showNoticeNotification, showErrorNotification, } from "../states/notifications"; -import Config, { setConfig } from "../config"; + +import { Config } from "../config/store"; +import { setConfig } from "../config/setters"; import * as TestWords from "./test-words"; import * as TestInput from "./test-input"; import * as CustomText from "./custom-text"; diff --git a/frontend/src/ts/test/timer-progress.ts b/frontend/src/ts/test/timer-progress.ts index 1a53a710d267..90f4ce76840e 100644 --- a/frontend/src/ts/test/timer-progress.ts +++ b/frontend/src/ts/test/timer-progress.ts @@ -1,4 +1,4 @@ -import Config from "../config"; +import { Config } from "../config/store"; import * as CustomText from "./custom-text"; import * as DateTime from "../utils/date-and-time"; import * as TestWords from "./test-words"; diff --git a/frontend/src/ts/test/tts.ts b/frontend/src/ts/test/tts.ts index ab62b05954ea..a32d61947ab6 100644 --- a/frontend/src/ts/test/tts.ts +++ b/frontend/src/ts/test/tts.ts @@ -1,4 +1,4 @@ -import Config from "../config"; +import { Config } from "../config/store"; import * as JSONData from "../utils/json-data"; import * as ConfigEvent from "../observables/config-event"; import * as TTSEvent from "../observables/tts-event"; diff --git a/frontend/src/ts/test/words-generator.ts b/frontend/src/ts/test/words-generator.ts index 88d175db9e20..4302df510bfe 100644 --- a/frontend/src/ts/test/words-generator.ts +++ b/frontend/src/ts/test/words-generator.ts @@ -1,4 +1,5 @@ -import Config, { setConfig, setQuoteLengthAll, toggleFunbox } from "../config"; +import { Config } from "../config/store"; +import { setConfig, setQuoteLengthAll, toggleFunbox } from "../config/setters"; import * as CustomText from "./custom-text"; import { Wordset, FunboxWordsFrequency, withWords } from "./wordset"; import QuotesController, { diff --git a/frontend/src/ts/ui.ts b/frontend/src/ts/ui.ts index 689b910a0369..73991df8112b 100644 --- a/frontend/src/ts/ui.ts +++ b/frontend/src/ts/ui.ts @@ -1,4 +1,4 @@ -import Config from "./config"; +import { Config } from "./config/store"; import * as Caret from "./test/caret"; import * as CustomText from "./test/custom-text"; import * as TestState from "./test/test-state"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 60434a5bd13a..2a8342af6b06 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -286,8 +286,8 @@ importers: specifier: workspace:* version: link:../packages/util '@sentry/browser': - specifier: 9.14.0 - version: 9.14.0 + specifier: 10.44.0 + version: 10.44.0 '@sentry/vite-plugin': specifier: 3.3.1 version: 3.3.1(encoding@0.1.13) @@ -423,7 +423,7 @@ importers: version: 0.8.10(solid-js@1.9.10) '@tailwindcss/vite': specifier: 4.2.1 - version: 4.2.1(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.2.1(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)) '@tanstack/eslint-plugin-query': specifier: 5.91.4 version: 5.91.4(eslint@9.39.1(jiti@2.6.1))(typescript@6.0.0-beta) @@ -462,7 +462,7 @@ importers: version: 5.0.2 '@vitest/coverage-v8': specifier: 4.0.15 - version: 4.0.15(vitest@4.1.0(@types/node@24.9.1)(happy-dom@20.0.10)(jsdom@27.4.0)(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))) + version: 4.0.15(vitest@4.1.0(@types/node@24.9.1)(happy-dom@20.0.10)(jsdom@27.4.0)(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))) autoprefixer: specifier: 10.4.27 version: 10.4.27(postcss@8.5.8) @@ -512,11 +512,11 @@ importers: specifier: 8.5.8 version: 8.5.8 sass: - specifier: 1.98.0 - version: 1.98.0 + specifier: 1.70.0 + version: 1.70.0 solid-devtools: specifier: 0.34.5 - version: 0.34.5(solid-js@1.9.10)(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 0.34.5(solid-js@1.9.10)(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)) solid-js: specifier: 1.9.10 version: 1.9.10 @@ -537,7 +537,7 @@ importers: version: 3.0.0 vite: specifier: 7.3.1 - version: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2) + version: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2) vite-bundle-visualizer: specifier: 1.2.1 version: 1.2.1(rollup@2.80.0) @@ -546,19 +546,19 @@ importers: version: 1.1.2 vite-plugin-inspect: specifier: 11.3.3 - version: 11.3.3(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 11.3.3(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)) vite-plugin-minify: specifier: 2.1.0 - version: 2.1.0(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 2.1.0(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)) vite-plugin-pwa: specifier: 1.1.0 - version: 1.1.0(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(workbox-build@7.1.1(@types/babel__core@7.20.5))(workbox-window@7.1.0) + version: 1.1.0(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(workbox-build@7.1.1(@types/babel__core@7.20.5))(workbox-window@7.1.0) vite-plugin-solid: specifier: 2.11.11 - version: 2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@1.9.10)(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@1.9.10)(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)) vitest: specifier: 4.1.0 - version: 4.1.0(@types/node@24.9.1)(@vitest/browser-playwright@4.0.18)(happy-dom@20.0.10)(jsdom@27.4.0)(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.1.0(@types/node@24.9.1)(happy-dom@20.0.10)(jsdom@27.4.0)(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)) frontend/storybook: devDependencies: @@ -649,7 +649,7 @@ importers: version: 6.0.0-beta vitest: specifier: 4.1.0 - version: 4.1.0(@types/node@24.9.1)(happy-dom@20.0.10)(jsdom@27.4.0)(vite@8.0.0(@types/node@24.9.1)(esbuild@0.25.11)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.1.0(@opentelemetry/api@1.8.0)(@types/node@24.9.1)(happy-dom@20.0.10)(jsdom@27.4.0)(vite@8.0.0(@types/node@24.9.1)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)) packages/funbox: dependencies: @@ -3314,28 +3314,28 @@ packages: cpu: [x64] os: [win32] - '@sentry-internal/browser-utils@9.14.0': - resolution: {integrity: sha512-pDk9XUu9zf7lcT9QX0nTObPNp/y0xQyy1Dj+5/8TSB3vAfe0LQcooKGl/D1h7EoIXVHUozZk5JC/dH+gz6BXRg==} + '@sentry-internal/browser-utils@10.44.0': + resolution: {integrity: sha512-z9xz3T/v+MnfHY6kdUCmOZI8CiAl3LlKYtGH2p3rAsrxhwX+BTnUp01VhMVnEZIDgUXNt3AhJac+4kcDIPu1Hg==} engines: {node: '>=18'} - '@sentry-internal/feedback@9.14.0': - resolution: {integrity: sha512-D+PiEUWbDT0vqmaTiOs6OzXwVRVFgf7BCkFs48qsN9sAPwUgT+5zh2oo/rU2r0NrmMcvJVtSY+ezwPMk8BgGsg==} + '@sentry-internal/feedback@10.44.0': + resolution: {integrity: sha512-yNS2EGK1bNm8YUI+Orzpa7yr05Da+b1VEe/9x7dl7gTjw/+tfutoXlG6Y+iFZBB3gQ9QU+nxZAhU+KcxiPEURw==} engines: {node: '>=18'} - '@sentry-internal/replay-canvas@9.14.0': - resolution: {integrity: sha512-GhCSqc0oNzRiLhQsi9LCXgUmIwdHdvzVIsX4fihoFYWfgWSSj5YLqeEkb3CMM8htM6vheSFzIbPLlRS8fjCrPQ==} + '@sentry-internal/replay-canvas@10.44.0': + resolution: {integrity: sha512-RA7XgYZWHY7M+vaHvuMxDFT51wCs4puS2smElM5oh+j3YqbFXY7P16fOCwIAGoyI4gVsj8aTeBgVqUmrmzhAXQ==} engines: {node: '>=18'} - '@sentry-internal/replay@9.14.0': - resolution: {integrity: sha512-wgt397/PtpfVQ9t779a0L+hGH3JN9doXv3+9Wj98MLWwhymvJBjpjCFUBLScO5iP6imewTbRqQHbq7XS7I+x1A==} + '@sentry-internal/replay@10.44.0': + resolution: {integrity: sha512-KDmoqBsRmkaoc+eKLR2CbScd2eBmLcw+1+D441lLttAO3WWhvYyCaYdu/HIGGUoybuSgt+IcpCJdi7hFuCvYqw==} engines: {node: '>=18'} '@sentry/babel-plugin-component-annotate@3.3.1': resolution: {integrity: sha512-5GOxGT7lZN+I8A7Vp0rWY+726FDKEw8HnFiebe51rQrMbfGfCu2Aw9uSM0nT9OG6xhV6WvGccIcCszTPs4fUZQ==} engines: {node: '>= 14'} - '@sentry/browser@9.14.0': - resolution: {integrity: sha512-acxFbFEei3hzKr/IW3OmkzHlwohRaRBG0872nIhLYV2f/BgZmR6eV5zrUoELMmt2cgoLmDYyfp1734OoplfDbw==} + '@sentry/browser@10.44.0': + resolution: {integrity: sha512-UpMx5forbVKieNULma3gT2SsLYqsYT4nLXa6s1io/Y8BFej9sH2dD5ExA8TrkQThQwAWFI3qKsQzYnF+EX/Bfg==} engines: {node: '>=18'} '@sentry/bundler-plugin-core@3.3.1': @@ -3388,8 +3388,8 @@ packages: engines: {node: '>= 10'} hasBin: true - '@sentry/core@9.14.0': - resolution: {integrity: sha512-OLfucnP3LAL5bxVNWc2RVOHCX7fk9Er5bWPCS+O5cPjqNUUz0HQHhVh2Vhei5C0kYZZM4vy4BQit5T9LrlOaNA==} + '@sentry/core@10.44.0': + resolution: {integrity: sha512-aa7CiDaNFZvHpqd97LJhuskolfJ/4IH5xyuVVLnv7l6B0v9KTwskPUxb0tH1ej3FxuzfH+i8iTiTFuqpfHS3QA==} engines: {node: '>=18'} '@sentry/vite-plugin@3.3.1': @@ -6606,6 +6606,9 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + immutable@4.3.8: + resolution: {integrity: sha512-d/Ld9aLbKpNwyl0KiM2CT1WYvkitQ1TSvmRtkcV8FKStiDoA7Slzgjmb/1G2yhKM1p0XeNOieaTbFZmU1d3Xuw==} + immutable@5.1.5: resolution: {integrity: sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==} @@ -9066,6 +9069,11 @@ packages: engines: {node: '>=18'} hasBin: true + sass@1.70.0: + resolution: {integrity: sha512-uUxNQ3zAHeAx5nRFskBnrWzDUJrrvpCPD5FNAoRvTi0WwremlheES3tg+56PaVtCs5QDRX5CBLxxKMDJMEa1WQ==} + engines: {node: '>=14.0.0'} + hasBin: true + sass@1.98.0: resolution: {integrity: sha512-+4N/u9dZ4PrgzGgPlKnaaRQx64RO0JBKs9sDhQ2pLgN6JQZ25uPQZKQYaBJU48Kd5BxgXoJ4e09Dq7nMcOUW3A==} engines: {node: '>=14.0.0'} @@ -13430,33 +13438,33 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.52.5': optional: true - '@sentry-internal/browser-utils@9.14.0': + '@sentry-internal/browser-utils@10.44.0': dependencies: - '@sentry/core': 9.14.0 + '@sentry/core': 10.44.0 - '@sentry-internal/feedback@9.14.0': + '@sentry-internal/feedback@10.44.0': dependencies: - '@sentry/core': 9.14.0 + '@sentry/core': 10.44.0 - '@sentry-internal/replay-canvas@9.14.0': + '@sentry-internal/replay-canvas@10.44.0': dependencies: - '@sentry-internal/replay': 9.14.0 - '@sentry/core': 9.14.0 + '@sentry-internal/replay': 10.44.0 + '@sentry/core': 10.44.0 - '@sentry-internal/replay@9.14.0': + '@sentry-internal/replay@10.44.0': dependencies: - '@sentry-internal/browser-utils': 9.14.0 - '@sentry/core': 9.14.0 + '@sentry-internal/browser-utils': 10.44.0 + '@sentry/core': 10.44.0 '@sentry/babel-plugin-component-annotate@3.3.1': {} - '@sentry/browser@9.14.0': + '@sentry/browser@10.44.0': dependencies: - '@sentry-internal/browser-utils': 9.14.0 - '@sentry-internal/feedback': 9.14.0 - '@sentry-internal/replay': 9.14.0 - '@sentry-internal/replay-canvas': 9.14.0 - '@sentry/core': 9.14.0 + '@sentry-internal/browser-utils': 10.44.0 + '@sentry-internal/feedback': 10.44.0 + '@sentry-internal/replay': 10.44.0 + '@sentry-internal/replay-canvas': 10.44.0 + '@sentry/core': 10.44.0 '@sentry/bundler-plugin-core@3.3.1(encoding@0.1.13)': dependencies: @@ -13512,7 +13520,7 @@ snapshots: - encoding - supports-color - '@sentry/core@9.14.0': {} + '@sentry/core@10.44.0': {} '@sentry/vite-plugin@3.3.1(encoding@0.1.13)': dependencies: @@ -13899,6 +13907,13 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.2.1 '@tailwindcss/oxide-win32-x64-msvc': 4.2.1 + '@tailwindcss/vite@4.2.1(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))': + dependencies: + '@tailwindcss/node': 4.2.1 + '@tailwindcss/oxide': 4.2.1 + tailwindcss: 4.2.1 + vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2) + '@tailwindcss/vite@4.2.1(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@tailwindcss/node': 4.2.1 @@ -14462,7 +14477,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitest/coverage-v8@4.0.15(vitest@4.1.0(@types/node@24.9.1)(happy-dom@20.0.10)(jsdom@27.4.0)(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)))': + '@vitest/coverage-v8@4.0.15(vitest@4.1.0(@types/node@24.9.1)(happy-dom@20.0.10)(jsdom@27.4.0)(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)))': dependencies: '@bcoe/v8-coverage': 1.0.2 '@vitest/utils': 4.0.15 @@ -14475,7 +14490,7 @@ snapshots: obug: 2.1.1 std-env: 3.10.0 tinyrainbow: 3.0.3 - vitest: 4.1.0(@types/node@24.9.1)(@vitest/browser-playwright@4.0.18)(happy-dom@20.0.10)(jsdom@27.4.0)(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)) + vitest: 4.1.0(@types/node@24.9.1)(happy-dom@20.0.10)(jsdom@27.4.0)(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)) transitivePeerDependencies: - supports-color @@ -14520,29 +14535,29 @@ snapshots: optionalDependencies: vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2) - '@vitest/mocker@4.1.0(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))': + '@vitest/mocker@4.1.0(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@vitest/spy': 4.1.0 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2) - '@vitest/mocker@4.1.0(vite@8.0.0(@types/node@20.5.1)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))': + '@vitest/mocker@4.1.0(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@vitest/spy': 4.1.0 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.0(@types/node@20.5.1)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2) - '@vitest/mocker@4.1.0(vite@8.0.0(@types/node@24.9.1)(esbuild@0.25.11)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))': + '@vitest/mocker@4.1.0(vite@8.0.0(@types/node@20.5.1)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@vitest/spy': 4.1.0 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.0(@types/node@24.9.1)(esbuild@0.25.11)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2) + vite: 8.0.0(@types/node@20.5.1)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2) '@vitest/mocker@4.1.0(vite@8.0.0(@types/node@24.9.1)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))': dependencies: @@ -17436,7 +17451,10 @@ snapshots: ignore@5.3.2: {} - immutable@5.1.5: {} + immutable@4.3.8: {} + + immutable@5.1.5: + optional: true import-fresh@3.3.1: dependencies: @@ -20207,6 +20225,12 @@ snapshots: dependencies: commander: 12.1.0 + sass@1.70.0: + dependencies: + chokidar: 3.6.0 + immutable: 4.3.8 + source-map-js: 1.2.1 + sass@1.98.0: dependencies: chokidar: 4.0.3 @@ -20214,6 +20238,7 @@ snapshots: source-map-js: 1.2.1 optionalDependencies: '@parcel/watcher': 2.5.6 + optional: true saxes@6.0.0: dependencies: @@ -20517,7 +20542,7 @@ snapshots: ip-address: 9.0.5 smart-buffer: 4.2.0 - solid-devtools@0.34.5(solid-js@1.9.10)(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)): + solid-devtools@0.34.5(solid-js@1.9.10)(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)): dependencies: '@babel/core': 7.28.6 '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.28.6) @@ -20526,7 +20551,7 @@ snapshots: '@solid-devtools/shared': 0.20.0(solid-js@1.9.10) solid-js: 1.9.10 optionalDependencies: - vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2) transitivePeerDependencies: - supports-color @@ -21559,19 +21584,19 @@ snapshots: - rollup - supports-color - vite-dev-rpc@1.1.0(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)): + vite-dev-rpc@1.1.0(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)): dependencies: birpc: 2.6.1 - vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2) - vite-hot-client: 2.1.0(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)) + vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2) + vite-hot-client: 2.1.0(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)) - vite-hot-client@2.1.0(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)): + vite-hot-client@2.1.0(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)): dependencies: - vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2) vite-plugin-html-inject@1.1.2: {} - vite-plugin-inspect@11.3.3(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)): + vite-plugin-inspect@11.3.3(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)): dependencies: ansis: 4.2.0 debug: 4.4.3(supports-color@5.5.0) @@ -21581,23 +21606,23 @@ snapshots: perfect-debounce: 2.0.0 sirv: 3.0.2 unplugin-utils: 0.3.1 - vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2) - vite-dev-rpc: 1.1.0(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)) + vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2) + vite-dev-rpc: 1.1.0(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)) transitivePeerDependencies: - supports-color - vite-plugin-minify@2.1.0(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)): + vite-plugin-minify@2.1.0(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)): dependencies: '@types/html-minifier-terser': 7.0.2 html-minifier-terser: 7.2.0 - vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2) - vite-plugin-pwa@1.1.0(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(workbox-build@7.1.1(@types/babel__core@7.20.5))(workbox-window@7.1.0): + vite-plugin-pwa@1.1.0(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2))(workbox-build@7.1.1(@types/babel__core@7.20.5))(workbox-window@7.1.0): dependencies: debug: 4.4.3(supports-color@5.5.0) pretty-bytes: 6.1.1 tinyglobby: 0.2.15 - vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2) workbox-build: 7.1.1(@types/babel__core@7.20.5) workbox-window: 7.1.0 transitivePeerDependencies: @@ -21618,7 +21643,7 @@ snapshots: transitivePeerDependencies: - supports-color - vite-plugin-solid@2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@1.9.10)(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)): + vite-plugin-solid@2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@1.9.10)(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)): dependencies: '@babel/core': 7.28.6 '@types/babel__core': 7.20.5 @@ -21626,14 +21651,14 @@ snapshots: merge-anything: 5.1.7 solid-js: 1.9.10 solid-refresh: 0.6.3(solid-js@1.9.10) - vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2) - vitefu: 1.1.1(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)) + vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2) + vitefu: 1.1.1(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)) optionalDependencies: '@testing-library/jest-dom': 6.9.1 transitivePeerDependencies: - supports-color - vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2): + vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2): dependencies: esbuild: 0.27.3 fdir: 6.5.0(picomatch@4.0.3) @@ -21646,30 +21671,30 @@ snapshots: fsevents: 2.3.3 jiti: 2.6.1 lightningcss: 1.32.0 - sass: 1.98.0 + sass: 1.70.0 terser: 5.46.1 tsx: 4.21.0 yaml: 2.8.2 - vite@8.0.0(@types/node@20.5.1)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2): + vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2): dependencies: - '@oxc-project/runtime': 0.115.0 - lightningcss: 1.32.0 + esbuild: 0.27.3 + fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.8 - rolldown: 1.0.0-rc.9 + rollup: 4.52.5 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 20.5.1 - esbuild: 0.27.3 + '@types/node': 24.9.1 fsevents: 2.3.3 jiti: 2.6.1 + lightningcss: 1.32.0 sass: 1.98.0 terser: 5.46.1 tsx: 4.21.0 yaml: 2.8.2 - vite@8.0.0(@types/node@24.9.1)(esbuild@0.25.11)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2): + vite@8.0.0(@types/node@20.5.1)(esbuild@0.27.3)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2): dependencies: '@oxc-project/runtime': 0.115.0 lightningcss: 1.32.0 @@ -21678,8 +21703,8 @@ snapshots: rolldown: 1.0.0-rc.9 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 24.9.1 - esbuild: 0.25.11 + '@types/node': 20.5.1 + esbuild: 0.27.3 fsevents: 2.3.3 jiti: 2.6.1 sass: 1.98.0 @@ -21705,6 +21730,10 @@ snapshots: tsx: 4.21.0 yaml: 2.8.2 + vitefu@1.1.1(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)): + optionalDependencies: + vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2) + vitefu@1.1.1(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)): optionalDependencies: vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2) @@ -21798,10 +21827,10 @@ snapshots: transitivePeerDependencies: - msw - vitest@4.1.0(@types/node@24.9.1)(happy-dom@20.0.10)(jsdom@27.4.0)(vite@8.0.0(@types/node@24.9.1)(esbuild@0.25.11)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)): + vitest@4.1.0(@types/node@24.9.1)(happy-dom@20.0.10)(jsdom@27.4.0)(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)): dependencies: '@vitest/expect': 4.1.0 - '@vitest/mocker': 4.1.0(vite@8.0.0(@types/node@24.9.1)(esbuild@0.25.11)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)) + '@vitest/mocker': 4.1.0(vite@7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2)) '@vitest/pretty-format': 4.1.0 '@vitest/runner': 4.1.0 '@vitest/snapshot': 4.1.0 @@ -21818,7 +21847,7 @@ snapshots: tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vite: 8.0.0(@types/node@24.9.1)(esbuild@0.25.11)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.70.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 24.9.1