From 112a34ccabbf01999cd3c1ef7f3264f3b52b2f2a Mon Sep 17 00:00:00 2001
From: Paolo Brasolin <paolo.brasolin@eurac.edu>
Date: Wed, 23 Mar 2022 16:09:02 +0100
Subject: [PATCH] feat: #fe working mobile keyboard (also fixes diacritics and
 ligatures)

---
 frontend/src/js/fight_scene.ts |  90 +++++----------------
 frontend/src/js/main.ts        |  24 ------
 frontend/src/js/typewriter.ts  | 143 +++++++++++++++++++++++++++++++++
 3 files changed, 162 insertions(+), 95 deletions(-)
 create mode 100644 frontend/src/js/typewriter.ts

diff --git a/frontend/src/js/fight_scene.ts b/frontend/src/js/fight_scene.ts
index d390b75..58c0019 100644
--- a/frontend/src/js/fight_scene.ts
+++ b/frontend/src/js/fight_scene.ts
@@ -8,6 +8,7 @@ import levenshtein from "damerau-levenshtein";
 
 import * as Types from "../../../backend/src/types";
 import Foe from "./foe";
+import Typewriter from "./typewriter";
 
 interface InputStatus {
   began_at: string | null;
@@ -23,6 +24,7 @@ export default class FightScene extends Phaser.Scene {
   cluesGroup: Phaser.Physics.Arcade.Group;
   beGame: Types.Game;
   inputStatus: InputStatus;
+  typewriter: Typewriter;
 
   constructor() {
     super("fight");
@@ -274,43 +276,22 @@ export default class FightScene extends Phaser.Scene {
       font: "bold 64px Courier",
       color: "#ffffff",
     });
-    this.input.keyboard.on(
-      Phaser.Input.Keyboard.Events.ANY_KEY_DOWN,
-      (event: any) => {
-        if (this.inputStatus.final === "") {
-          this.inputStatus.began_at = new Date().toISOString();
-        }
-        if (LETTERS_KEYCODES.has(event.keyCode)) {
-          this.inputStatus.typed += event.key;
-          this.inputStatus.final += event.key;
-          textEntry.text = this.inputStatus.final;
-        } else if (
-          event.keyCode === Phaser.Input.Keyboard.KeyCodes.BACKSPACE &&
-          this.inputStatus.final.length > 0
-        ) {
-          this.inputStatus.typed += "\b";
-          this.inputStatus.final = this.inputStatus.final.substr(
-            0,
-            this.inputStatus.final.length - 1,
-          );
-          textEntry.text = this.inputStatus.final;
-        } else if (
-          event.keyCode === Phaser.Input.Keyboard.KeyCodes.ENTER &&
-          this.inputStatus.final.length > 0
-        ) {
-          this.inputStatus.typed += "\n";
-          this.inputStatus.ended_at = new Date().toISOString();
-          this.submitTranscription(this.inputStatus);
-          this.inputStatus = {
-            began_at: null,
-            ended_at: null,
-            typed: "",
-            final: "",
-          };
-          textEntry.text = this.inputStatus.final;
-        }
-      },
-    );
+    this.typewriter = new Typewriter();
+    this.typewriter.onSubmit = (inputStatus) => {
+      if (inputStatus.began_at === null) return;
+      if (inputStatus.ended_at === null) return;
+      if (inputStatus.final === "") return;
+      this.submitTranscription({
+        began_at: inputStatus.began_at.toISOString(),
+        ended_at: inputStatus.ended_at.toISOString(),
+        typed: inputStatus.typed,
+        final: inputStatus.final,
+      });
+      textEntry.text = "";
+    };
+    this.typewriter.onChange = (inputStatus) => {
+      textEntry.text = inputStatus.final;
+    };
   }
 }
 
@@ -331,46 +312,13 @@ function createAnim(scene: any, key: any, refKey: any, from: any, to: any) {
   });
 }
 
