mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2025-10-06 05:26:54 +08:00
impr(keymap): dynamic legends now show alt layer if supported by the layout
This commit is contained in:
parent
6513e6481a
commit
cfc810a7d8
4 changed files with 159 additions and 108 deletions
|
@ -9,6 +9,10 @@ import * as Hangul from "hangul-js";
|
|||
import * as Notifications from "../elements/notifications";
|
||||
import * as ActivePage from "../states/active-page";
|
||||
import * as TestWords from "../test/test-words";
|
||||
import { capsState } from "../test/caps-warning";
|
||||
import * as ShiftTracker from "../test/shift-tracker";
|
||||
import * as AltTracker from "../test/alt-tracker";
|
||||
import * as KeyConverter from "../utils/key-converter";
|
||||
|
||||
const stenoKeys: JSONData.Layout = {
|
||||
keymapShowTopRow: true,
|
||||
|
@ -402,6 +406,112 @@ export async function refresh(
|
|||
}
|
||||
}
|
||||
|
||||
const isMacLike = /Mac|iPod|iPhone|iPad/.test(navigator.platform);
|
||||
const symbolsPattern = /^[^\p{L}\p{N}]{1}$/u;
|
||||
type KeymapLegendStates = [letters: 0 | 1 | 2 | 3, symbols: 0 | 1 | 2 | 3];
|
||||
let keymapLegendStates: KeymapLegendStates = [0, 0];
|
||||
|
||||
function getLegendStates(): KeymapLegendStates | undefined {
|
||||
// MacOS has different CapsLock and Shift logic than other operating systems
|
||||
// Windows and Linux only capitalize letters if either Shift OR CapsLock are
|
||||
// pressed, but not both at once.
|
||||
// MacOS instead capitalizes when either or both are pressed,
|
||||
// so we have to check for that.
|
||||
const shiftState = ShiftTracker.leftState || ShiftTracker.rightState;
|
||||
const altState = AltTracker.leftState || AltTracker.rightState;
|
||||
|
||||
const osDependentLettersState = isMacLike
|
||||
? shiftState || capsState
|
||||
: shiftState !== capsState;
|
||||
|
||||
const lettersState = (osDependentLettersState ? 1 : 0) + (altState ? 2 : 0);
|
||||
const symbolsState = (shiftState ? 1 : 0) + (altState ? 2 : 0);
|
||||
|
||||
const [previousLettersState, previousSymbolsState] = keymapLegendStates;
|
||||
|
||||
if (
|
||||
previousLettersState === lettersState &&
|
||||
previousSymbolsState === symbolsState
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
keymapLegendStates = [
|
||||
lettersState as 0 | 1 | 2 | 3,
|
||||
symbolsState as 0 | 1 | 2 | 3,
|
||||
];
|
||||
return keymapLegendStates;
|
||||
}
|
||||
|
||||
async function updateLegends(): Promise<void> {
|
||||
const states = getLegendStates();
|
||||
if (states === undefined) return;
|
||||
|
||||
const keymapKeys = [...document.getElementsByClassName("keymapKey")].filter(
|
||||
(el) => {
|
||||
const isKeymapKey = el.classList.contains("keymapKey");
|
||||
const isNotSpace = !el.classList.contains("keySpace");
|
||||
|
||||
return isKeymapKey && isNotSpace;
|
||||
}
|
||||
) as HTMLElement[];
|
||||
|
||||
const layoutKeys = keymapKeys.map((el) => el.dataset["key"]);
|
||||
if (layoutKeys.includes(undefined)) return;
|
||||
|
||||
const keys = keymapKeys.map((el) => el.childNodes[1]);
|
||||
|
||||
const [lettersState, symbolsState] = states;
|
||||
|
||||
const layoutName =
|
||||
Config.keymapLayout === "overrideSync"
|
||||
? Config.layout === "default"
|
||||
? "qwerty"
|
||||
: Config.layout
|
||||
: Config.keymapLayout;
|
||||
|
||||
const layout = await JSONData.getLayout(layoutName).catch(() => undefined);
|
||||
if (layout === undefined) {
|
||||
Notifications.add("Failed to load keymap layout", -1);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < layoutKeys.length; i++) {
|
||||
const layoutKey = layoutKeys[i] as string;
|
||||
const key = keys[i];
|
||||
const lowerCaseCharacter = layoutKey[0];
|
||||
const upperCaseCharacter = layoutKey[1];
|
||||
|
||||
if (
|
||||
key === undefined ||
|
||||
layoutKey === undefined ||
|
||||
lowerCaseCharacter === undefined ||
|
||||
upperCaseCharacter === undefined
|
||||
)
|
||||
continue;
|
||||
|
||||
const keyIsSymbol = [lowerCaseCharacter, upperCaseCharacter].some(
|
||||
(character) => symbolsPattern.test(character ?? "")
|
||||
);
|
||||
|
||||
const keycode = KeyConverter.layoutKeyToKeycode(lowerCaseCharacter, layout);
|
||||
if (keycode === undefined) {
|
||||
return;
|
||||
}
|
||||
const oppositeShift = ShiftTracker.isUsingOppositeShift(keycode);
|
||||
|
||||
const state = keyIsSymbol ? symbolsState : lettersState;
|
||||
const characterIndex = oppositeShift ? state : 0;
|
||||
|
||||
//if the character at the index is undefined, try without alt
|
||||
const character =
|
||||
layoutKey[characterIndex] ?? layoutKey[characterIndex - 2];
|
||||
|
||||
key.textContent = character ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
ConfigEvent.subscribe((eventKey, newValue) => {
|
||||
if (eventKey === "layout" && Config.keymapLayout === "overrideSync") {
|
||||
void refresh(Config.keymapLayout);
|
||||
|
@ -427,3 +537,27 @@ KeymapEvent.subscribe((mode, key, correct) => {
|
|||
void flashKey(key, correct);
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on("keydown", (e) => {
|
||||
if (
|
||||
Config.keymapLegendStyle === "dynamic" &&
|
||||
(e.code === "ShiftLeft" ||
|
||||
e.code === "ShiftRight" ||
|
||||
e.code === "AltRight" ||
|
||||
e.code === "AltLeft")
|
||||
) {
|
||||
void updateLegends();
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on("keyup", (e) => {
|
||||
if (
|
||||
Config.keymapLegendStyle === "dynamic" &&
|
||||
(e.code === "ShiftLeft" ||
|
||||
e.code === "ShiftRight" ||
|
||||
e.code === "AltRight" ||
|
||||
e.code === "AltLeft")
|
||||
) {
|
||||
void updateLegends();
|
||||
}
|
||||
});
|
||||
|
|
23
frontend/src/ts/test/alt-tracker.ts
Normal file
23
frontend/src/ts/test/alt-tracker.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
export let leftState = false;
|
||||
export let rightState = false;
|
||||
|
||||
$(document).on("keydown", (e) => {
|
||||
if (e.code === "AltLeft") {
|
||||
leftState = true;
|
||||
} else if (e.code === "AltRight") {
|
||||
rightState = true;
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on("keyup", (e) => {
|
||||
if (e.code === "AltLeft") {
|
||||
leftState = false;
|
||||
} else if (e.code === "AltRight") {
|
||||
rightState = false;
|
||||
}
|
||||
});
|
||||
|
||||
export function reset(): void {
|
||||
leftState = false;
|
||||
rightState = false;
|
||||
}
|
|
@ -1,109 +1,9 @@
|
|||
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 symbolsPattern = /^[^\p{L}\p{N}]{1}$/u;
|
||||
|
||||
const isMacLike = /Mac|iPod|iPhone|iPad/.test(navigator.platform);
|
||||
|
||||
let keymapLegendStates: KeymapLegendStates = [false, false];
|
||||
function getLegendStates(): KeymapLegendStates | undefined {
|
||||
const symbolsState = leftState || rightState;
|
||||
// MacOS has different CapsLock and Shift logic than other operating systems
|
||||
// Windows and Linux only capitalize letters if either Shift OR CapsLock are
|
||||
// pressed, but not both at once.
|
||||
// MacOS instead capitalizes when either or both are pressed,
|
||||
// so we have to check for that.
|
||||
const lettersState = isMacLike
|
||||
? symbolsState || capsState
|
||||
: symbolsState !== capsState;
|
||||
|
||||
const [previousLettersState, previousSymbolsState] = keymapLegendStates;
|
||||
|
||||
if (
|
||||
previousLettersState === lettersState &&
|
||||
previousSymbolsState === symbolsState
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (keymapLegendStates = [lettersState, symbolsState]);
|
||||
}
|
||||
|
||||
async function updateKeymapLegendCasing(): Promise<void> {
|
||||
const states = getLegendStates();
|
||||
if (states === undefined) return;
|
||||
|
||||
const keymapKeys = [...document.getElementsByClassName("keymapKey")].filter(
|
||||
(el) => {
|
||||
const isKeymapKey = el.classList.contains("keymapKey");
|
||||
const isNotSpace = !el.classList.contains("keySpace");
|
||||
|
||||
return isKeymapKey && isNotSpace;
|
||||
}
|
||||
) as HTMLElement[];
|
||||
|
||||
const layoutKeys = keymapKeys.map((el) => el.dataset["key"]);
|
||||
if (layoutKeys.includes(undefined)) return;
|
||||
|
||||
const keys = keymapKeys.map((el) => el.childNodes[1]);
|
||||
|
||||
const [lettersState, symbolsState] = states;
|
||||
|
||||
const layoutName =
|
||||
Config.keymapLayout === "overrideSync"
|
||||
? Config.layout === "default"
|
||||
? "qwerty"
|
||||
: Config.layout
|
||||
: Config.keymapLayout;
|
||||
|
||||
const layout = await JSONData.getLayout(layoutName).catch(() => undefined);
|
||||
if (layout === undefined) {
|
||||
Notifications.add("Failed to load keymap layout", -1);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < layoutKeys.length; i++) {
|
||||
const layoutKey = layoutKeys[i] as string;
|
||||
const key = keys[i];
|
||||
const lowerCaseCharacter = layoutKey[0];
|
||||
const upperCaseCharacter = layoutKey[1];
|
||||
|
||||
if (
|
||||
key === undefined ||
|
||||
layoutKey === undefined ||
|
||||
lowerCaseCharacter === undefined ||
|
||||
upperCaseCharacter === undefined
|
||||
)
|
||||
continue;
|
||||
|
||||
const keyIsSymbol = [lowerCaseCharacter, upperCaseCharacter].some(
|
||||
(character) => symbolsPattern.test(character ?? "")
|
||||
);
|
||||
|
||||
const keycode = KeyConverter.layoutKeyToKeycode(lowerCaseCharacter, layout);
|
||||
if (keycode === undefined) {
|
||||
return;
|
||||
}
|
||||
const oppositeShift = isUsingOppositeShift(keycode);
|
||||
|
||||
const state = keyIsSymbol ? symbolsState : lettersState;
|
||||
const capitalize = oppositeShift && state;
|
||||
const keyIndex = Number(capitalize);
|
||||
const character = layoutKey[keyIndex];
|
||||
|
||||
key.textContent = character ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
$(document).on("keydown", (e) => {
|
||||
if (e.code === "ShiftLeft") {
|
||||
leftState = true;
|
||||
|
@ -112,10 +12,6 @@ $(document).on("keydown", (e) => {
|
|||
leftState = false;
|
||||
rightState = true;
|
||||
}
|
||||
|
||||
if (Config.keymapLegendStyle === "dynamic") {
|
||||
void updateKeymapLegendCasing();
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on("keyup", (e) => {
|
||||
|
@ -123,10 +19,6 @@ $(document).on("keyup", (e) => {
|
|||
leftState = false;
|
||||
rightState = false;
|
||||
}
|
||||
|
||||
if (Config.keymapLegendStyle === "dynamic") {
|
||||
void updateKeymapLegendCasing();
|
||||
}
|
||||
});
|
||||
|
||||
export function reset(): void {
|
||||
|
|
|
@ -13,6 +13,7 @@ import * as CustomTextState from "../states/custom-text-name";
|
|||
import * as TestStats from "./test-stats";
|
||||
import * as PractiseWords from "./practise-words";
|
||||
import * as ShiftTracker from "./shift-tracker";
|
||||
import * as AltTracker from "./alt-tracker";
|
||||
import * as Focus from "./focus";
|
||||
import * as Funbox from "./funbox/funbox";
|
||||
import * as Keymap from "../elements/keymap";
|
||||
|
@ -255,6 +256,7 @@ export function restart(options = {} as RestartOptions): void {
|
|||
TestInput.restart();
|
||||
TestInput.corrected.reset();
|
||||
ShiftTracker.reset();
|
||||
AltTracker.reset();
|
||||
Caret.hide();
|
||||
TestState.setActive(false);
|
||||
Replay.stopReplayRecording();
|
||||
|
|
Loading…
Add table
Reference in a new issue