Skip to content

refactor: split key labels in layout files (@fehmer) #6527

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions frontend/__tests__/utils/key-converter.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { readFileSync } from "fs";
import { layoutKeyToKeycode } from "../../src/ts/utils/key-converter";

const isoDvorak = JSON.parse(
readFileSync(
import.meta.dirname + "/../../static/layouts/swedish_dvorak.json",
"utf-8"
)
);
const dvorak = JSON.parse(
readFileSync(
import.meta.dirname + "/../../static/layouts/dvorak.json",
"utf-8"
)
);

describe("key-converter", () => {
describe("layoutKeyToKeycode", () => {
it("handles unknown key", () => {
const keycode = layoutKeyToKeycode("🤷", isoDvorak);

expect(keycode).toBeUndefined();
});
it("handles iso backslash", () => {
const keycode = layoutKeyToKeycode("*", isoDvorak);

expect(keycode).toEqual("Backslash");
});
it("handles iso IntlBackslash", () => {
const keycode = layoutKeyToKeycode("<", isoDvorak);

expect(keycode).toEqual("IntlBackslash");
});
it("handles iso row4", () => {
const keycode = layoutKeyToKeycode("q", isoDvorak);

expect(keycode).toEqual("KeyX");
});
it("handles ansi", () => {
const keycode = layoutKeyToKeycode("q", dvorak);

expect(keycode).toEqual("KeyX");
});
});
});
32 changes: 22 additions & 10 deletions frontend/scripts/json-validation.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,18 @@ function validateOthers() {
return reject(new Error(challengesValidator.errors[0].message));
}

