Skip to content

Commit

Permalink
Consume paste API endpoint (#1626)
Browse files Browse the repository at this point in the history
Resolves #1026

This PR consumes the new `/api/paste` endpoint and removes the,
now-unused, text-to-keystroke frontend logic.

Demo video:


https://github.com/tiny-pilot/tinypilot/assets/6730025/2b3cb2c9-964b-4e90-88c9-7ab13775f02b

### Notes
1. Pasting unsupported characters (i.e., `“`, `”`, `–` (en-dash), `—`
(em-dash), etc.) now displays this error dialog:
    
<img width="870" alt="Screen Shot 2023-09-15 at 11 20 21"
src="https://github.com/tiny-pilot/tinypilot/assets/6730025/1ebad5a7-fbb6-40f4-a9fe-317a06d458a8">

Previously, these unsupported characters were silently ignored during a
paste.
As [discussed
here](#1545 (comment)),
we plan to further refine this behavior in the future.

### Peer testing
This PR is stacked onto the following PRs:
* #1625
* #1624

You can test them all on device via the following command:
```bash
curl \
  --silent \
  --show-error \
  --location \
  https://raw.githubusercontent.com/tiny-pilot/tinypilot/master/scripts/install-bundle | \
  sudo bash -s -- https://output.circle-artifacts.com/output/job/d842ed53-55a6-490d-9cfd-842155c7d6e5/artifacts/0/bundler/dist/tinypilot-community-20230919T1127Z-1.9.1-39+3e308b3.tgz && \
  sudo reboot
```

<a data-ca-tag
href="https://codeapprove.com/pr/tiny-pilot/tinypilot/1626"><img
src="https://codeapprove.com/external/github-tag-allbg.png" alt="Review
on CodeApprove" /></a>
  • Loading branch information
jdeanwallace authored Sep 19, 2023
1 parent da6f5f0 commit 2d411f3
Show file tree
Hide file tree
Showing 5 changed files with 36 additions and 194 deletions.
56 changes: 1 addition & 55 deletions app/static/js/app.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
import {
isModifierCode,
findKeyCode,
keystrokeToCanonicalCode,
requiresShiftKey,
} from "./keycodes.js";
import { isModifierCode, keystrokeToCanonicalCode } from "./keycodes.js";
import { KeyboardState } from "./keyboardstate.js";
import { sendKeystroke } from "./keystrokes.js";
import * as settings from "./settings.js";
Expand Down Expand Up @@ -63,13 +58,6 @@ function unixTime() {
return new Date().getTime();
}

function browserLanguage() {
if (navigator.languages) {
return navigator.languages[0];
}
return navigator.language || navigator.userLanguage;
}

const keystrokeHistory = document.getElementById("status-bar").keystrokeHistory;

// Send a keystroke message to the backend, and add a key card to the web UI.
Expand Down Expand Up @@ -216,45 +204,6 @@ function onKeyUp(evt) {
}
}

// Translate a single character into a keystroke and sends it to the backend.
function processTextCharacter(textCharacter, language) {
// Ignore carriage returns.
if (textCharacter === "\r") {
return;
}

const code = findKeyCode([textCharacter.toLowerCase()], language);
let friendlyName = textCharacter;
// Give cleaner names to keys so that they render nicely in the history.
if (textCharacter === "\n") {
friendlyName = "Enter";
} else if (textCharacter === "\t") {
friendlyName = "Tab";
}

processKeystroke({
metaLeft: false,
metaRight: false,
altLeft: false,
altRight: false,
shiftLeft: requiresShiftKey(textCharacter),
shiftRight: false,
ctrlLeft: false,
ctrlRight: false,
key: friendlyName,
code: code,
});
}

// Translate a string of text into individual keystrokes and sends them to the
// backend.
function processTextInput(textInput) {
const language = browserLanguage();
for (const textCharacter of textInput) {
processTextCharacter(textCharacter, language);
}
}

function setCursor(cursor, save = true) {
// Ensure the correct cursor option displays as active in the navbar.
if (save) {
Expand Down Expand Up @@ -288,9 +237,6 @@ document.addEventListener("video-streaming-mode-changed", (evt) => {
document.getElementById("status-bar").videoStreamIndicator.mode =
evt.detail.mode;
});
document.addEventListener("paste-text", (evt) => {
processTextInput(evt.detail);
});

// To allow for keycode combinations to be pressed (e.g., Alt + Tab), the
// backend doesn't automatically release modifier keycodes after being pressed.
Expand Down
14 changes: 14 additions & 0 deletions app/static/js/controllers.js
Original file line number Diff line number Diff line change
Expand Up @@ -323,3 +323,17 @@ export async function isMjpegStreamAvailable() {
.then((response) => response.ok)
.catch(() => false);
}

export async function pasteText(text, language) {
return fetch("/api/paste", {
method: "POST",
mode: "same-origin",
cache: "no-cache",
redirect: "error",
headers: {
"Content-Type": "application/json",
"X-CSRFToken": getCsrfToken(),
},
body: JSON.stringify({ text, language }),
}).then(processJsonResponse);
}
120 changes: 0 additions & 120 deletions app/static/js/keycodes.js
Original file line number Diff line number Diff line change
@@ -1,74 +1,3 @@
//TODO: Fix these mappings.

// Mappings of characters to codes that are shared among different keyboard
// layouts.
const commonKeyCodes = {
"\t": "Tab",
"\n": "Enter",
" ": "Space",
1: "Digit1",
2: "Digit2",
3: "Digit3",
4: "Digit4",
5: "Digit5",
6: "Digit6",
7: "Digit7",
8: "Digit8",
9: "Digit9",
0: "Digit0",
$: "Digit4",
"!": "Digit1",
"%": "Digit5",
"^": "Digit6",
"&": "Digit7",
"*": "Digit8",
"(": "Digit9",
")": "Digit0",
_: "Minus",
"-": "Minus",
"+": "Equal",
"=": "Equal",
":": "Semicolon",
";": "Semicolon",
a: "KeyA",
b: "KeyB",
c: "KeyC",
d: "KeyD",
e: "KeyE",
f: "KeyF",
g: "KeyG",
h: "KeyH",
i: "KeyI",
j: "KeyJ",
k: "KeyK",
l: "KeyL",
m: "KeyM",
n: "KeyN",
o: "KeyO",
p: "KeyP",
q: "KeyQ",
r: "KeyR",
s: "KeyS",
t: "KeyT",
u: "KeyU",
v: "KeyV",
w: "KeyW",
x: "KeyX",
y: "KeyY",
z: "KeyZ",
",": "Comma",
"<": "Comma",
".": "Period",
">": "Period",
"/": "Slash",
"?": "Slash",
"[": "BracketLeft",
"{": "BracketLeft",
"]": "BracketRight",
"}": "BracketRight",
"'": "Quote",
};

/**
* @param {KeyboardEvent} keystroke - https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent
*/
Expand All @@ -90,21 +19,6 @@ export function keystrokeToCanonicalCode(keystroke) {
return keystroke.code;
}

// Given a character and a browser language, finds the matching code.
export function findKeyCode(character, browserLanguage) {
if (browserLanguage === "en-GB") {
return findKeyCodeEnGb(character);
}
// Default to en-US if no other language matches.
return findKeyCodeEnUs(character);
}

// Returns true if the text character requires a shift key.
export function requiresShiftKey(character) {
const shiftedPattern = /^[A-Z¬!"£$%^&*()_+{}|<>?:@~#]/;
return shiftedPattern.test(character);
}

export function isModifierCode(code) {
const modifierCodes = [
"AltLeft",
Expand All @@ -118,37 +32,3 @@ export function isModifierCode(code) {
];
return modifierCodes.indexOf(code) >= 0;
}

function joinDictionaries(a, b) {
return Object.assign({}, a, b);
}

function findKeyCodeEnUs(character) {
const usSpecificKeys = {
"@": "Digit2",
"#": "Digit3",
"~": "Backquote",
"`": "Backquote",
"\\": "Backslash",
"|": "Backslash",
'"': "Quote",
};
const lookup = joinDictionaries(commonKeyCodes, usSpecificKeys);
return lookup[character];
}

function findKeyCodeEnGb(character) {
const gbSpecificKeys = {
'"': "Digit2",
"£": "Digit3",
"\\": "IntlBackslash",
"|": "IntlBackslash",
"~": "Backslash",
"#": "Backslash",
"`": "Backquote",
"¬": "Backquote",
"@": "Quote",
};
const lookup = joinDictionaries(commonKeyCodes, gbSpecificKeys);
return lookup[character];
}
10 changes: 0 additions & 10 deletions app/static/js/keycodes.test.js

This file was deleted.

30 changes: 21 additions & 9 deletions app/templates/custom-elements/paste-dialog.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ <h3>Paste Text</h3>
</template>

<script type="module">
import { DialogClosedEvent } from "/js/events.js";
import { DialogClosedEvent, DialogFailedEvent } from "/js/events.js";
import { pasteText } from "/js/controllers.js";

(function () {
const template = document.querySelector("#paste-dialog-template");
Expand Down Expand Up @@ -78,14 +79,25 @@ <h3>Paste Text</h3>
}

_handleConfirmPaste() {
this.dispatchEvent(
new CustomEvent("paste-text", {
detail: this.elements.pasteArea.value,
bubbles: true,
composed: true,
})
);
this._closeDialog();
const text = this.elements.pasteArea.value;
const language = this._browserLanguage();
pasteText(text, language)
.then(() => this._closeDialog())
.catch((error) =>
this.dispatchEvent(
new DialogFailedEvent({
title: "Failed to Paste Text",
details: error,
})
)
);
}

_browserLanguage() {
if (navigator.languages) {
return navigator.languages[0];
}
return navigator.language || navigator.userLanguage;
}

_closeDialog() {
Expand Down

0 comments on commit 2d411f3

Please sign in to comment.