vbonnet commited on
Commit
e7e1246
·
verified ·
1 Parent(s): ff88236

Default keyboard labels to QWERTY

Browse files
app/src/ui/keyboard-layout.js CHANGED
@@ -1,9 +1,7 @@
1
  // Controls use physical KeyboardEvent.code positions; getLayoutMap() provides
2
  // the characters printed on those keys for the active layout.
3
- const FALLBACK_LABELS = Object.freeze({ KeyW: "W/Z", KeyA: "A/Q", KeyQ: "Q/A" });
4
-
5
  const MOVEMENT_CODES = ["KeyW", "KeyA", "KeyS", "KeyD"];
6
- const MOVEMENT_FALLBACK_HINT = "arrows or WASD / ZQSD";
7
 
8
  function keycap(value) {
9
  if (typeof value !== "string") return null;
@@ -14,22 +12,27 @@ function keycap(value) {
14
  }
15
 
16
  export function resolveKeycaps(codes, layoutMap) {
 
17
  const labels = {};
18
- const fromLayout = new Set();
19
- for (const code of new Set([...MOVEMENT_CODES, ...codes])) {
20
  let label = null;
21
  try {
22
  label = keycap(layoutMap?.get?.(code));
23
  } catch {
24
  label = null;
25
  }
26
- if (label) fromLayout.add(code);
27
- labels[code] = label ?? FALLBACK_LABELS[code] ?? code.replace(/^Key/, "");
 
 
 
 
28
  }
29
- const moveHint = MOVEMENT_CODES.every((code) => fromLayout.has(code))
30
- ? `arrows or ${MOVEMENT_CODES.map((code) => labels[code]).join("")}`
31
- : MOVEMENT_FALLBACK_HINT;
32
- return { labels, moveHint };
33
  }
34
 
35
  export async function readLayoutMap(nav = globalThis.navigator) {
 
1
  // Controls use physical KeyboardEvent.code positions; getLayoutMap() provides
2
  // the characters printed on those keys for the active layout.
 
 
3
  const MOVEMENT_CODES = ["KeyW", "KeyA", "KeyS", "KeyD"];
4
+ const MOVEMENT_FALLBACK_HINT = "arrows or WASD";
5
 
6
  function keycap(value) {
7
  if (typeof value !== "string") return null;
 
12
  }
13
 
14
  export function resolveKeycaps(codes, layoutMap) {
15
+ const required = [...new Set([...MOVEMENT_CODES, ...codes])];
16
  const labels = {};
17
+ let complete = true;
18
+ for (const code of required) {
19
  let label = null;
20
  try {
21
  label = keycap(layoutMap?.get?.(code));
22
  } catch {
23
  label = null;
24
  }
25
+ if (!label) complete = false;
26
+ labels[code] = label;
27
+ }
28
+ if (!complete) {
29
+ for (const code of required) labels[code] = code.replace(/^Key/, "");
30
+ return { labels, moveHint: MOVEMENT_FALLBACK_HINT };
31
  }
32
+ return {
33
+ labels,
34
+ moveHint: `arrows or ${MOVEMENT_CODES.map((code) => labels[code]).join("")}`,
35
+ };
36
  }
37
 
38
  export async function readLayoutMap(nav = globalThis.navigator) {
app/test/keyboard-layout.test.js CHANGED
@@ -31,35 +31,39 @@ test("resolves caps and the movement hint from the active layout", () => {
31
  assert.equal(dvorak.moveHint, "arrows or ,AOE");
32
  });
33
 
34
- test("falls back to labels naming both layouts without a map", () => {
35
  assert.deepEqual(resolveKeycaps(CODES, null), {
36
- labels: { KeyW: "W/Z", KeyA: "A/Q", KeyS: "S", KeyD: "D", KeyQ: "Q/A", KeyE: "E" },
37
- moveHint: "arrows or WASD / ZQSD",
38
  });
39
  assert.equal(resolveKeycaps(["KeyX"], null).labels.KeyX, "X");
40
  });
41
 
42
- test("keeps the inclusive hint when the map misses a movement key", () => {
 
 
 
 
 
43
  const partial = new Map([["KeyW", "z"], ["KeyA", "q"], ["KeyS", "s"]]);
44
- const { labels, moveHint } = resolveKeycaps(CODES, partial);
45
- assert.equal(labels.KeyW, "Z");
46
- assert.equal(labels.KeyD, "D");
47
- assert.equal(moveHint, "arrows or WASD / ZQSD");
 
 
48
  });
49
 
50
  test("rejects layout values that cannot sit on a keycap", () => {
51
  const junk = new Map(
52
  Object.entries({ KeyQ: "", KeyE: " ", KeyW: "Dead", KeyA: "́", KeyS: 7 }),
53
  );
54
- const { labels } = resolveKeycaps(CODES, junk);
55
- assert.deepEqual(labels, {
56
- KeyW: "W/Z", KeyA: "A/Q", KeyS: "S", KeyD: "D", KeyQ: "Q/A", KeyE: "E",
57
- });
58
  });
59
 
60
  test("a throwing map cannot break the menu", () => {
61
  const hostile = { get: () => { throw new Error("nope"); } };
62
- assert.equal(resolveKeycaps(CODES, hostile).labels.KeyQ, "Q/A");
63
  });
64
 
65
  test("readLayoutMap yields null when the API is absent or refuses", async () => {
 
31
  assert.equal(dvorak.moveHint, "arrows or ,AOE");
32
  });
33
 
34
+ test("falls back to QWERTY labels without a map", () => {
35
  assert.deepEqual(resolveKeycaps(CODES, null), {
36
+ labels: { KeyW: "W", KeyA: "A", KeyS: "S", KeyD: "D", KeyQ: "Q", KeyE: "E" },
37
+ moveHint: "arrows or WASD",
38
  });
39
  assert.equal(resolveKeycaps(["KeyX"], null).labels.KeyX, "X");
40
  });
41
 
42
+ const ALL_QWERTY = {
43
+ labels: { KeyW: "W", KeyA: "A", KeyS: "S", KeyD: "D", KeyQ: "Q", KeyE: "E" },
44
+ moveHint: "arrows or WASD",
45
+ };
46
+
47
+ test("falls back entirely to QWERTY when the map misses a movement key", () => {
48
  const partial = new Map([["KeyW", "z"], ["KeyA", "q"], ["KeyS", "s"]]);
49
+ assert.deepEqual(resolveKeycaps(CODES, partial), ALL_QWERTY);
50
+ });
51
+
52
+ test("falls back entirely to QWERTY when the map misses a requested key", () => {
53
+ const partial = new Map([...AZERTY].filter(([code]) => code !== "KeyE"));
54
+ assert.deepEqual(resolveKeycaps(CODES, partial), ALL_QWERTY);
55
  });
56
 
57
  test("rejects layout values that cannot sit on a keycap", () => {
58
  const junk = new Map(
59
  Object.entries({ KeyQ: "", KeyE: " ", KeyW: "Dead", KeyA: "́", KeyS: 7 }),
60
  );
61
+ assert.deepEqual(resolveKeycaps(CODES, junk), ALL_QWERTY);
 
 
 
62
  });
63
 
64
  test("a throwing map cannot break the menu", () => {
65
  const hostile = { get: () => { throw new Error("nope"); } };
66
+ assert.deepEqual(resolveKeycaps(CODES, hostile), ALL_QWERTY);
67
  });
68
 
69
  test("readLayoutMap yields null when the API is absent or refuses", async () => {