remove webpack and improve performance
This commit is contained in:
parent
c05a23912f
commit
ca9d562ed5
16 changed files with 193 additions and 1718 deletions
|
@ -20,7 +20,7 @@ control_message_parser.add_handler("0", {
|
||||||
"auth_string": "str"
|
"auth_string": "str"
|
||||||
})
|
})
|
||||||
|
|
||||||
app.static("app", "frontend/dist/", resource_type="dir")
|
app.static("app", "frontend/", resource_type="dir")
|
||||||
|
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
async def home(req):
|
async def home(req):
|
||||||
|
|
|
@ -1,31 +1,53 @@
|
||||||
import Banner from "./Banner";
|
import Banner from "./Banner.js";
|
||||||
import Connection from "./Connection";
|
import connection, { ConnectionEvent } from "./connection.js";
|
||||||
import KeyboardController from "./Keyboard";
|
import KeyboardController from "./Keyboard.js";
|
||||||
import { getAuth, setAuth } from "./LocalConfiguration";
|
import { setItem } from "./storage.js";
|
||||||
import LoginPrompt from "./LoginPrompt";
|
import LoginPrompt from "./LoginPrompt.js";
|
||||||
import TouchpadController from "./Touchpad";
|
import TouchpadController from "./Touchpad.js";
|
||||||
|
|
||||||
class App {
|
class App {
|
||||||
constructor(mountElement) {
|
constructor() {
|
||||||
this.mountElement = mountElement;
|
this.connection = connection;
|
||||||
|
|
||||||
this.connection = new Connection(`ws://${location.host}/gateway`);
|
|
||||||
this.touchpad = new TouchpadController(this.connection);
|
this.touchpad = new TouchpadController(this.connection);
|
||||||
this.keyboard = new KeyboardController(this.connection);
|
this.keyboard = new KeyboardController(this.connection);
|
||||||
this.loginPromptComponent = null;
|
this.bannerComponent = new Banner();
|
||||||
|
this.loginPromptComponent = new LoginPrompt(this.connection);
|
||||||
|
this.connectionHandlers = [];
|
||||||
|
}
|
||||||
|
|
||||||
this.connection.connect(getAuth());
|
mountOn(mount, keepMountChildren=false) {
|
||||||
this.connection.ws.addEventListener("close", ({code}) => {
|
this.mountElement = mount;
|
||||||
|
|
||||||
|
if (!keepMountChildren) {
|
||||||
|
while (mount.firstChild) {
|
||||||
|
mount.removeChild(mount.lastChild);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.connectionHandlers.push([ConnectionEvent.Closed, this.connection.subscribe(ConnectionEvent.Closed, (code) => {
|
||||||
this.unmountApp();
|
this.unmountApp();
|
||||||
if (code === 4001) { // 4001 - code for bad auth
|
if (code === 4001) { // 4001 - code for bad auth
|
||||||
this.transitionTo("login");
|
this.transitionTo("login");
|
||||||
} else {
|
} else {
|
||||||
this.transitionTo("reconnectingBanner");
|
this.transitionTo("reconnectingBanner");
|
||||||
}
|
}
|
||||||
});
|
})]);
|
||||||
this.connection.onHandshakeCompleted = () => {
|
|
||||||
|
this.connectionHandlers.push([ConnectionEvent.Ready, this.connection.subscribe(ConnectionEvent.Ready, () => {
|
||||||
this.transitionTo("app");
|
this.transitionTo("app");
|
||||||
};
|
})]);
|
||||||
|
}
|
||||||
|
|
||||||
|
unmount() {
|
||||||
|
if (this.mountElement) {
|
||||||
|
this.unmountApp();
|
||||||
|
this.unmountBannerComponent();
|
||||||
|
this.unmountLoginComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.connectionHandlers.forEach(([ event, handler ]) => {
|
||||||
|
this.connection.unsubscribe(event, handler);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
transitionTo(type) {
|
transitionTo(type) {
|
||||||
|
@ -51,27 +73,20 @@ class App {
|
||||||
}
|
}
|
||||||
|
|
||||||
mountBannerComponent(title, text) {
|
mountBannerComponent(title, text) {
|
||||||
if (!this.bannerComponent)
|
|
||||||
this.bannerComponent = new Banner();
|
|
||||||
|
|
||||||
this.bannerComponent.mountOn(this.mountElement);
|
this.bannerComponent.mountOn(this.mountElement);
|
||||||
this.bannerComponent.updateTitle(title);
|
this.bannerComponent.updateTitle(title);
|
||||||
this.bannerComponent.updateText(text);
|
this.bannerComponent.updateText(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
unmountBannerComponent() {
|
unmountBannerComponent() {
|
||||||
if (this.bannerComponent)
|
|
||||||
this.bannerComponent.unmount();
|
this.bannerComponent.unmount();
|
||||||
}
|
}
|
||||||
|
|
||||||
mountLoginComponent() {
|
mountLoginComponent() {
|
||||||
if (!this.loginPromptComponent)
|
|
||||||
this.loginPromptComponent = new LoginPrompt(this.connection);
|
|
||||||
|
|
||||||
this.loginPromptComponent.mountOn(this.mountElement);
|
this.loginPromptComponent.mountOn(this.mountElement);
|
||||||
this.loginPromptComponent.onPasswordSubmitted = (p) => {
|
this.loginPromptComponent.onPasswordSubmitted = (p) => {
|
||||||
setAuth(p);
|
setItem("auth:token", p);
|
||||||
this.connection.connect(p);
|
this.connection.connect();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,8 +38,10 @@ class KeyboardController {
|
||||||
this.container.removeChild(this.container.lastChild);
|
this.container.removeChild(this.container.lastChild);
|
||||||
}
|
}
|
||||||
|
|
||||||
const rowsElement = document.createElement("div");
|
const rowsParent = document.createElement("div");
|
||||||
rowsElement.classList.add("Keyboard-rows");
|
rowsParent.classList.add("Keyboard-rows");
|
||||||
|
|
||||||
|
const rowsFragment = document.createDocumentFragment();
|
||||||
|
|
||||||
for (let row = 0; row < layout.length; row++) {
|
for (let row = 0; row < layout.length; row++) {
|
||||||
const rowElement = document.createElement("div");
|
const rowElement = document.createElement("div");
|
||||||
|
@ -49,17 +51,18 @@ class KeyboardController {
|
||||||
|
|
||||||
const colElement = document.createElement("button");
|
const colElement = document.createElement("button");
|
||||||
colElement.classList.add("Keyboard-button");
|
colElement.classList.add("Keyboard-button");
|
||||||
colElement.innerHTML = key;
|
colElement.innerText = key;
|
||||||
colElement.addEventListener("click", (event) => {
|
colElement.addEventListener("click", () => {
|
||||||
this.handleKeypress(key);
|
this.handleKeypress(key);
|
||||||
});
|
});
|
||||||
|
|
||||||
rowElement.appendChild(colElement);
|
rowElement.appendChild(colElement);
|
||||||
}
|
}
|
||||||
rowsElement.appendChild(rowElement);
|
rowsFragment.appendChild(rowElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.container.appendChild(rowsElement);
|
rowsParent.appendChild(rowsFragment);
|
||||||
|
this.container.appendChild(rowsParent);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,16 +8,11 @@ class LoginPrompt {
|
||||||
return; // Already mounted
|
return; // Already mounted
|
||||||
|
|
||||||
this.element = document.createRange().createContextualFragment(`
|
this.element = document.createRange().createContextualFragment(`
|
||||||
<div class="Card Card-ui-bottom Card-centered-text">
|
<div class="Card Card-ui-bottom LoginPrompt">
|
||||||
<h2>Login</h2>
|
<h2>Login</h2>
|
||||||
<p>You need to enter the login code before you can start controlling your device.</p>
|
|
||||||
<br>
|
|
||||||
<div class="full-width">
|
|
||||||
<input type="password" id="code-input" class="input full-width" placeholder="Code">
|
<input type="password" id="code-input" class="input full-width" placeholder="Code">
|
||||||
<br>
|
|
||||||
<button id="continue-button" class="button-default full-width">Continue</button>
|
<button id="continue-button" class="button-default full-width">Continue</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
`).children[0];
|
`).children[0];
|
||||||
|
|
||||||
this.element.querySelector("#continue-button").addEventListener("click", () => {
|
this.element.querySelector("#continue-button").addEventListener("click", () => {
|
27
frontend/app.html
Normal file
27
frontend/app.html
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||||
|
<title>Capybara</title>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="./main.css">
|
||||||
|
<script type="module">
|
||||||
|
import App from "./App.js";
|
||||||
|
import connection from "./connection.js";
|
||||||
|
|
||||||
|
window.__capybara = {};
|
||||||
|
|
||||||
|
window.__capybara.app = new App();
|
||||||
|
window.__capybara.app.mountOn(document.body);
|
||||||
|
|
||||||
|
connection.connect();
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="Card Card-ui-bottom Card-centered-text">
|
||||||
|
<h2>Loading...</h2>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1,38 +1,45 @@
|
||||||
import Logger from "./common/Logger";
|
import logger from "./logger.js";
|
||||||
|
import { getItem } from "./storage.js";
|
||||||
|
|
||||||
class Connection {
|
export const ConnectionEvent = {
|
||||||
constructor(url) {
|
Open: 0,
|
||||||
this.ws = null;
|
Closed: 1,
|
||||||
this.log = Logger(["Connection"], ["log"]).log;
|
Ready: 2,
|
||||||
this.messageLog = Logger(["Connection", "Message"], ["log"]).log;
|
};
|
||||||
this.url = url;
|
|
||||||
this.isReady = false;
|
export default {
|
||||||
this.reconnectTimeout = 0;
|
ws: null,
|
||||||
}
|
log: logger(["Connection"], ["log"]).log,
|
||||||
|
messageLog: logger(["Connection", "Message"], ["log"]).log,
|
||||||
|
url: getItem("server:gatewayBase"),
|
||||||
|
isReady: false,
|
||||||
|
reconnectTimeout: 0,
|
||||||
|
handlers: new Map(),
|
||||||
|
|
||||||
formatAuthString(password) {
|
formatAuthString(password) {
|
||||||
return `%auth%${btoa(password)}`;
|
return `%auth%${btoa(password)}`;
|
||||||
}
|
},
|
||||||
|
|
||||||
connect(password) {
|
connect(password=getItem("auth:token")) {
|
||||||
this.ws = new WebSocket(this.url);
|
this.ws = new WebSocket(this.url);
|
||||||
this.ws.onerror = (e) => this.log("Error", e);
|
this.ws.onerror = (e) => this.log("Error", e);
|
||||||
this.ws.onopen = () => {
|
this.ws.onopen = () => {
|
||||||
this.log("Open");
|
this.log("Open");
|
||||||
|
this.dispatch(ConnectionEvent.Open, 1);
|
||||||
this.reconnectTimeout = 0;
|
this.reconnectTimeout = 0;
|
||||||
this.log("Sending authentication packet");
|
this.log("Sending authentication packet");
|
||||||
this.ws.send(`0${this.formatAuthString(password)}`); // send auth packet
|
this.ws.send(`0${this.formatAuthString(password)}`); // send auth packet
|
||||||
};
|
};
|
||||||
this.ws.onmessage = ({ data }) => {
|
this.ws.onmessage = ({ data }) => {
|
||||||
if (data === "1") {
|
if (data === "1") {
|
||||||
if (this.onHandshakeCompleted)
|
|
||||||
this.onHandshakeCompleted();
|
|
||||||
|
|
||||||
this.isReady = true;
|
this.isReady = true;
|
||||||
|
this.dispatch(ConnectionEvent.Ready, 1);
|
||||||
this.log("Handshake complete");
|
this.log("Handshake complete");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.ws.onclose = ({ code }) => {
|
this.ws.onclose = ({ code }) => {
|
||||||
|
this.dispatch(ConnectionEvent.Closed, code);
|
||||||
|
|
||||||
if (code === 4001) {// code for bad auth
|
if (code === 4001) {// code for bad auth
|
||||||
this.log("Closed due to bad auth - skipping reconnect");
|
this.log("Closed due to bad auth - skipping reconnect");
|
||||||
return;
|
return;
|
||||||
|
@ -44,7 +51,7 @@ class Connection {
|
||||||
this.log(`Closed - will reconnect in ${this.reconnectTimeout}ms`);
|
this.log(`Closed - will reconnect in ${this.reconnectTimeout}ms`);
|
||||||
setTimeout(() => this.connect(password), this.reconnectTimeout);
|
setTimeout(() => this.connect(password), this.reconnectTimeout);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
|
||||||
sendMessage(code, params=[]) {
|
sendMessage(code, params=[]) {
|
||||||
let message = code;
|
let message = code;
|
||||||
|
@ -57,11 +64,38 @@ class Connection {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.ws.send(message);
|
this.ws.send(message);
|
||||||
}
|
},
|
||||||
|
|
||||||
disconnect() {
|
disconnect() {
|
||||||
this.ws.close();
|
this.ws.close();
|
||||||
}
|
},
|
||||||
|
|
||||||
|
dispatch(event, data) {
|
||||||
|
const eventHandlers = this.handlers.get(event);
|
||||||
|
if (!eventHandlers)
|
||||||
|
return;
|
||||||
|
|
||||||
|
eventHandlers.forEach(e => e(data));
|
||||||
|
},
|
||||||
|
|
||||||
|
subscribe(event, handler) {
|
||||||
|
if (!this.handlers.get(event)) {
|
||||||
|
this.handlers.set(event, new Set());
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Connection;
|
this.handlers.get(event).add(handler);
|
||||||
|
return handler; // can later be used for unsubscribe()
|
||||||
|
},
|
||||||
|
|
||||||
|
unsubscribe(event, handler) {
|
||||||
|
const eventHandlers = this.handlers.get(event);
|
||||||
|
if (!eventHandlers)
|
||||||
|
return;
|
||||||
|
|
||||||
|
eventHandlers.delete(handler);
|
||||||
|
|
||||||
|
if (eventHandlers.size < 1) {
|
||||||
|
this.handlers.delete(event);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
|
@ -35,7 +35,7 @@ const loggerOfType = (components, type='log') => (...args) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function Logger(components, types=['warn', 'error', 'log']) {
|
function logger(components, types=['warn', 'error', 'log']) {
|
||||||
const loggerObj = {};
|
const loggerObj = {};
|
||||||
|
|
||||||
for (const type of types) {
|
for (const type of types) {
|
||||||
|
@ -50,4 +50,4 @@ export function domLog(message) {
|
||||||
document.body.appendChild(document.createTextNode(message));
|
document.body.appendChild(document.createTextNode(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Logger;
|
export default logger;
|
|
@ -108,6 +108,15 @@ body {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* LoginPrompt */
|
||||||
|
|
||||||
|
.LoginPrompt {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
/* Touchpad */
|
/* Touchpad */
|
||||||
|
|
||||||
.Touchpad {
|
.Touchpad {
|
|
@ -1,19 +0,0 @@
|
||||||
{
|
|
||||||
"name": "capybara-frontend",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"private": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"scripts": {
|
|
||||||
"watch": "webpack --watch",
|
|
||||||
"build": "webpack"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"css-loader": "^6.5.0",
|
|
||||||
"css-minimizer-webpack-plugin": "^3.1.1",
|
|
||||||
"html-webpack-plugin": "^5.5.0",
|
|
||||||
"mini-css-extract-plugin": "^2.4.3",
|
|
||||||
"webpack": "^5.61.0",
|
|
||||||
"webpack-cli": "^4.9.1"
|
|
||||||
},
|
|
||||||
"dependencies": {}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
export function getAuth() {
|
|
||||||
return localStorage.getItem("$auth");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setAuth(newValue) {
|
|
||||||
return localStorage.setItem("$auth", newValue);
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
import "./styles/main.css";
|
|
||||||
import App from "./App";
|
|
||||||
|
|
||||||
window.__app = new App(document.body);
|
|
47
frontend/storage.js
Normal file
47
frontend/storage.js
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
const defaults = {
|
||||||
|
"server:gatewayBase": `${location.protocol === "https:" ? "wss" : "ws"}://${location.host}/gateway`,
|
||||||
|
"auth:token": "",
|
||||||
|
};
|
||||||
|
const store = new Map(Object.entries(defaults));
|
||||||
|
const persistentProvider = localStorage;
|
||||||
|
let didCacheProvider = false;
|
||||||
|
|
||||||
|
export function setItem(key, value) {
|
||||||
|
store.set(key, value);
|
||||||
|
if (persistentProvider) {
|
||||||
|
persistentProvider.setItem(key, typeof value === "string" ? value : JSON.stringify(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getItem(key) {
|
||||||
|
if (!didCacheProvider) {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
return store.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeItem(key) {
|
||||||
|
store.delete(key);
|
||||||
|
if (persistentProvider) {
|
||||||
|
persistentProvider.removeItem(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function init() {
|
||||||
|
if (!persistentProvider)
|
||||||
|
return;
|
||||||
|
|
||||||
|
store.forEach((defaultValue, key) => {
|
||||||
|
const override = persistentProvider.getItem(key);
|
||||||
|
if (override !== null) {
|
||||||
|
try {
|
||||||
|
store.set(key, typeof defaultValue === "string" ? override : JSON.parse(override));
|
||||||
|
} catch (o_O) {
|
||||||
|
console.warn("[Storage]", `init(): An exception was thrown while parsing the value of key "${key}" from persistentProvider. The key "${key}" will be removed from persistentProvider.`, o_O);
|
||||||
|
persistentProvider.removeItem(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
didCacheProvider = true;
|
||||||
|
}
|
|
@ -1,52 +0,0 @@
|
||||||
const path = require("path");
|
|
||||||
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
|
||||||
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
|
||||||
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
mode: "development",
|
|
||||||
entry: './src/index.js',
|
|
||||||
output: {
|
|
||||||
filename: '[name].[contenthash].js',
|
|
||||||
path: path.resolve(__dirname, 'dist'),
|
|
||||||
clean: true
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
new HtmlWebpackPlugin({
|
|
||||||
title: "Capybara",
|
|
||||||
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"
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
module: {
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
test: /\.css$/i,
|
|
||||||
use: [MiniCssExtractPlugin.loader, "css-loader"],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
optimization: {
|
|
||||||
moduleIds: 'deterministic',
|
|
||||||
runtimeChunk: 'single',
|
|
||||||
splitChunks: {
|
|
||||||
cacheGroups: {
|
|
||||||
vendor: {
|
|
||||||
test: /[\\/]node_modules[\\/]/,
|
|
||||||
name: 'vendors',
|
|
||||||
chunks: 'all',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
minimizer: [
|
|
||||||
`...`,
|
|
||||||
new CssMinimizerPlugin(),
|
|
||||||
]
|
|
||||||
},
|
|
||||||
};
|
|
1573
frontend/yarn.lock
1573
frontend/yarn.lock
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue