refactor: config group definitions

- moved group definitionsg to the shared pacakge
 - made sure typescript will throw errors if a config is left without a group
 - removed the 'missing group' check because its not possible anymore
This commit is contained in:
Miodec 2025-01-15 00:04:22 +01:00
parent 0f612257aa
commit c8fd68a601
3 changed files with 158 additions and 189 deletions

View file

@ -7,24 +7,23 @@ import * as Notifications from "../elements/notifications";
import * as ConnectionState from "../states/connection";
import AnimatedModal from "../utils/animated-modal";
import {
ActiveSettingGroups,
ActiveSettingGroupsSchema,
PresetSettingGroup,
PresetSettingGroupSchema,
PresetType,
PresetTypeSchema,
} from "@monkeytype/contracts/schemas/presets";
import { getPreset } from "../controllers/preset-controller";
import defaultConfig from "../constants/default-config";
import { Config as ConfigType } from "@monkeytype/contracts/schemas/configs";
import {
ConfigGroupName,
ConfigGroupNameSchema,
ConfigGroupsLiteral,
ConfigKey,
Config as ConfigType,
} from "@monkeytype/contracts/schemas/configs";
const state = {
presetType: "full" as PresetType,
checkboxes: new Map(
PresetSettingGroupSchema.options.map((key: PresetSettingGroup) => [
key,
true,
])
ConfigGroupNameSchema.options.map((key: ConfigGroupName) => [key, true])
),
setPresetToCurrent: false,
};
@ -122,19 +121,17 @@ async function initializeEditState(id: string): Promise<void> {
}
function addCheckboxListeners(): void {
PresetSettingGroupSchema.options.forEach(
(settingGroup: PresetSettingGroup) => {
const checkboxInput = $(
`#editPresetModal .modal .checkboxList .checkboxTitlePair[data-id="${settingGroup}"] input`
ConfigGroupNameSchema.options.forEach((settingGroup: ConfigGroupName) => {
const checkboxInput = $(
`#editPresetModal .modal .checkboxList .checkboxTitlePair[data-id="${settingGroup}"] input`
);
checkboxInput.on("change", (e) => {
state.checkboxes.set(
settingGroup,
checkboxInput.prop("checked") as boolean
);
checkboxInput.on("change", (e) => {
state.checkboxes.set(
settingGroup,
checkboxInput.prop("checked") as boolean
);
});
}
);
});
});
const presetToCurrentCheckbox = $(
`#editPresetModal .modal .changePresetToCurrentCheckbox input`
@ -154,7 +151,7 @@ function addCheckBoxes(): void {
const settingGroupListEl = $(
"#editPresetModal .modal .inputs .checkboxList"
).empty();
PresetSettingGroupSchema.options.forEach((currSettingGroup) => {
ConfigGroupNameSchema.options.forEach((currSettingGroup) => {
const currSettingGroupTitle = camelCaseToSpaced(currSettingGroup);
const settingGroupCheckbox: string = `<label class="checkboxTitlePair" data-id="${currSettingGroup}">
<input type="checkbox" />
@ -169,13 +166,11 @@ function addCheckBoxes(): void {
}
function updateUI(): void {
PresetSettingGroupSchema.options.forEach(
(settingGroup: PresetSettingGroup) => {
$(
`#editPresetModal .modal .checkboxList .checkboxTitlePair[data-id="${settingGroup}"] input`
).prop("checked", state.checkboxes.get(settingGroup));
}
);
ConfigGroupNameSchema.options.forEach((settingGroup: ConfigGroupName) => {
$(
`#editPresetModal .modal .checkboxList .checkboxTitlePair[data-id="${settingGroup}"] input`
).prop("checked", state.checkboxes.get(settingGroup));
});
$(`#editPresetModal .modal .presetType button`).removeClass("active");
$(
`#editPresetModal .modal .presetType button[value="${state.presetType}"]`
@ -291,7 +286,7 @@ async function apply(): Promise<void> {
return;
}
const configChanges = getConfigChanges();
const activeSettingGroups: ActiveSettingGroups | null =
const activeSettingGroups: ConfigGroupName[] | null =
state.presetType === "partial" ? getActiveSettingGroupsFromState() : null;
const response = await Ape.presets.save({
body: {
@ -342,147 +337,23 @@ async function apply(): Promise<void> {
Loader.hide();
}
function getSettingGroup(configFieldName: string): PresetSettingGroup | null {
const themeSettings = [
"theme",
"themeLight",
"themeDark",
"autoSwitchTheme",
"customTheme",
"customThemeColors",
"favThemes",
"flipTestColors",
"colorfulMode",
"randomTheme",
"customBackground",
"customBackgroundSize",
"customBackgroundFilter",
];
const hideElementsSettings = [
"showKeyTips",
"capsLockWarning",
"showOutOfFocusWarning",
"showAverage",
];
const caretSettings = [
"smoothCaret",
"caretStyle",
"paceCaretStyle",
"paceCaret",
"paceCaretCustomSpeed",
"repeatedPace",
];
const behaviorSettings = [
"quickRestart",
"difficulty",
"blindMode",
"funbox",
"alwaysShowWordsHistory",
"singleListCommandLine",
"minWpm",
"minWpmCustomSpeed",
"minAcc",
"minAccCustom",
"repeatQuotes",
"customLayoutfluid",
"minBurst",
"minBurstCustomSpeed",
"britishEnglish",
"tags",
];
const testSettings = [
"punctuation",
"words",
"time",
"numbers",
"mode",
"quoteLength",
"language",
"burstHeatmap",
];
const appearanceSettings = [
"fontSize",
"timerStyle",
"liveSpeedStyle",
"liveAccStyle",
"liveBurstStyle",
"timerColor",
"timerOpacity",
"showAllLines",
"keymapMode",
"keymapStyle",
"keymapLegendStyle",
"keymapLayout",
"keymapShowTopRow",
"keymapSize",
"fontFamily",
"smoothLineScroll",
"alwaysShowDecimalPlaces",
"startGraphsAtZero",
"highlightMode",
"tapeMode",
"tapeMargin",
"typingSpeedUnit",
"maxLineWidth",
];
const inputSettings = [
"freedomMode",
"quickEnd",
"layout",
"confidenceMode",
"indicateTypos",
"stopOnError",
"hideExtraLetters",
"strictSpace",
"oppositeShiftMode",
"lazyMode",
"codeUnindentOnBackspace",
];
const soundSettings = ["playSoundOnError", "playSoundOnClick", "soundVolume"];
const hiddenSettings = ["accountChart", "monkey", "monkeyPowerLevel"];
const adsSettings = ["ads"];
if (themeSettings.includes(configFieldName)) {
return "theme";
} else if (hideElementsSettings.includes(configFieldName)) {
return "hideElements";
} else if (caretSettings.includes(configFieldName)) {
return "caret";
} else if (behaviorSettings.includes(configFieldName)) {
return "behavior";
} else if (testSettings.includes(configFieldName)) {
return "test";
} else if (appearanceSettings.includes(configFieldName)) {
return "appearance";
} else if (inputSettings.includes(configFieldName)) {
return "input";
} else if (soundSettings.includes(configFieldName)) {
return "sound";
} else if (hiddenSettings.includes(configFieldName)) {
return "hidden";
} else if (adsSettings.includes(configFieldName)) {
return "ads";
}
Notifications.add(
`Setting group not found for setting ${configFieldName} - it will not be saved. Please report this.`,
-1
);
return null;
function getSettingGroup(configFieldName: ConfigKey): ConfigGroupName {
return ConfigGroupsLiteral[configFieldName];
}
function getPartialConfigChanges(
configChanges: Partial<ConfigType>
): Partial<ConfigType> {
const activeConfigChanges: Partial<ConfigType> = {};
Object.keys(defaultConfig)
(Object.keys(defaultConfig) as ConfigKey[])
.filter((settingName) => {
const group = getSettingGroup(settingName);
if (group === null) return false;
return state.checkboxes.get(group) === true;
})
.forEach((settingName) => {
const safeSettingName = settingName as keyof Partial<ConfigType>;
const safeSettingName = settingName;
const newValue =
configChanges[safeSettingName] !== undefined
? configChanges[safeSettingName]
@ -492,13 +363,13 @@ function getPartialConfigChanges(
});
return activeConfigChanges;
}
function getActiveSettingGroupsFromState(): ActiveSettingGroups {
return ActiveSettingGroupsSchema.parse(
Array.from(state.checkboxes.entries())
.filter(([, value]) => value)
.map(([key]) => key)
);
function getActiveSettingGroupsFromState(): ConfigGroupName[] {
return Array.from(state.checkboxes.entries())
.filter(([, value]) => value)
.map(([key]) => key);
}
function getConfigChanges(): Partial<ConfigType> {
const activeConfigChanges =
state.presetType === "partial"

View file

@ -1,4 +1,4 @@
import { z } from "zod";
import { z, ZodSchema } from "zod";
import { LanguageSchema, token } from "./util";
import * as Shared from "./shared";
@ -379,11 +379,119 @@ export const ConfigSchema = z
lazyMode: z.boolean(),
showAverage: ShowAverageSchema,
maxLineWidth: MaxLineWidthSchema,
})
} satisfies Record<string, ZodSchema>)
.strict();
export type Config = z.infer<typeof ConfigSchema>;
export type ConfigKey = keyof Config;
export type ConfigValue = Config[keyof Config];
export const PartialConfigSchema = ConfigSchema.partial();
export type PartialConfig = z.infer<typeof PartialConfigSchema>;
export type ConfigValue = Config[keyof Config];
export const ConfigGroupNameSchema = z.enum([
"test",
"behavior",
"input",
"sound",
"caret",
"appearance",
"theme",
"hideElements",
"ads",
"hidden",
]);
export type ConfigGroupName = z.infer<typeof ConfigGroupNameSchema>;
export const ConfigGroupsLiteral = {
theme: "theme",
themeLight: "theme",
themeDark: "theme",
autoSwitchTheme: "theme",
customTheme: "theme",
customThemeColors: "theme",
favThemes: "theme",
showKeyTips: "hideElements",
smoothCaret: "caret",
codeUnindentOnBackspace: "input",
quickRestart: "behavior",
punctuation: "test",
numbers: "test",
words: "test",
time: "test",
mode: "test",
quoteLength: "test",
language: "test",
fontSize: "appearance",
freedomMode: "input",
difficulty: "behavior",
blindMode: "behavior",
quickEnd: "input",
caretStyle: "caret",
paceCaretStyle: "caret",
flipTestColors: "theme",
layout: "input",
funbox: "behavior",
confidenceMode: "input",
indicateTypos: "input",
timerStyle: "appearance",
liveSpeedStyle: "appearance",
liveAccStyle: "appearance",
liveBurstStyle: "appearance",
colorfulMode: "theme",
randomTheme: "theme",
timerColor: "appearance",
timerOpacity: "appearance",
stopOnError: "input",
showAllLines: "appearance",
keymapMode: "appearance",
keymapStyle: "appearance",
keymapLegendStyle: "appearance",
keymapLayout: "appearance",
keymapShowTopRow: "appearance",
keymapSize: "appearance",
fontFamily: "appearance",
smoothLineScroll: "appearance",
alwaysShowDecimalPlaces: "appearance",
alwaysShowWordsHistory: "behavior",
singleListCommandLine: "behavior",
capsLockWarning: "hideElements",
playSoundOnError: "sound",
playSoundOnClick: "sound",
soundVolume: "sound",
startGraphsAtZero: "appearance",
showOutOfFocusWarning: "hideElements",
paceCaret: "caret",
paceCaretCustomSpeed: "caret",
repeatedPace: "caret",
accountChart: "hidden",
minWpm: "behavior",
minWpmCustomSpeed: "behavior",
highlightMode: "appearance",
tapeMode: "appearance",
tapeMargin: "appearance",
typingSpeedUnit: "appearance",
ads: "ads",
hideExtraLetters: "input",
strictSpace: "input",
minAcc: "behavior",
minAccCustom: "behavior",
monkey: "hidden",
repeatQuotes: "behavior",
oppositeShiftMode: "input",
customBackground: "theme",
customBackgroundSize: "theme",
customBackgroundFilter: "theme",
customLayoutfluid: "behavior",
monkeyPowerLevel: "hidden",
minBurst: "behavior",
minBurstCustomSpeed: "behavior",
burstHeatmap: "test",
britishEnglish: "behavior",
lazyMode: "input",
showAverage: "hideElements",
maxLineWidth: "appearance",
} as const satisfies Record<ConfigKey, ConfigGroupName>;
export type ConfigGroups = typeof ConfigGroupsLiteral;

View file

@ -1,6 +1,10 @@
import { z } from "zod";
import { IdSchema, TagSchema } from "./util";
import { PartialConfigSchema } from "./configs";
import {
ConfigGroupName,
ConfigGroupNameSchema,
PartialConfigSchema,
} from "./configs";
export const PresetNameSchema = z
.string()
@ -11,28 +15,15 @@ export type PresentName = z.infer<typeof PresetNameSchema>;
export const PresetTypeSchema = z.enum(["full", "partial"]);
export type PresetType = z.infer<typeof PresetTypeSchema>;
export const PresetSettingGroupSchema = z.enum([
"test",
"behavior",
"input",
"sound",
"caret",
"appearance",
"theme",
"hideElements",
"ads",
"hidden",
]);
export type PresetSettingGroup = z.infer<typeof PresetSettingGroupSchema>;
export const ActiveSettingGroupsSchema = z
.array(PresetSettingGroupSchema)
const PresetSettingsGroupsSchema = z
.array(ConfigGroupNameSchema)
.min(1)
.superRefine((settingList, ctx) => {
PresetSettingGroupSchema.options.forEach(
(presetSettingGroup: PresetSettingGroup) => {
ConfigGroupNameSchema.options.forEach(
(presetSettingGroup: ConfigGroupName) => {
const duplicateElemExits: boolean =
settingList.filter(
(settingGroup: PresetSettingGroup) =>
(settingGroup: ConfigGroupName) =>
settingGroup === presetSettingGroup
).length > 1;
if (duplicateElemExits) {
@ -44,12 +35,11 @@ export const ActiveSettingGroupsSchema = z
}
);
});
export type ActiveSettingGroups = z.infer<typeof ActiveSettingGroupsSchema>;
export const PresetSchema = z.object({
_id: IdSchema,
name: PresetNameSchema,
settingGroups: ActiveSettingGroupsSchema.nullable().optional(),
settingGroups: PresetSettingsGroupsSchema.nullable().optional(),
config: PartialConfigSchema.extend({
tags: z.array(TagSchema).optional(),
}),