diff --git a/frontend/package.json b/frontend/package.json
index f1135e5..2ad2c5d 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -15,7 +15,5 @@
"webpack": "^5.61.0",
"webpack-cli": "^4.9.1"
},
- "dependencies": {
- "simple-keyboard": "^3.3.16"
- }
+ "dependencies": {}
}
diff --git a/frontend/src/Banner.js b/frontend/src/Banner.js
index d9f1f6d..9443f6a 100644
--- a/frontend/src/Banner.js
+++ b/frontend/src/Banner.js
@@ -21,7 +21,7 @@ class Banner {
return; // Already mounted
this.element = document.createRange().createContextualFragment(`
-
+
diff --git a/frontend/src/Keyboard.js b/frontend/src/Keyboard.js
index 32435a9..f2b8e4b 100644
--- a/frontend/src/Keyboard.js
+++ b/frontend/src/Keyboard.js
@@ -1,90 +1,122 @@
-import SimpleKeyboard from "simple-keyboard";
-
class KeyboardController {
constructor(connection) {
this.connection = connection;
- this.keyboard = null;
- this.keyboardDiv = null;
+ this.container = null;
+
+ this.layouts = {
+ default: [
+ ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"],
+ ["q", "w", "e", "r", "t", "y", "u", "i", "o", "p"],
+ ["a", "s", "d", "f", "g", "h", "j", "k", "l"],
+ ["🄰", "z", "x", "c", "v", "b", "n", "m", ".", "⌦"],
+ ["🔢", " ", "↵"]
+ ],
+ uppercase: [
+ ["!", "@", "#", "$", "%", "^", "&", "*", "(", ")"],
+ ["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"],
+ ["A", "S", "D", "F", "G", "H", "J", "K", "L"],
+ ["🅰", "Z", "X", "C", "V", "B", "N", "M", ",", "⌦"],
+ ["🔢", " ", "↵"]
+ ],
+ symbols: [
+ ["!", "@", "#", "$", "%", "^", "&", "*", "(", ")"],
+ ["[", "]", "{", "}", ";", ":", "'", "\"", ",", "<"],
+ [".", ">", "-", "_", "=", "+", "/", "?", "\\"],
+ ["🄰", "|", "`", "~", "⌦"],
+ ["🔤", " ", "↵"]
+ ]
+ }
}
_sendKeyPress(l) {
this.connection.sendMessage("k", [l]);
}
+ makeKeyboardForLayout(layout) {
+ queueMicrotask(() => {
+ while (this.container.firstChild) {
+ this.container.removeChild(this.container.lastChild);
+ }
+
+ const rowsElement = document.createElement("div");
+ rowsElement.classList.add("Keyboard-rows");
+
+ for (let row = 0; row < layout.length; row++) {
+ const rowElement = document.createElement("div");
+ rowElement.classList.add("Keyboard-row");
+ for (let col = 0; col < layout[row].length; col++) {
+ const key = layout[row][col];
+
+ const colElement = document.createElement("button");
+ colElement.classList.add("Keyboard-button");
+ colElement.innerHTML = key;
+ colElement.addEventListener("click", (event) => {
+ this.handleKeypress(key);
+ });
+
+ rowElement.appendChild(colElement);
+ }
+ rowsElement.appendChild(rowElement);
+ }
+
+ this.container.appendChild(rowsElement);
+ });
+ }
+
+ handleKeypress(key) {
+ switch (key) {
+ case "🄰": {
+ this.makeKeyboardForLayout(this.layouts.uppercase);
+ break;
+ }
+ case "🅰": {
+ this.makeKeyboardForLayout(this.layouts.default);
+ break;
+ }
+ case "🔢": {
+ this.makeKeyboardForLayout(this.layouts.symbols);
+ break;
+ }
+ case "🔤": {
+ this.makeKeyboardForLayout(this.layouts.default);
+ break;
+ }
+ case " ": {
+ this._sendKeyPress("{space}");
+ break;
+ }
+ case "⌦": {
+ this._sendKeyPress("{backspace}");
+ break;
+ }
+ case "↵": {
+ this._sendKeyPress("{ent}");
+ break;
+ }
+ default: {
+ this._sendKeyPress(key);
+ break;
+ }
+ }
+ }
+
mountOn(element) {
- if (this.keyboardDiv)
+ if (this.container)
return; // Already mounted;
- this.keyboardDiv = document.createElement("div");
- this.keyboardDiv.classList.add("keyboard");
- element.appendChild(this.keyboardDiv);
+ this.container = document.createElement("div");
+ this.container.classList.add("Keyboard");
+ element.appendChild(this.container);
- this.keyboard = new SimpleKeyboard(this.keyboardDiv, {
- onKeyPress: this.onKeyPress.bind(this),
- mergeDisplay: true,
- layoutName: "default",
- // refer to: https://hodgef.com/simple-keyboard/demos/?d=mobile
- layout: {
- default: [
- "q w e r t y u i o p",
- "a s d f g h j k l",
- "{shift} z x c v b n m {backspace}",
- "{numbers} {space} {ent}"
- ],
- shift: [
- "Q W E R T Y U I O P",
- "A S D F G H J K L",
- "{shift} Z X C V B N M {backspace}",
- "{numbers} {space} {ent}"
- ],
- numbers: ["1 2 3", "4 5 6", "7 8 9", "{abc} 0 {backspace}"]
- },
- display: {
- "{numbers}": "123",
- "{ent}": "return",
- "{escape}": "esc ⎋",
- "{tab}": "tab ⇥",
- "{backspace}": "⌫",
- "{capslock}": "caps lock ⇪",
- "{shift}": "⇧",
- "{controlleft}": "ctrl ⌃",
- "{controlright}": "ctrl ⌃",
- "{altleft}": "alt ⌥",
- "{altright}": "alt ⌥",
- "{metaleft}": "cmd ⌘",
- "{metaright}": "cmd ⌘",
- "{abc}": "ABC"
- }
- });
+ this.makeKeyboardForLayout(this.layouts.default);
}
unmount() {
- if (!this.keyboardDiv)
+ if (!this.container)
return; // Not mounted - don't do anything
- this.keyboardDiv.parentElement.removeChild(this.keyboardDiv);
- this.keyboardDiv = null;
- }
-
- onKeyPress(button) {
- if (button === "{shift}" || button === "{lock}")
- return this.handleShift();
- if (button === "{numbers}" || button === "{abc}")
- return this.handleNumbers();
-
- this._sendKeyPress(button);
- }
-
- handleShift() {
- this.keyboard.setOptions({
- layoutName: this.keyboard.options.layoutName === "default" ? "shift" : "default"
- });
- }
-
- handleNumbers() {
- this.keyboard.setOptions({
- layoutName: this.keyboard.options.layoutName !== "numbers" ? "numbers" : "default"
- });
+ this.container.parentElement.removeChild(this.container);
+ this.container = null;
}
}
diff --git a/frontend/src/LoginPrompt.js b/frontend/src/LoginPrompt.js
index 72ab2c9..4456258 100644
--- a/frontend/src/LoginPrompt.js
+++ b/frontend/src/LoginPrompt.js
@@ -8,7 +8,7 @@ class LoginPrompt {
return; // Already mounted
this.element = document.createRange().createContextualFragment(`
-
+
Login
You need to enter the login code before you can start controlling your device.
diff --git a/frontend/src/Touchpad.js b/frontend/src/Touchpad.js
index 3c8d2cb..0162ba5 100644
--- a/frontend/src/Touchpad.js
+++ b/frontend/src/Touchpad.js
@@ -1,5 +1,5 @@
const HOLDING_THRESHOLD_MS = 300;
-const SCROLL_X_DAMPENING = 0.03;
+const SCROLL_X_DAMPENING = 0.02;
const SCROLL_Y_DAMPENING = 0.09;
class TouchpadController {
@@ -65,25 +65,26 @@ class TouchpadController {
return; // Already mounted
this.touchpadDiv = document.createElement("div");
- this.touchpadDiv.classList.add("touchpad");
- element.appendChild(this.touchpadDiv);
+ this.touchpadDiv.classList.add("Touchpad");
this.touchpadDiv.addEventListener("touchmove", this.onTouchMove.bind(this));
this.touchpadDiv.addEventListener("touchend", this.onTouchEnd.bind(this));
this.touchpadDiv.addEventListener("touchstart", this.onTouchStart.bind(this));
+
+ element.appendChild(this.touchpadDiv);
}
unmount() {
if (!this.touchpadDiv)
return; // Not mounted - don't do anything
- this.touchpadDiv.parentElement.removeChild(this.touchpadDiv);
- this.touchpadDiv = null;
+ this.touchpadDiv.removeEventListener("touchmove", this.onTouchMove.bind(this));
+ this.touchpadDiv.removeEventListener("touchend", this.onTouchEnd.bind(this));
+ this.touchpadDiv.removeEventListener("touchstart", this.onTouchStart.bind(this));
}
onTouchMove(event) {
const touches = event.changedTouches;
- event.preventDefault();
const targetTouch = touches[0];
@@ -125,7 +126,13 @@ class TouchpadController {
}
if (shouldMarkTouchesAsMoved) {
for (let i = 0; i < touches.length; i++) {
- this.ongoingTouches[touches[i].identifier].hasMoved = true;
+ const touch = this.ongoingTouches[touches[i].identifier];
+ touch.hasMoved = true;
+
+ if (touch.indicatorElement) {
+ touch.indicatorElement.style.top = `${targetTouch.pageY}px`;
+ touch.indicatorElement.style.left = `${targetTouch.pageX}px`;
+ }
}
}
}
@@ -150,6 +157,14 @@ class TouchpadController {
// remove all ended touches
for (let i = 0; i < changedTouches.length; i++) {
const touch = changedTouches[i];
+ const knownTouch = this.ongoingTouches[changedTouches[0].identifier];
+
+ if (knownTouch && knownTouch.indicatorElement) {
+ this.touchpadDiv.removeChild(knownTouch.indicatorElement);
+ knownTouch.indicatorElement = null;
+ delete knownTouch.indicatorElement;
+ }
+
this.ongoingTouches[touch.identifier] = null;
delete this.ongoingTouches[touch.identifier];
}
@@ -177,12 +192,21 @@ class TouchpadController {
for (let i = 0; i < changedTouches.length; i++) {
const touch = changedTouches[i];
+ let indicatorElement = this.ongoingTouches[touch.identifier] ? this.ongoingTouches[touch.identifier].indicatorElement : null;
+ if (!indicatorElement) {
+ indicatorElement = document.createElement("div");
+ indicatorElement.classList.add("Touchpad-touch-indicator");
+ indicatorElement.style.top = `${touch.clientY}px`;
+ indicatorElement.style.left = `${touch.clientX}px`;
+ this.touchpadDiv.appendChild(indicatorElement);
+ }
this.ongoingTouches[touch.identifier] = {
identifier: touch.identifier,
clientX: touch.clientX,
clientY: touch.clientY,
hasMoved: false,
- gestured: []
+ gestured: [],
+ indicatorElement
};
}
}
diff --git a/frontend/src/index.js b/frontend/src/index.js
index 7ad9d61..113e535 100644
--- a/frontend/src/index.js
+++ b/frontend/src/index.js
@@ -2,4 +2,4 @@ import "simple-keyboard/build/css/index.css";
import "./styles/main.css";
import App from "./App";
-new App(document.body);
+window.__app = new App(document.body);
diff --git a/frontend/src/styles/main.css b/frontend/src/styles/main.css
index daba610..cd6797b 100644
--- a/frontend/src/styles/main.css
+++ b/frontend/src/styles/main.css
@@ -1,29 +1,55 @@
:root {
--body-bg-color: #2e2e2e;
+ --body-bg-color-accent: hsl(0, 0%, 25%);
--accent-bg-color: #d4d3d3;
--accent-color: #949494;
--grayed-text-color: #949494;
--button-accent-color: #3d3d3d;
--selected-bg-color: #2e2e2e;
--hover-bg-color: #4d4d4d;
- --card-border-radius: 1rem;
- --button-border-radius: 0.5rem;
+ --card-border-radius: 1em;
+ --button-border-radius: 0.5em;
--main-font-weight: 500;
--fonts-regular: "Noto Sans", "Liberation Sans", sans-serif;
}
-body {
+* {
+ box-sizing: border-box;
+}
+
+html, body {
font-weight: var(--main-font-weight);
font-family: var(--fonts-regular);
background-color: var(--body-bg-color);
color: var(--accent-bg-color);
- overflow-x: none;
+ overflow: hidden;
margin: 0;
- padding: 12px;
+ padding: 0;
+ height: 100%;
+ width: 100%;
}
-.card {
+body {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: flex-end;
+ background: var(--body-bg-color);
+ background-image: radial-gradient(var(--body-bg-color-accent) 1.4px, transparent 0);
+ background-size: 35px 35px;
+}
+
+/* util */
+
+.full-width {
+ width: 100%;
+}
+
+/* Card */
+
+.Keyboard,
+.Card {
margin: 6px;
padding: 8px;
background: var(--accent-bg-color);
@@ -31,56 +57,32 @@ body {
border-radius: var(--card-border-radius);
}
-.card.inner-card {
- margin: 4px;
- margin-bottom: 28px;
- padding: 18px;
- border: solid var(--accent-color) 1px;
-}
-
-.card.layout-card {
- margin: 36px auto;
- padding: 26px;
- width: 80%;
+.Keyboard,
+.Card-ui-bottom {
+ padding: 14px;
+ width: 100%;
+ max-width: 610px;
box-shadow: 0 0 28px 3px rgba(0, 0, 0, 0.40);
+ margin: 0;
+ border-radius: var(--card-border-radius) var(--card-border-radius) 0 0;
+ padding-top: 16px;
}
-.card.small-card {
- margin: 36px auto;
- padding: 26px;
- width: 350px;
- box-shadow: 0 0 28px 3px rgba(0, 0, 0, 0.40);
-}
-
-@media screen and (max-width: 768px) {
- .card.small-card {
- width: 80%;
- }
-}
-
-.button-default {
- display: block;
- box-sizing: border-box;
- padding: 12px;
- margin: 4px;
- text-decoration: none;
- border: none;
- background-color: var(--accent-bg-color);
- border-radius: var(--button-border-radius);
- font-size: 18px;
- white-space: nowrap;
- color: var(--button-accent-color);
- cursor: pointer;
- outline: none;
- min-width: 50px;
+.Card-centered-text {
text-align: center;
}
-.full-width {
- width: 100%;
-}
+/* button */
-.center-text {
+.button-default {
+ display: block;
+ padding: 12px;
+ margin: 4px;
+ border: none;
+ background-color: var(--accent-bg-color);
+ border-radius: var(--button-border-radius);
+ color: var(--button-accent-color);
+ min-width: 50px;
text-align: center;
}
@@ -94,6 +96,8 @@ body {
background-color: var(--selected-bg-color);
}
+/* input */
+
.input {
display: block;
box-sizing: border-box;
@@ -104,36 +108,51 @@ body {
padding: 12px;
}
-.touchpad {
- height: clamp(5rem, 30rem, 50vh);
+/* Touchpad */
+
+.Touchpad {
width: 100%;
- background-color: var(--accent-bg-color);
- border-radius: var(--card-border-radius);
+ height: 100%;
}
-/* for virtual keyboard */
+.Touchpad-touch-indicator {
+ pointer-events: none;
+ position: absolute;
+ width: 28px;
+ height: 28px;
+ border-radius: 50%;
+ background-color: var(--accent-bg-color);
+}
-.hg-theme-default {
- font-weight: var(--main-font-weight);
+/* Keyboard */
+
+.keyboard-rows {
+ border-radius: 12px;
+ display: flex;
+ flex-direction: column;
+}
+
+.Keyboard-row {
+ display: flex;
+ flex-direction: row;
+ flex-grow: 1;
+}
+
+.Keyboard-button {
font-family: var(--fonts-regular);
-}
-
-.keyboard {
+ font-weight: var(--main-font-weight);
padding: 8px;
- color: var(--body-bg-color);
- margin-top: 24px;
- border-radius: var(--card-border-radius);
background-color: var(--accent-bg-color);
+ color: #000000;
+ flex-grow: 1;
+ min-height: 50px;
+ width: 20px;
+ border: none;
+ display: flex;
+ align-items: center;
+ justify-content: center;
}
-.keyboard .hg-rows {
- margin: 4px;
-}
-
-.hg-button {
- margin: 2px;
-}
-
-.hg-activeButton {
- background: var(--accent-color) !important;
+.Keyboard-button:active {
+ background-color: var(--accent-color);
}
diff --git a/frontend/webpack.config.js b/frontend/webpack.config.js
index 307437e..93cbf1b 100644
--- a/frontend/webpack.config.js
+++ b/frontend/webpack.config.js
@@ -14,7 +14,11 @@ module.exports = {
plugins: [
new HtmlWebpackPlugin({
title: "Capybara",
- filename: "app.html"
+ filename: "app.html",
+ meta: {
+ "viewport": "width=device-width,height=device-height,initial-scale=1,minimum-scale=1,user-scalable=no,viewport-fit=contain",
+ "application-name": "Capybara"
+ }
}),
new MiniCssExtractPlugin({
filename: "[name].[contenthash].css"
diff --git a/frontend/yarn.lock b/frontend/yarn.lock
index 7698a72..90fd04c 100644
--- a/frontend/yarn.lock
+++ b/frontend/yarn.lock
@@ -1345,11 +1345,6 @@ signal-exit@^3.0.3:
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.5.tgz#9e3e8cc0c75a99472b44321033a7702e7738252f"
integrity sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==
-simple-keyboard@^3.3.16:
- version "3.3.16"
- resolved "https://registry.yarnpkg.com/simple-keyboard/-/simple-keyboard-3.3.16.tgz#045da7c291d8e973f489d9a686f5872fe8121475"
- integrity sha512-fAUNBIeNxh7Rlx3q11Sonq6DLFKYlfQJuRaiqlJkX9vYQeXEqdZGEh/FYyRUMIP0bjVVTI7Slq2H40sQe/DADQ==
-
source-map-js@^0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-0.6.2.tgz#0bb5de631b41cfbda6cfba8bd05a80efdfd2385e"