From fa6757b221091663a0c0a7b4dc116b1ac4fa40dc Mon Sep 17 00:00:00 2001 From: hippoz <10706925-hippoz@users.noreply.gitlab.com> Date: Fri, 26 Aug 2022 15:56:42 +0300 Subject: [PATCH] use own keyboard and improve css --- frontend/package.json | 4 +- frontend/src/Banner.js | 2 +- frontend/src/Keyboard.js | 170 +++++++++++++++++++++-------------- frontend/src/LoginPrompt.js | 2 +- frontend/src/Touchpad.js | 40 +++++++-- frontend/src/index.js | 2 +- frontend/src/styles/main.css | 163 ++++++++++++++++++--------------- frontend/webpack.config.js | 6 +- frontend/yarn.lock | 5 -- 9 files changed, 233 insertions(+), 161 deletions(-) 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"