mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2025-10-06 05:26:54 +08:00
impr: monkey now types with the same hand as the user (@ShaneBerhoff) (#5930)
This PR also refactors some layout conversion code to reduce repetition.
This commit is contained in:
parent
ebf98f15f6
commit
ac639348ca
5 changed files with 355 additions and 182 deletions
|
@ -35,6 +35,7 @@ import { IgnoredKeys } from "../constants/ignored-keys";
|
|||
import { ModifierKeys } from "../constants/modifier-keys";
|
||||
import { navigate } from "./route-controller";
|
||||
import * as Loader from "../elements/loader";
|
||||
import * as KeyConverter from "../utils/key-converter";
|
||||
|
||||
let dontInsertSpace = false;
|
||||
let correctShiftUsed = true;
|
||||
|
@ -1047,8 +1048,6 @@ $(document).on("keydown", async (event) => {
|
|||
}
|
||||
}
|
||||
|
||||
Monkey.type();
|
||||
|
||||
if (event.key === "Backspace" && TestInput.input.current.length === 0) {
|
||||
backspaceToPrevious();
|
||||
if (TestInput.input.current) {
|
||||
|
@ -1123,14 +1122,16 @@ $(document).on("keydown", async (event) => {
|
|||
|
||||
return;
|
||||
}
|
||||
const keycode = ShiftTracker.layoutKeyToKeycode(event.key, keymapLayout);
|
||||
const keycode = KeyConverter.layoutKeyToKeycode(event.key, keymapLayout);
|
||||
|
||||
correctShiftUsed =
|
||||
keycode === undefined
|
||||
? true
|
||||
: ShiftTracker.isUsingOppositeShift(keycode);
|
||||
} else {
|
||||
correctShiftUsed = ShiftTracker.isUsingOppositeShift(event.code);
|
||||
correctShiftUsed = ShiftTracker.isUsingOppositeShift(
|
||||
event.code as KeyConverter.Keycode
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1182,6 +1183,7 @@ $("#wordsInput").on("keydown", (event) => {
|
|||
return;
|
||||
}
|
||||
|
||||
Monkey.type(event);
|
||||
// console.debug("Event: keydown", event);
|
||||
|
||||
if (event.code === "NumpadEnter" && Config.funbox.includes("58008")) {
|
||||
|
@ -1235,10 +1237,11 @@ $("#wordsInput").on("keyup", (event) => {
|
|||
return;
|
||||
}
|
||||
|
||||
Monkey.stop(event);
|
||||
|
||||
if (IgnoredKeys.includes(event.key)) return;
|
||||
|
||||
if (TestUI.resultVisible) return;
|
||||
Monkey.stop();
|
||||
});
|
||||
|
||||
$("#wordsInput").on("beforeinput", (event) => {
|
||||
|
|
|
@ -8,10 +8,10 @@ let isAltGrPressed = false;
|
|||
const isPunctuationPattern = /\p{P}/u;
|
||||
|
||||
export async function getCharFromEvent(
|
||||
event: JQuery.KeyDownEvent
|
||||
event: JQuery.KeyDownEvent | JQuery.KeyUpEvent
|
||||
): Promise<string | null> {
|
||||
function emulatedLayoutGetVariant(
|
||||
event: JQuery.KeyDownEvent,
|
||||
event: JQuery.KeyDownEvent | JQuery.KeyUpEvent,
|
||||
keyVariants: string
|
||||
): string | undefined {
|
||||
let isCapitalized = event.shiftKey;
|
||||
|
|
|
@ -2,6 +2,7 @@ import { mapRange } from "@monkeytype/util/numbers";
|
|||
import Config from "../config";
|
||||
import * as ConfigEvent from "../observables/config-event";
|
||||
import * as TestState from "../test/test-state";
|
||||
import * as KeyConverter from "../utils/key-converter";
|
||||
|
||||
ConfigEvent.subscribe((eventKey) => {
|
||||
if (eventKey === "monkey" && TestState.isActive) {
|
||||
|
@ -15,6 +16,7 @@ ConfigEvent.subscribe((eventKey) => {
|
|||
|
||||
let left = false;
|
||||
let right = false;
|
||||
const middleKeysState = { left: false, right: false, last: "right" };
|
||||
|
||||
// 0 hand up
|
||||
// 1 hand down
|
||||
|
@ -38,8 +40,6 @@ const elementsFast = {
|
|||
"11": document.querySelector("#monkey .fast .both"),
|
||||
};
|
||||
|
||||
let last = "right";
|
||||
|
||||
function toBit(b: boolean): "1" | "0" {
|
||||
return b ? "1" : "0";
|
||||
}
|
||||
|
@ -70,25 +70,68 @@ export function updateFastOpacity(num: number): void {
|
|||
$("#monkey").css({ animationDuration: animDuration + "s" });
|
||||
}
|
||||
|
||||
export function type(): void {
|
||||
export function type(event: JQuery.KeyDownEvent): void {
|
||||
if (!Config.monkey) return;
|
||||
if (!left && last === "right") {
|
||||
left = true;
|
||||
last = "left";
|
||||
} else if (!right) {
|
||||
right = true;
|
||||
last = "right";
|
||||
|
||||
const { leftSide, rightSide } = KeyConverter.keycodeToKeyboardSide(
|
||||
event.code as KeyConverter.Keycode
|
||||
);
|
||||
if (leftSide && rightSide) {
|
||||
// if its a middle key handle special case
|
||||
if (middleKeysState.last === "left") {
|
||||
if (!right) {
|
||||
right = true;
|
||||
middleKeysState.last = "right";
|
||||
middleKeysState.right = true;
|
||||
} else if (!left) {
|
||||
left = true;
|
||||
middleKeysState.last = "left";
|
||||
middleKeysState.left = true;
|
||||
}
|
||||
} else {
|
||||
if (!left) {
|
||||
left = true;
|
||||
middleKeysState.last = "left";
|
||||
middleKeysState.left = true;
|
||||
} else if (!right) {
|
||||
right = true;
|
||||
middleKeysState.last = "right";
|
||||
middleKeysState.right = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// normal key set hand
|
||||
left = left || leftSide;
|
||||
right = right || rightSide;
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
export function stop(): void {
|
||||
export function stop(event: JQuery.KeyUpEvent): void {
|
||||
if (!Config.monkey) return;
|
||||
if (left) {
|
||||
left = false;
|
||||
} else if (right) {
|
||||
right = false;
|
||||
|
||||
const { leftSide, rightSide } = KeyConverter.keycodeToKeyboardSide(
|
||||
event.code as KeyConverter.Keycode
|
||||
);
|
||||
if (leftSide && rightSide) {
|
||||
// if middle key handle special case
|
||||
if (middleKeysState.left && middleKeysState.last === "left") {
|
||||
left = false;
|
||||
middleKeysState.left = false;
|
||||
} else if (middleKeysState.right && middleKeysState.last === "right") {
|
||||
right = false;
|
||||
middleKeysState.right = false;
|
||||
} else {
|
||||
left = left && !middleKeysState.left;
|
||||
right = right && !middleKeysState.right;
|
||||
}
|
||||
} else {
|
||||
// normal key unset hand
|
||||
left = left && !leftSide;
|
||||
right = right && !rightSide;
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
|
|
|
@ -2,71 +2,13 @@ import Config from "../config";
|
|||
import * as JSONData from "../utils/json-data";
|
||||
import { capsState } from "./caps-warning";
|
||||
import * as Notifications from "../elements/notifications";
|
||||
import * as KeyConverter from "../utils/key-converter";
|
||||
|
||||
export let leftState = false;
|
||||
export let rightState = false;
|
||||
|
||||
type KeymapLegendStates = [letters: boolean, symbols: boolean];
|
||||
|
||||
const qwertyKeycodeKeymap = [
|
||||
[
|
||||
"Backquote",
|
||||
"Digit1",
|
||||
"Digit2",
|
||||
"Digit3",
|
||||
"Digit4",
|
||||
"Digit5",
|
||||
"Digit6",
|
||||
"Digit7",
|
||||
"Digit8",
|
||||
"Digit9",
|
||||
"Digit0",
|
||||
"Minus",
|
||||
"Equal",
|
||||
],
|
||||
[
|
||||
"KeyQ",
|
||||
"KeyW",
|
||||
"KeyE",
|
||||
"KeyR",
|
||||
"KeyT",
|
||||
"KeyY",
|
||||
"KeyU",
|
||||
"KeyI",
|
||||
"KeyO",
|
||||
"KeyP",
|
||||
"BracketLeft",
|
||||
"BracketRight",
|
||||
"Backslash",
|
||||
],
|
||||
[
|
||||
"KeyA",
|
||||
"KeyS",
|
||||
"KeyD",
|
||||
"KeyF",
|
||||
"KeyG",
|
||||
"KeyH",
|
||||
"KeyJ",
|
||||
"KeyK",
|
||||
"KeyL",
|
||||
"Semicolon",
|
||||
"Quote",
|
||||
],
|
||||
[
|
||||
"KeyZ",
|
||||
"KeyX",
|
||||
"KeyC",
|
||||
"KeyV",
|
||||
"KeyB",
|
||||
"KeyN",
|
||||
"KeyM",
|
||||
"Comma",
|
||||
"Period",
|
||||
"Slash",
|
||||
],
|
||||
["Space"],
|
||||
];
|
||||
|
||||
const symbolsPattern = /^[^\p{L}\p{N}]{1}$/u;
|
||||
|
||||
const isMacLike = /Mac|iPod|iPhone|iPad/.test(navigator.platform);
|
||||
|
@ -149,11 +91,10 @@ async function updateKeymapLegendCasing(): Promise<void> {
|
|||
(character) => symbolsPattern.test(character ?? "")
|
||||
);
|
||||
|
||||
const keycode = layoutKeyToKeycode(lowerCaseCharacter, layout);
|
||||
const keycode = KeyConverter.layoutKeyToKeycode(lowerCaseCharacter, layout);
|
||||
if (keycode === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oppositeShift = isUsingOppositeShift(keycode);
|
||||
|
||||
const state = keyIsSymbol ? symbolsState : lettersState;
|
||||
|
@ -195,71 +136,7 @@ export function reset(): void {
|
|||
rightState = false;
|
||||
}
|
||||
|
||||
const leftSideKeys = [
|
||||
"KeyQ",
|
||||
"KeyW",
|
||||
"KeyE",
|
||||
"KeyR",
|
||||
"KeyT",
|
||||
"KeyY",
|
||||
|
||||
"KeyA",
|
||||
"KeyS",
|
||||
"KeyD",
|
||||
"KeyF",
|
||||
"KeyG",
|
||||
|
||||
"IntlBackslash",
|
||||
"KeyZ",
|
||||
"KeyX",
|
||||
"KeyC",
|
||||
"KeyV",
|
||||
"KeyB",
|
||||
|
||||
"Backquote",
|
||||
"Digit1",
|
||||
"Digit2",
|
||||
"Digit3",
|
||||
"Digit4",
|
||||
"Digit5",
|
||||
"Digit6",
|
||||
];
|
||||
|
||||
const rightSideKeys = [
|
||||
"KeyY",
|
||||
"KeyU",
|
||||
"KeyI",
|
||||
"KeyO",
|
||||
"KeyP",
|
||||
|
||||
"KeyH",
|
||||
"KeyJ",
|
||||
"KeyK",
|
||||
"KeyL",
|
||||
|
||||
"KeyB",
|
||||
"KeyN",
|
||||
"KeyM",
|
||||
|
||||
"Digit6",
|
||||
"Digit7",
|
||||
"Digit8",
|
||||
"Digit9",
|
||||
"Digit0",
|
||||
"Minus",
|
||||
"Equal",
|
||||
|
||||
"Backslash",
|
||||
"BracketLeft",
|
||||
"BracketRight",
|
||||
"Semicolon",
|
||||
"Quote",
|
||||
"Comma",
|
||||
"Period",
|
||||
"Slash",
|
||||
];
|
||||
|
||||
export function isUsingOppositeShift(keycode: string): boolean {
|
||||
export function isUsingOppositeShift(keycode: KeyConverter.Keycode): boolean {
|
||||
if (!leftState && !rightState) {
|
||||
return true;
|
||||
}
|
||||
|
@ -268,46 +145,14 @@ export function isUsingOppositeShift(keycode: string): boolean {
|
|||
return true;
|
||||
}
|
||||
|
||||
const isRight = rightSideKeys.includes(keycode);
|
||||
const isLeft = leftSideKeys.includes(keycode);
|
||||
if (!isRight && !isLeft) {
|
||||
const { leftSide, rightSide } = KeyConverter.keycodeToKeyboardSide(keycode);
|
||||
if (!leftSide && !rightSide) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((leftState && isRight) || (rightState && isLeft)) {
|
||||
if ((leftState && rightSide) || (rightState && leftSide)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function layoutKeyToKeycode(
|
||||
key: string,
|
||||
layout: JSONData.Layout
|
||||
): string | undefined {
|
||||
const rows: string[][] = Object.values(layout.keys);
|
||||
|
||||
const rowIndex = rows.findIndex((row) => row.find((k) => k.includes(key)));
|
||||
const row = rows[rowIndex];
|
||||
if (row === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const keyIndex = row.findIndex((k) => k.includes(key));
|
||||
if (keyIndex === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
let keycode = qwertyKeycodeKeymap[rowIndex]?.[keyIndex];
|
||||
if (layout.type === "iso") {
|
||||
if (rowIndex === 2 && keyIndex === 11) {
|
||||
keycode = "Backslash";
|
||||
} else if (rowIndex === 3 && keyIndex === 0) {
|
||||
keycode = "IntlBackslash";
|
||||
} else if (rowIndex === 3) {
|
||||
keycode = qwertyKeycodeKeymap[3]?.[keyIndex - 1];
|
||||
}
|
||||
}
|
||||
|
||||
return keycode;
|
||||
}
|
||||
|
|
282
frontend/src/ts/utils/key-converter.ts
Normal file
282
frontend/src/ts/utils/key-converter.ts
Normal file
|
@ -0,0 +1,282 @@
|
|||
import * as JSONData from "../utils/json-data";
|
||||
|
||||
export type Keycode =
|
||||
| "Backquote"
|
||||
| "Digit1"
|
||||
| "Digit2"
|
||||
| "Digit3"
|
||||
| "Digit4"
|
||||
| "Digit5"
|
||||
| "Digit6"
|
||||
| "Digit7"
|
||||
| "Digit8"
|
||||
| "Digit9"
|
||||
| "Digit0"
|
||||
| "Minus"
|
||||
| "Equal"
|
||||
| "KeyQ"
|
||||
| "KeyW"
|
||||
| "KeyE"
|
||||
| "KeyR"
|
||||
| "KeyT"
|
||||
| "KeyY"
|
||||
| "KeyU"
|
||||
| "KeyI"
|
||||
| "KeyO"
|
||||
| "KeyP"
|
||||
| "BracketLeft"
|
||||
| "BracketRight"
|
||||
| "Backslash"
|
||||
| "KeyA"
|
||||
| "KeyS"
|
||||
| "KeyD"
|
||||
| "KeyF"
|
||||
| "KeyG"
|
||||
| "KeyH"
|
||||
| "KeyJ"
|
||||
| "KeyK"
|
||||
| "KeyL"
|
||||
| "Semicolon"
|
||||
| "Quote"
|
||||
| "KeyZ"
|
||||
| "KeyX"
|
||||
| "KeyC"
|
||||
| "KeyV"
|
||||
| "KeyB"
|
||||
| "KeyN"
|
||||
| "KeyM"
|
||||
| "Comma"
|
||||
| "Period"
|
||||
| "Slash"
|
||||
| "Space"
|
||||
| "ShiftLeft"
|
||||
| "IntlBackslash"
|
||||
| "ShiftRight"
|
||||
| "ArrowUp"
|
||||
| "ArrowLeft"
|
||||
| "ArrowDown"
|
||||
| "ArrowRight"
|
||||
| "NumpadMultiply"
|
||||
| "NumpadSubtract"
|
||||
| "NumpadAdd"
|
||||
| "NumpadDecimal"
|
||||
| "NumpadEqual"
|
||||
| "NumpadDivide"
|
||||
| "Numpad0"
|
||||
| "Numpad1"
|
||||
| "Numpad2"
|
||||
| "Numpad3"
|
||||
| "Numpad4"
|
||||
| "Numpad5"
|
||||
| "Numpad6"
|
||||
| "Numpad7"
|
||||
| "Numpad8"
|
||||
| "Numpad9"
|
||||
| "NumpadEnter"
|
||||
| "Enter"
|
||||
| "Backspace";
|
||||
|
||||
const qwertyKeycodeKeymap: Keycode[][] = [
|
||||
[
|
||||
"Backquote",
|
||||
"Digit1",
|
||||
"Digit2",
|
||||
"Digit3",
|
||||
"Digit4",
|
||||
"Digit5",
|
||||
"Digit6",
|
||||
"Digit7",
|
||||
"Digit8",
|
||||
"Digit9",
|
||||
"Digit0",
|
||||
"Minus",
|
||||
"Equal",
|
||||
],
|
||||
[
|
||||
"KeyQ",
|
||||
"KeyW",
|
||||
"KeyE",
|
||||
"KeyR",
|
||||
"KeyT",
|
||||
"KeyY",
|
||||
"KeyU",
|
||||
"KeyI",
|
||||
"KeyO",
|
||||
"KeyP",
|
||||
"BracketLeft",
|
||||
"BracketRight",
|
||||
"Backslash",
|
||||
],
|
||||
[
|
||||
"KeyA",
|
||||
"KeyS",
|
||||
"KeyD",
|
||||
"KeyF",
|
||||
"KeyG",
|
||||
"KeyH",
|
||||
"KeyJ",
|
||||
"KeyK",
|
||||
"KeyL",
|
||||
"Semicolon",
|
||||
"Quote",
|
||||
],
|
||||
[
|
||||
"KeyZ",
|
||||
"KeyX",
|
||||
"KeyC",
|
||||
"KeyV",
|
||||
"KeyB",
|
||||
"KeyN",
|
||||
"KeyM",
|
||||
"Comma",
|
||||
"Period",
|
||||
"Slash",
|
||||
],
|
||||
["Space"],
|
||||
];
|
||||
|
||||
const leftSideKeys: Keycode[] = [
|
||||
"Backquote",
|
||||
"Digit1",
|
||||
"Digit2",
|
||||
"Digit3",
|
||||
"Digit4",
|
||||
"Digit5",
|
||||
"Digit6",
|
||||
|
||||
"KeyQ",
|
||||
"KeyW",
|
||||
"KeyE",
|
||||
"KeyR",
|
||||
"KeyT",
|
||||
"KeyY",
|
||||
|
||||
"KeyA",
|
||||
"KeyS",
|
||||
"KeyD",
|
||||
"KeyF",
|
||||
"KeyG",
|
||||
|
||||
"ShiftLeft",
|
||||
"IntlBackslash",
|
||||
"KeyZ",
|
||||
"KeyX",
|
||||
"KeyC",
|
||||
"KeyV",
|
||||
"KeyB",
|
||||
|
||||
"Space",
|
||||
];
|
||||
|
||||
const rightSideKeys: Keycode[] = [
|
||||
"Digit6",
|
||||
"Digit7",
|
||||
"Digit8",
|
||||
"Digit9",
|
||||
"Digit0",
|
||||
"Minus",
|
||||
"Equal",
|
||||
"Backspace",
|
||||
|
||||
"KeyY",
|
||||
"KeyU",
|
||||
"KeyI",
|
||||
"KeyO",
|
||||
"KeyP",
|
||||
"BracketLeft",
|
||||
"BracketRight",
|
||||
"Backslash",
|
||||
|
||||
"KeyH",
|
||||
"KeyJ",
|
||||
"KeyK",
|
||||
"KeyL",
|
||||
"Semicolon",
|
||||
"Quote",
|
||||
"Enter",
|
||||
|
||||
"KeyB",
|
||||
"KeyN",
|
||||
"KeyM",
|
||||
"Comma",
|
||||
"Period",
|
||||
"Slash",
|
||||
"ShiftRight",
|
||||
|
||||
"ArrowUp",
|
||||
"ArrowLeft",
|
||||
"ArrowDown",
|
||||
"ArrowRight",
|
||||
|
||||
"NumpadMultiply",
|
||||
"NumpadSubtract",
|
||||
"NumpadAdd",
|
||||
"NumpadDecimal",
|
||||
"NumpadEqual",
|
||||
"NumpadDivide",
|
||||
"Numpad0",
|
||||
"Numpad1",
|
||||
"Numpad2",
|
||||
"Numpad3",
|
||||
"Numpad4",
|
||||
"Numpad5",
|
||||
"Numpad6",
|
||||
"Numpad7",
|
||||
"Numpad8",
|
||||
"Numpad9",
|
||||
"NumpadEnter",
|
||||
|
||||
"Space",
|
||||
];
|
||||
|
||||
/**
|
||||
* Converts a key to a keycode based on a layout
|
||||
* @param key Key to convert (e.g., "a")
|
||||
* @param layout Layout object from our JSON data (e.g., `layouts["qwerty"]`)
|
||||
* @returns Keycode location of the key (e.g., "KeyA")
|
||||
*/
|
||||
export function layoutKeyToKeycode(
|
||||
key: string,
|
||||
layout: JSONData.Layout
|
||||
): Keycode | undefined {
|
||||
const rows: string[][] = Object.values(layout.keys);
|
||||
|
||||
const rowIndex = rows.findIndex((row) => row.find((k) => k.includes(key)));
|
||||
const row = rows[rowIndex];
|
||||
if (row === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const keyIndex = row.findIndex((k) => k.includes(key));
|
||||
if (keyIndex === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
let keycode = qwertyKeycodeKeymap[rowIndex]?.[keyIndex];
|
||||
if (layout.type === "iso") {
|
||||
if (rowIndex === 2 && keyIndex === 11) {
|
||||
keycode = "Backslash";
|
||||
} else if (rowIndex === 3 && keyIndex === 0) {
|
||||
keycode = "IntlBackslash";
|
||||
} else if (rowIndex === 3) {
|
||||
keycode = qwertyKeycodeKeymap[3]?.[keyIndex - 1];
|
||||
}
|
||||
}
|
||||
|
||||
return keycode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a keycode to a keyboard side. Can return true for both sides if the key is in the location KeyY, KeyB or Space.
|
||||
* @param keycode Keycode to convert (e.g., "KeyA")
|
||||
* @returns Object with leftSide and rightSide booleans
|
||||
*/
|
||||
export function keycodeToKeyboardSide(keycode: Keycode): {
|
||||
leftSide: boolean;
|
||||
rightSide: boolean;
|
||||
} {
|
||||
const left = leftSideKeys.includes(keycode);
|
||||
const right = rightSideKeys.includes(keycode);
|
||||
|
||||
return { leftSide: left, rightSide: right };
|
||||
}
|
Loading…
Add table
Reference in a new issue