const charDefinitionSchema = {
type: "array",
minItems: 1,
maxItems: 4,
items: { type: "string", minLength: 1, maxLength: 1 },
};
const charDefinitionSchemaRow5 = {
type: "array",
minItems: 1,
maxItems: 2,
items: { type: "string", minLength: 1, maxLength: 1 },
};
//layouts
const layoutsSchema = {
ansi: {
Expand All @@ -145,31 +157,31 @@ function validateOthers() {
properties: {
row1: {
type: "array",
items: { type: "string", minLength: 1, maxLength: 4 },
items: charDefinitionSchema,
minItems: 13,
maxItems: 13,
},
row2: {
type: "array",
items: { type: "string", minLength: 1, maxLength: 4 },
items: charDefinitionSchema,
minItems: 13,
maxItems: 13,
},
row3: {
type: "array",
items: { type: "string", minLength: 1, maxLength: 4 },
items: charDefinitionSchema,
minItems: 11,
maxItems: 11,
},
row4: {
type: "array",
items: { type: "string", minLength: 1, maxLength: 4 },
items: charDefinitionSchema,
minItems: 10,
maxItems: 10,
},
row5: {
type: "array",
items: { type: "string", minLength: 1, maxLength: 2 },
items: charDefinitionSchemaRow5,
minItems: 1,
maxItems: 2,
},
Expand All @@ -189,31 +201,31 @@ function validateOthers() {
properties: {
row1: {
type: "array",
items: { type: "string", minLength: 1, maxLength: 4 },
items: charDefinitionSchema,
minItems: 13,
maxItems: 13,
},
row2: {
type: "array",
items: { type: "string", minLength: 1, maxLength: 4 },
items: charDefinitionSchema,
minItems: 12,
maxItems: 12,
},
row3: {
type: "array",
items: { type: "string", minLength: 1, maxLength: 4 },
items: charDefinitionSchema,
minItems: 12,
maxItems: 12,
},
row4: {
type: "array",
items: { type: "string", minLength: 1, maxLength: 4 },
items: charDefinitionSchema,
minItems: 11,
maxItems: 11,
},
row5: {
type: "array",
items: { type: "string", minLength: 1, maxLength: 2 },
items: charDefinitionSchemaRow5,
minItems: 1,
maxItems: 2,
},
Expand Down
82 changes: 61 additions & 21 deletions frontend/src/ts/elements/keymap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,45 @@ import * as ShiftTracker from "../test/shift-tracker";
import * as AltTracker from "../test/alt-tracker";
import * as KeyConverter from "../utils/key-converter";
import { getActiveFunboxNames } from "../test/funbox/list";
import { areSortedArraysEqual } from "../utils/arrays";

export const keyDataDelimiter = "~~";

const stenoKeys: JSONData.Layout = {
keymapShowTopRow: true,
type: "matrix",
keys: {
row1: [],
row2: ["sS", "tT", "pP", "hH", "**", "fF", "pP", "lL", "tT", "dD"],
row3: ["sS", "kK", "wW", "rR", "**", "rR", "bB", "gG", "sS", "zZ"],
row4: ["aA", "oO", "eE", "uU"],
row2: [
["s", "S"],
["t", "T"],
["p", "P"],
["h", "H"],
["*", "*"],
["f", "F"],
["p", "P"],
["l", "L"],
["t", "T"],
["d", "D"],
],
row3: [
["s", "S"],
["k", "K"],
["w", "W"],
["r", "R"],
["*", "*"],
["r", "R"],
["b", "B"],
["g", "G"],
["s", "S"],
["z", "Z"],
],
row4: [
["a", "A"],
["o", "O"],
["e", "E"],
["u", "U"],
],
row5: [],
},
};
Expand Down Expand Up @@ -115,7 +145,7 @@ export function show(): void {
function buildRow(options: {
layoutData: JSONData.Layout;
rowId: string;
rowKeys: string[];
rowKeys: string[][];
layoutNameDisplayString: string;
showTopRow: boolean;
isMatrix: boolean;
Expand Down Expand Up @@ -189,30 +219,37 @@ function buildRow(options: {
* It is just created for simplicity in the for loop below.
* */
// If only one space, add another
if (rowKeys.length === 1 && rowKeys[0] === " ") {
rowKeys[1] = rowKeys[0];
const isRowEmpty = (row: string[] | undefined): boolean =>
areSortedArraysEqual(row ?? [], [" "]);

if (rowKeys.length === 1 && isRowEmpty(rowKeys[0])) {
rowKeys[1] = rowKeys[0] ?? [];
}
// If only one alpha, add one space and place it on the left
if (rowKeys.length === 1 && rowKeys[0] !== " ") {
rowKeys[1] = " ";
if (rowKeys.length === 1 && !isRowEmpty(rowKeys[0])) {
rowKeys[1] = [" "];
rowKeys.reverse();
}
// If two alphas equal, replace one with a space on the left
if (rowKeys.length > 1 && rowKeys[0] !== " " && rowKeys[0] === rowKeys[1]) {
rowKeys[0] = " ";
if (
rowKeys.length > 1 &&
!isRowEmpty(rowKeys[0]) &&
areSortedArraysEqual(rowKeys[0] as string[], rowKeys[1] as string[])
) {
rowKeys[0] = [" "];
}
const alphas = (v: string): boolean => v !== " ";
const alphas = (v: string[]): boolean => v.some((key) => key !== " ");
hasAlphas = rowKeys.some(alphas);

keysHtml += "<div></div>";

for (let keyId = 0; keyId < rowKeys.length; keyId++) {
const key = rowKeys[keyId] as string;
const key = rowKeys[keyId] as string[];
let keyDisplay = key[0] as string;
if (Config.keymapLegendStyle === "uppercase") {
keyDisplay = keyDisplay.toUpperCase();
}
const keyVisualValue = key.replace('"', "&quot;");
const keyVisualValue = key.map((it) => it.replace('"', "&quot;"));
// these are used to keep grid layout but magically hide keys using opacity:
let side = keyId < 1 ? "left" : "right";
// we won't use this trick for alternate layouts, unless Alice (for rotation):
Expand All @@ -221,7 +258,7 @@ function buildRow(options: {
keysHtml += `<div class="keymapSplitSpacer"></div>`;
r5Grid += "-";
}
if (keyVisualValue === " ") {
if (isRowEmpty(keyVisualValue)) {
keysHtml += `<div class="keymapKey keySpace layoutIndicator ${side}">
<div class="letter" ${letterStyle}>${layoutDisplay}</div>
</div>`;
Expand Down Expand Up @@ -256,7 +293,7 @@ function buildRow(options: {
continue;
}

const key = rowKeys[keyId] as string;
const key = rowKeys[keyId] as string[];
const bump = rowId === "row3" && (keyId === 3 || keyId === 6);
let keyDisplay = key[0] as string;
let letterStyle = "";
Expand All @@ -277,10 +314,11 @@ function buildRow(options: {
hide = ` invisible`;
}

const keyElement = `<div class="keymapKey${hide}" data-key="${key.replace(
'"',
"&quot;"
)}"><span class="letter" ${letterStyle}>${keyDisplay}</span>${
const keyElement = `<div class="keymapKey${hide}" data-key="${key
.map((it) => it.replace('"', "&quot;"))
.join(
keyDataDelimiter
)}"><span class="letter" ${letterStyle}>${keyDisplay}</span>${
bump ? "<div class='bump'></div>" : ""
}</div>`;

Expand Down Expand Up @@ -486,7 +524,9 @@ async function updateLegends(): Promise<void> {
}
) as HTMLElement[];

const layoutKeys = keymapKeys.map((el) => el.dataset["key"]);
const layoutKeys = keymapKeys.map((el) =>
el.dataset["key"]?.split(keyDataDelimiter)
);
if (layoutKeys.includes(undefined)) return;

const keys = keymapKeys.map((el) => el.childNodes[0]);
Expand All @@ -508,7 +548,7 @@ async function updateLegends(): Promise<void> {
}

for (let i = 0; i < layoutKeys.length; i++) {
const layoutKey = layoutKeys[i] as string;
const layoutKey = layoutKeys[i] as string[];
const key = keys[i];
const lowerCaseCharacter = layoutKey[0];
const upperCaseCharacter = layoutKey[1];
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/ts/modals/word-filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import { Language } from "@monkeytype/contracts/schemas/languages";

type FilterPreset = {
display: string;
getIncludeString: (layout: JSONData.Layout) => string[];
getExcludeString: (layout: JSONData.Layout) => string[];
getIncludeString: (layout: JSONData.Layout) => string[][];
getExcludeString: (layout: JSONData.Layout) => string[][];
};

const presets: Record<string, FilterPreset> = {
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/ts/test/layout-emulator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ export async function getCharFromEvent(
): Promise<string | null> {
function emulatedLayoutGetVariant(
event: JQuery.KeyDownEvent | JQuery.KeyUpEvent,
keyVariants: string
keyVariants: string[]
): string | undefined {
let isCapitalized = event.shiftKey;
const altGrIndex = isAltGrPressed && keyVariants.length > 2 ? 2 : 0;
const isNotPunctuation = !isPunctuationPattern.test(
keyVariants.slice(altGrIndex, altGrIndex + 2)
keyVariants.slice(altGrIndex, altGrIndex + 2).join()
);
if (capsState && isNotPunctuation) {
isCapitalized = !event.shiftKey;
Expand Down Expand Up @@ -229,7 +229,7 @@ export async function getCharFromEvent(
}
const charVariant = emulatedLayoutGetVariant(
event,
layoutMap[mapIndex] ?? ""
layoutMap[mapIndex] ?? []
);
if (charVariant !== undefined) {
return charVariant;
Expand Down
10 changes: 5 additions & 5 deletions frontend/src/ts/utils/json-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,11 @@ export const cachedFetchJson = memoizeAsync<string, typeof fetchJson>(
);

export type Keys = {
row1: string[];
row2: string[];
row3: string[];
row4: string[];
row5: string[];
row1: string[][];
row2: string[][];
row3: string[][];
row4: string[][];
row5: string[][];
};

export type Layout = {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/ts/utils/key-converter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ export function layoutKeyToKeycode(
key: string,
layout: JSONData.Layout
): Keycode | undefined {
const rows: string[][] = Object.values(layout.keys);
const rows: string[][][] = Object.values(layout.keys);

const rowIndex = rows.findIndex((row) => row.find((k) => k.includes(key)));
const row = rows[rowIndex];
Expand Down
Loading