From c906bfbe26e84d269b1fce9abc4d5721df486ee5 Mon Sep 17 00:00:00 2001 From: butterflycup Date: Mon, 29 Jul 2024 14:39:57 +0200 Subject: [PATCH] feat: add keymap size (@butterflycup, @fehmer, @miodec) (#5659) --- frontend/__tests__/root/config.spec.ts | 17 +++++ frontend/src/html/pages/settings.html | 13 ++++ frontend/src/styles/settings.scss | 6 ++ frontend/src/ts/commandline/lists.ts | 2 + .../src/ts/commandline/lists/keymap-size.ts | 19 ++++++ frontend/src/ts/config.ts | 33 +++++++++ frontend/src/ts/constants/default-config.ts | 1 + .../ts/elements/settings/settings-group.ts | 49 +++++++++++-- frontend/src/ts/pages/settings.ts | 68 +++++++++++++++++++ packages/contracts/src/schemas/configs.ts | 4 ++ 10 files changed, 208 insertions(+), 4 deletions(-) create mode 100644 frontend/src/ts/commandline/lists/keymap-size.ts diff --git a/frontend/__tests__/root/config.spec.ts b/frontend/__tests__/root/config.spec.ts index 81f34883f..f6ceacf8b 100644 --- a/frontend/__tests__/root/config.spec.ts +++ b/frontend/__tests__/root/config.spec.ts @@ -1,4 +1,5 @@ import * as Config from "../../src/ts/config"; + import { CustomThemeColors } from "@monkeytype/contracts/schemas/configs"; import { randomBytes } from "crypto"; @@ -188,6 +189,22 @@ describe("Config", () => { expect(Config.setKeymapShowTopRow("never")).toBe(true); expect(Config.setKeymapShowTopRow("invalid" as any)).toBe(false); }); + it("setKeymapSize", () => { + expect(Config.setKeymapSize(0.5)).toBe(true); + expect(Config.setKeymapSize(2)).toBe(true); + expect(Config.setKeymapSize(3.5)).toBe(true); + expect(Config.setKeymapSize("invalid" as any)).toBe(false); + + //invalid values being "auto-fixed" + expect(Config.setKeymapSize(0)).toBe(true); + expect(Config.default.keymapSize).toBe(0.5); + expect(Config.setKeymapSize(4)).toBe(true); + expect(Config.default.keymapSize).toBe(3.5); + expect(Config.setKeymapSize(1.25)).toBe(true); + expect(Config.default.keymapSize).toBe(1.3); + expect(Config.setKeymapSize(1.24)).toBe(true); + expect(Config.default.keymapSize).toBe(1.2); + }); it("setCustomBackgroundSize", () => { expect(Config.setCustomBackgroundSize("contain")).toBe(true); expect(Config.setCustomBackgroundSize("cover")).toBe(true); diff --git a/frontend/src/html/pages/settings.html b/frontend/src/html/pages/settings.html index 24cc0dc04..ab0922570 100644 --- a/frontend/src/html/pages/settings.html +++ b/frontend/src/html/pages/settings.html @@ -1049,6 +1049,19 @@ +
+
+ + keymap size +
+
Change the size of the keymap.
+
+
+
1.0
+ +
+
+
diff --git a/frontend/src/styles/settings.scss b/frontend/src/styles/settings.scss index cc8ddbf77..e63c2eb5a 100644 --- a/frontend/src/styles/settings.scss +++ b/frontend/src/styles/settings.scss @@ -70,6 +70,12 @@ } } + .rangeGroup { + display: grid; + grid-template-columns: auto 1fr; + gap: 1rem; + } + &[data-config-name="autoSwitchThemeInputs"] { grid-template-areas: unset; grid-template-columns: 1fr 3fr 1fr 3fr; diff --git a/frontend/src/ts/commandline/lists.ts b/frontend/src/ts/commandline/lists.ts index 6cdec2608..a0f72588f 100644 --- a/frontend/src/ts/commandline/lists.ts +++ b/frontend/src/ts/commandline/lists.ts @@ -56,6 +56,7 @@ import KeymapModeCommands from "./lists/keymap-mode"; import KeymapStyleCommands from "./lists/keymap-style"; import KeymapLegendStyleCommands from "./lists/keymap-legend-style"; import KeymapShowTopRowCommands from "./lists/keymap-show-top-row"; +import KeymapSizeCommands from "./lists/keymap-size"; import EnableAdsCommands from "./lists/enable-ads"; import MonkeyPowerLevelCommands from "./lists/monkey-power-level"; import BailOutCommands from "./lists/bail-out"; @@ -296,6 +297,7 @@ export const commands: MonkeyTypes.CommandsSubgroup = { ...KeymapModeCommands, ...KeymapStyleCommands, ...KeymapLegendStyleCommands, + ...KeymapSizeCommands, ...KeymapLayoutsCommands, ...KeymapShowTopRowCommands, diff --git a/frontend/src/ts/commandline/lists/keymap-size.ts b/frontend/src/ts/commandline/lists/keymap-size.ts new file mode 100644 index 000000000..eb0f4e0ca --- /dev/null +++ b/frontend/src/ts/commandline/lists/keymap-size.ts @@ -0,0 +1,19 @@ +import Config, * as UpdateConfig from "../../config"; + +const commands: MonkeyTypes.Command[] = [ + { + id: "changeKeymapSize", + display: "Keymap size...", + icon: "fa-keyboard", + alias: "keyboard", + input: true, + defaultValue: (): string => { + return Config.keymapSize.toString(); + }, + exec: ({ input }): void => { + if (input === undefined || input === "") return; + UpdateConfig.setKeymapSize(parseFloat(input)); + }, + }, +]; +export default commands; diff --git a/frontend/src/ts/config.ts b/frontend/src/ts/config.ts index 45ff6d91a..8baa0d73a 100644 --- a/frontend/src/ts/config.ts +++ b/frontend/src/ts/config.ts @@ -19,6 +19,7 @@ import { import { isDevEnvironment, reloadAfter } from "./utils/misc"; import * as ConfigSchemas from "@monkeytype/contracts/schemas/configs"; import { Config } from "@monkeytype/contracts/schemas/configs"; +import { roundTo1 } from "./utils/numbers"; export let localStorageConfig: Config; @@ -1701,6 +1702,38 @@ export function setKeymapShowTopRow( return true; } +export function setKeymapSize( + keymapSize: ConfigSchemas.KeymapSize, + nosave?: boolean +): boolean { + //auto-fix values to avoid validation errors + if (keymapSize < 0.5) keymapSize = 0.5; + if (keymapSize > 3.5) keymapSize = 3.5; + keymapSize = roundTo1(keymapSize); + + if ( + !isConfigValueValid( + "keymap size", + keymapSize, + ConfigSchemas.KeymapSizeSchema + ) + ) { + return false; + } + + config.keymapSize = keymapSize; + + $("#keymap").css("zoom", keymapSize); + + saveToLocalStorage("keymapSize", nosave); + ConfigEvent.dispatch("keymapSize", config.keymapSize, nosave); + + // trigger a resize event to update the layout - handled in ui.ts:108 + $(window).trigger("resize"); + + return true; +} + export function setLayout( layout: ConfigSchemas.Layout, nosave?: boolean diff --git a/frontend/src/ts/constants/default-config.ts b/frontend/src/ts/constants/default-config.ts index 6d280542d..9b252847b 100644 --- a/frontend/src/ts/constants/default-config.ts +++ b/frontend/src/ts/constants/default-config.ts @@ -59,6 +59,7 @@ export default { keymapLegendStyle: "lowercase", keymapLayout: "overrideSync", keymapShowTopRow: "layout", + keymapSize: 1, fontFamily: "Roboto_Mono", smoothLineScroll: false, alwaysShowDecimalPlaces: false, diff --git a/frontend/src/ts/elements/settings/settings-group.ts b/frontend/src/ts/elements/settings/settings-group.ts index 83de62dcc..1a9d3f396 100644 --- a/frontend/src/ts/elements/settings/settings-group.ts +++ b/frontend/src/ts/elements/settings/settings-group.ts @@ -3,18 +3,21 @@ import Config from "../../config"; import * as Notifications from "../notifications"; // @ts-expect-error TODO: update slim-select import SlimSelect from "slim-select"; +import { debounce } from "throttle-debounce"; + +type Mode = "select" | "button" | "range"; export default class SettingsGroup { public configName: string; public configValue: T; public configFunction: (param: T, nosave?: boolean) => boolean; - public mode: string; + public mode: Mode; public setCallback?: () => void; public updateCallback?: () => void; constructor( configName: string, configFunction: (param: T, nosave?: boolean) => boolean, - mode: string, + mode: Mode, setCallback?: () => void, updateCallback?: () => void ) { @@ -67,6 +70,29 @@ export default class SettingsGroup { this.setValue(typed as T); } ); + } else if (this.mode === "range") { + const rangeElement = document.querySelector( + `.pageSettings .section[data-config-name=${this.configName}] input[type=range]` + ); + + if (!rangeElement) { + Notifications.add(`Failed to find range element for ${configName}`, -1); + return; + } + + const debounced = debounce(250, (val) => { + this.setValue(val); + }); + + rangeElement.addEventListener("input", (e) => { + const target = $(e.target as HTMLInputElement); + if (target.hasClass("disabled") || target.hasClass("no-auto-handle")) { + return; + } + const val = parseFloat(target.val() as string) as unknown as T; + this.updateUI(val); + debounced(val); + }); } } @@ -76,8 +102,9 @@ export default class SettingsGroup { if (this.setCallback) this.setCallback(); } - updateUI(): void { - this.configValue = Config[this.configName as keyof typeof Config] as T; + updateUI(valueOverride?: T): void { + this.configValue = + valueOverride ?? (Config[this.configName as keyof typeof Config] as T); $( `.pageSettings .section[data-config-name='${this.configName}'] button` ).removeClass("active"); @@ -103,6 +130,20 @@ export default class SettingsGroup { // eslint-disable-next-line @typescript-eslint/no-base-to-string `.pageSettings .section[data-config-name='${this.configName}'] button[data-config-value='${this.configValue}']` ).addClass("active"); + } else if (this.mode === "range") { + const range = document.querySelector( + `.pageSettings .section[data-config-name='${this.configName}'] input[type=range]` + ) as HTMLInputElement | null; + const rangeValue = document.querySelector( + `.pageSettings .section[data-config-name='${this.configName}'] .value` + ) as HTMLSpanElement | null; + + if (range === null || rangeValue === null) { + return; + } + + range.value = this.configValue as unknown as string; + rangeValue.textContent = `${(this.configValue as number).toFixed(1)}`; } if (this.updateCallback) this.updateCallback(); } diff --git a/frontend/src/ts/pages/settings.ts b/frontend/src/ts/pages/settings.ts index d2f8d54b8..f71a32176 100644 --- a/frontend/src/ts/pages/settings.ts +++ b/frontend/src/ts/pages/settings.ts @@ -72,6 +72,9 @@ async function initGroups(): Promise { $( ".pageSettings .section[data-config-name='keymapShowTopRow']" ).addClass("hidden"); + $(".pageSettings .section[data-config-name='keymapSize']").addClass( + "hidden" + ); } else { $(".pageSettings .section[data-config-name='keymapStyle']").removeClass( "hidden" @@ -85,6 +88,9 @@ async function initGroups(): Promise { $( ".pageSettings .section[data-config-name='keymapShowTopRow']" ).removeClass("hidden"); + $(".pageSettings .section[data-config-name='keymapSize']").removeClass( + "hidden" + ); } } ) as SettingsGroup; @@ -108,6 +114,11 @@ async function initGroups(): Promise { UpdateConfig.setKeymapShowTopRow, "button" ) as SettingsGroup; + groups["keymapSize"] = new SettingsGroup( + "keymapSize", + UpdateConfig.setKeymapSize, + "range" + ) as SettingsGroup; groups["showKeyTips"] = new SettingsGroup( "showKeyTips", UpdateConfig.setKeyTips, @@ -671,6 +682,10 @@ async function fillSettingsPage(): Promise { Config.maxLineWidth ); + $(".pageSettings .section[data-config-name='keymapSize'] input").val( + Config.keymapSize + ); + $(".pageSettings .section[data-config-name='customLayoutfluid'] input").val( Config.customLayoutfluid.replace(/#/g, " ") ); @@ -1311,6 +1326,59 @@ $( } }); +$( + ".pageSettings .section[data-config-name='keymapSize'] .inputAndButton button.save" +).on("click", () => { + const didConfigSave = UpdateConfig.setKeymapSize( + parseFloat( + $( + ".pageSettings .section[data-config-name='keymapSize'] .inputAndButton input" + ).val() as string + ) + ); + if (didConfigSave) { + Notifications.add("Saved", 1, { + duration: 1, + }); + } +}); + +$( + ".pageSettings .section[data-config-name='keymapSize'] .inputAndButton input" +).on("focusout", () => { + const didConfigSave = UpdateConfig.setKeymapSize( + parseFloat( + $( + ".pageSettings .section[data-config-name='keymapSize'] .inputAndButton input" + ).val() as string + ) + ); + if (didConfigSave) { + Notifications.add("Saved", 1, { + duration: 1, + }); + } +}); + +$( + ".pageSettings .section[data-config-name='keymapSize'] .inputAndButton input" +).on("keypress", (e) => { + if (e.key === "Enter") { + const didConfigSave = UpdateConfig.setKeymapSize( + parseFloat( + $( + ".pageSettings .section[data-config-name='keymapSize'] .inputAndButton input" + ).val() as string + ) + ); + if (didConfigSave) { + Notifications.add("Saved", 1, { + duration: 1, + }); + } + } +}); + $( ".pageSettings .section[data-config-name='customLayoutfluid'] .inputAndButton button.save" ).on("click", () => { diff --git a/packages/contracts/src/schemas/configs.ts b/packages/contracts/src/schemas/configs.ts index 437aee7e6..6e2916f72 100644 --- a/packages/contracts/src/schemas/configs.ts +++ b/packages/contracts/src/schemas/configs.ts @@ -90,6 +90,9 @@ export type KeymapLegendStyle = z.infer; export const KeymapShowTopRowSchema = z.enum(["always", "layout", "never"]); export type KeymapShowTopRow = z.infer; +export const KeymapSizeSchema = z.number().min(0.5).max(3.5).step(0.1); +export type KeymapSize = z.infer; + export const SingleListCommandLineSchema = z.enum(["manual", "on"]); export type SingleListCommandLine = z.infer; @@ -342,6 +345,7 @@ export const ConfigSchema = z keymapLegendStyle: KeymapLegendStyleSchema, keymapLayout: KeymapLayoutSchema, keymapShowTopRow: KeymapShowTopRowSchema, + keymapSize: KeymapSizeSchema, fontFamily: FontFamilySchema, smoothLineScroll: z.boolean(), alwaysShowDecimalPlaces: z.boolean(),