-const LETTERS_KEYCODES = new Set([
-  Phaser.Input.Keyboard.KeyCodes.SPACE,
-  Phaser.Input.Keyboard.KeyCodes.A,
-  Phaser.Input.Keyboard.KeyCodes.B,
-  Phaser.Input.Keyboard.KeyCodes.C,
-  Phaser.Input.Keyboard.KeyCodes.D,
-  Phaser.Input.Keyboard.KeyCodes.E,
-  Phaser.Input.Keyboard.KeyCodes.F,
-  Phaser.Input.Keyboard.KeyCodes.G,
-  Phaser.Input.Keyboard.KeyCodes.H,
-  Phaser.Input.Keyboard.KeyCodes.I,
-  Phaser.Input.Keyboard.KeyCodes.J,
-  Phaser.Input.Keyboard.KeyCodes.K,
-  Phaser.Input.Keyboard.KeyCodes.L,
-  Phaser.Input.Keyboard.KeyCodes.M,
-  Phaser.Input.Keyboard.KeyCodes.N,
-  Phaser.Input.Keyboard.KeyCodes.O,
-  Phaser.Input.Keyboard.KeyCodes.P,
-  Phaser.Input.Keyboard.KeyCodes.Q,
-  Phaser.Input.Keyboard.KeyCodes.R,
-  Phaser.Input.Keyboard.KeyCodes.S,
-  Phaser.Input.Keyboard.KeyCodes.T,
-  Phaser.Input.Keyboard.KeyCodes.U,
-  Phaser.Input.Keyboard.KeyCodes.V,
-  Phaser.Input.Keyboard.KeyCodes.W,
-  Phaser.Input.Keyboard.KeyCodes.X,
-  Phaser.Input.Keyboard.KeyCodes.Y,
-  Phaser.Input.Keyboard.KeyCodes.Z,
-  219, // ß
-  186, // ü
-  192, // ö
-  222, // ä
-]);
-
 function gameStart(scene: any) {
   spawn(scene);
 }
 
 async function spawn(scene: any) {
   await spawnFoe(scene);
+  return;
   scene.time.now;
   const delay =
     (8 * 1000 * (60 * 1000 - scene.time.now)) / 60 / 1000 + 2 * 1000;
diff --git a/frontend/src/js/main.ts b/frontend/src/js/main.ts
index 05cfa2b..3e0695f 100644
--- a/frontend/src/js/main.ts
+++ b/frontend/src/js/main.ts
@@ -22,27 +22,3 @@ const config = {
 };
 
 new Phaser.Game(config);
-
-import Keyboard from "simple-keyboard";
-
-document.addEventListener("DOMContentLoaded", () => {
-  new Keyboard({
-    theme: "hg-theme-default hg-theme-oetzi",
-    physicalKeyboardHighlight: true,
-    debug: true,
-    layout: {
-      default: [
-        "q w e r t z u i o p \u00FC {bksp}",
-        "a s d f g h j k l \u00F6 \u00E4",
-        "{space} y x c v b n m \u00DF {enter}",
-      ],
-    },
-    display: {
-      "{bksp}": "⟵", // "⌫⟵",
-      "{enter}": "↵", // "⏎↩↵⏎",
-      "{space}": "␣", // "␣",
-    },
-    // onChange: console.log,
-    // onKeyPress: console.log,
-  });
-});
diff --git a/frontend/src/js/typewriter.ts b/frontend/src/js/typewriter.ts
new file mode 100644
index 0000000..60f8957
--- /dev/null
+++ b/frontend/src/js/typewriter.ts
@@ -0,0 +1,143 @@
+import Keyboard from "simple-keyboard";
+import { KeyboardOptions } from "simple-keyboard/build/interfaces";
+
+interface InputStatus {
+  began_at: Date | null;
+  ended_at: Date | null;
+  typed: string;
+  final: string;
+}
+
+enum Key {
+  Space = "{space}",
+  Enter = "{enter}",
+  Backspace = "{backspace}",
+  A = "a",
+  B = "b",
+  C = "c",
+  D = "d",
+  E = "e",
+  F = "f",
+  G = "g",
+  H = "h",
+  I = "i",
+  J = "j",
+  K = "k",
+  L = "l",
+  M = "m",
+  N = "n",
+  O = "o",
+  P = "p",
+  Q = "q",
+  R = "r",
+  S = "s",
+  T = "t",
+  U = "u",
+  V = "v",
+  W = "w",
+  X = "x",
+  Y = "y",
+  Z = "z",
+  Ä = "ä",
+  Ö = "ö",
+  Ü = "ü",
+  ß = "ß",
+}
+
+class Typewriter {
+  inputStatus: InputStatus;
+  keyboard: Keyboard;
+
+  onChange: (inputStatus: InputStatus) => unknown;
+  onSubmit: (inputStatus: InputStatus) => unknown;
+
+  constructor() {
+    this.onChange = () => {};
+    this.onSubmit = () => {};
+
+    this.inputStatus = {
+      began_at: null,
+      ended_at: null,
+      typed: "",
+      final: "",
+    };
+
+    this.keyboard = new Keyboard({
+      // debug: true,
+      physicalKeyboardHighlight: true,
+      physicalKeyboardHighlightPress: true,
+      // autoUseTouchEvents: true,
+      newLineOnEnter: true,
+      disableCaretPositioning: true,
+      layout: {
+        default: [
+          `q w e r t z u i o p ü ${Key.Backspace}`,
+          `a s d f g h j k l ö ä`,
+          `${Key.Space} y x c v b n m ß ${Key.Enter}`,
+        ],
+      },
+      display: {
+        [Key.Backspace]: "⟵", // "⌫⟵",
+        [Key.Enter]: "↵", // "⏎↩↵⏎",
+        [Key.Space]: "␣", // "␣",
+      },
+      onChange: this.keyboardOnChangeHandler.bind(this),
+    } as KeyboardOptions);
+  }
+
+  extractKeyfromEvent(
+    event: KeyboardEvent | PointerEvent | MouseEvent | TouchEvent,
+  ): Key {
+    if (event instanceof KeyboardEvent) {
+      return (
+        {
+          ["Backspace"]: Key.Backspace,
+          ["Enter"]: Key.Enter,
+          [" "]: Key.Space,
+        }[event.key] ?? (event.key as Key)
+      );
+    } else {
+      const element = event.target as HTMLDivElement;
+      return element.dataset.skbtn as Key;
+    }
+  }
+
+  keyboardOnChangeHandler(
+    input: string,
+    event: KeyboardEvent | PointerEvent | MouseEvent | TouchEvent,
+  ) {
+    const key = this.extractKeyfromEvent(event);
+    if (!this.inputStatus.began_at) this.inputStatus.began_at = new Date();
+    if (key === Key.Enter) {
+      this.keyboard.clearInput();
+      this.inputStatus.typed += "\n";
+      this.inputStatus.ended_at = new Date();
+      this.onSubmit(this.inputStatus);
+      this.resetInputStatus();
+    } else if (key === Key.Backspace) {
+      // NOTE: Backspace events are skipped by simple-keyboard if its internal input is empty!
+      this.inputStatus.typed += "\b";
+      this.inputStatus.final = input;
+      this.onChange(this.inputStatus);
+    } else if (key === Key.Space) {
+      this.inputStatus.typed += " ";
+      this.inputStatus.final = input;
+      this.onChange(this.inputStatus);
+    } else {
+      this.inputStatus.typed += key;
+      this.inputStatus.final = input;
+      this.onChange(this.inputStatus);
+    }
+  }
+
+  resetInputStatus() {
+    this.inputStatus = {
+      began_at: null,
+      ended_at: null,
+      typed: "",
+      final: "",
+    };
+  }
+}
+
+export default Typewriter;
-- 
GitLab