mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2025-10-20 20:46:15 +08:00
feat: add keymap size (@butterflycup, @fehmer, @miodec) (#5659)
This commit is contained in:
parent
936aac677f
commit
c906bfbe26
10 changed files with 208 additions and 4 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -1049,6 +1049,19 @@
|
|||
<button data-config-value="never">never</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" data-config-name="keymapSize">
|
||||
<div class="groupTitle">
|
||||
<i class="fas fa-keyboard"></i>
|
||||
<span>keymap size</span>
|
||||
</div>
|
||||
<div class="text">Change the size of the keymap.</div>
|
||||
<div class="inputs">
|
||||
<div class="rangeGroup">
|
||||
<div class="value">1.0</div>
|
||||
<input type="range" min="0.5" max="3.5" step="0.1" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sectionSpacer"></div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
||||
|
|
|
|||
19
frontend/src/ts/commandline/lists/keymap-size.ts
Normal file
19
frontend/src/ts/commandline/lists/keymap-size.ts
Normal file
|
|
@ -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;
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ export default {
|
|||
keymapLegendStyle: "lowercase",
|
||||
keymapLayout: "overrideSync",
|
||||
keymapShowTopRow: "layout",
|
||||
keymapSize: 1,
|
||||
fontFamily: "Roboto_Mono",
|
||||
smoothLineScroll: false,
|
||||
alwaysShowDecimalPlaces: false,
|
||||
|
|
|
|||
|
|
@ -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<T extends ConfigValue> {
|
||||
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<T extends ConfigValue> {
|
|||
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<T extends ConfigValue> {
|
|||
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<T extends ConfigValue> {
|
|||
// 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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,6 +72,9 @@ async function initGroups(): Promise<void> {
|
|||
$(
|
||||
".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<void> {
|
|||
$(
|
||||
".pageSettings .section[data-config-name='keymapShowTopRow']"
|
||||
).removeClass("hidden");
|
||||
$(".pageSettings .section[data-config-name='keymapSize']").removeClass(
|
||||
"hidden"
|
||||
);
|
||||
}
|
||||
}
|
||||
) as SettingsGroup<ConfigValue>;
|
||||
|
|
@ -108,6 +114,11 @@ async function initGroups(): Promise<void> {
|
|||
UpdateConfig.setKeymapShowTopRow,
|
||||
"button"
|
||||
) as SettingsGroup<ConfigValue>;
|
||||
groups["keymapSize"] = new SettingsGroup(
|
||||
"keymapSize",
|
||||
UpdateConfig.setKeymapSize,
|
||||
"range"
|
||||
) as SettingsGroup<ConfigValue>;
|
||||
groups["showKeyTips"] = new SettingsGroup(
|
||||
"showKeyTips",
|
||||
UpdateConfig.setKeyTips,
|
||||
|
|
@ -671,6 +682,10 @@ async function fillSettingsPage(): Promise<void> {
|
|||
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", () => {
|
||||
|
|
|
|||
|
|
@ -90,6 +90,9 @@ export type KeymapLegendStyle = z.infer<typeof KeymapLegendStyleSchema>;
|
|||
export const KeymapShowTopRowSchema = z.enum(["always", "layout", "never"]);
|
||||
export type KeymapShowTopRow = z.infer<typeof KeymapShowTopRowSchema>;
|
||||
|
||||
export const KeymapSizeSchema = z.number().min(0.5).max(3.5).step(0.1);
|
||||
export type KeymapSize = z.infer<typeof KeymapSizeSchema>;
|
||||
|
||||
export const SingleListCommandLineSchema = z.enum(["manual", "on"]);
|
||||
export type SingleListCommandLine = z.infer<typeof SingleListCommandLineSchema>;
|
||||
|
||||
|
|
@ -342,6 +345,7 @@ export const ConfigSchema = z
|
|||
keymapLegendStyle: KeymapLegendStyleSchema,
|
||||
keymapLayout: KeymapLayoutSchema,
|
||||
keymapShowTopRow: KeymapShowTopRowSchema,
|
||||
keymapSize: KeymapSizeSchema,
|
||||
fontFamily: FontFamilySchema,
|
||||
smoothLineScroll: z.boolean(),
|
||||
alwaysShowDecimalPlaces: z.boolean(),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue