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"
|
||||
})
|
||||
|
||||
app.static("app", "frontend/dist/", resource_type="dir")
|
||||
app.static("app", "frontend/", resource_type="dir")
|
||||
|
||||
@app.get("/")
|
||||
async def home(req):
|
||||
|
|
|
@ -1,31 +1,53 @@
|
|||
import Banner from "./Banner";
|
||||
import Connection from "./Connection";
|
||||
import KeyboardController from "./Keyboard";
|
||||
import { getAuth, setAuth } from "./LocalConfiguration";
|
||||
import LoginPrompt from "./LoginPrompt";
|
||||
import TouchpadController from "./Touchpad";
|
||||
import Banner from "./Banner.js";
|
||||
import connection, { ConnectionEvent } from "./connection.js";
|
||||
import KeyboardController from "./Keyboard.js";
|
||||
import { setItem } from "./storage.js";
|
||||
import LoginPrompt from "./LoginPrompt.js";
|
||||
import TouchpadController from "./Touchpad.js";
|
||||
|
||||
class App {
|
||||
constructor(mountElement) {
|
||||
this.mountElement = mountElement;
|
||||
|
||||
this.connection = new Connection(`ws://${location.host}/gateway`);
|
||||
constructor() {
|
||||
this.connection = connection;
|
||||
this.touchpad = new TouchpadController(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());
|
||||
this.connection.ws.addEventListener("close", ({code}) => {
|
||||
mountOn(mount, keepMountChildren=false) {
|
||||
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();
|
||||
if (code === 4001) { // 4001 - code for bad auth
|
||||
this.transitionTo("login");
|
||||
} else {
|
||||
this.transitionTo("reconnectingBanner");
|
||||
}
|
||||
});
|
||||
this.connection.onHandshakeCompleted = () => {
|
||||
})]);
|
||||
|
||||
this.connectionHandlers.push([ConnectionEvent.Ready, this.connection.subscribe(ConnectionEvent.Ready, () => {
|
||||
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) {
|
||||
|
@ -51,27 +73,20 @@ class App {
|
|||
}
|
||||
|
||||
mountBannerComponent(title, text) {
|
||||
if (!this.bannerComponent)
|
||||
this.bannerComponent = new Banner();
|
||||
|
||||
this.bannerComponent.mountOn(this.mountElement);
|
||||
this.bannerComponent.updateTitle(title);
|
||||
this.bannerComponent.updateText(text);
|
||||
}
|
||||
|
||||
unmountBannerComponent() {
|
||||
if (this.bannerComponent)
|
||||
this.bannerComponent.unmount();
|
||||
this.bannerComponent.unmount();
|
||||
}
|
||||
|
||||
mountLoginComponent() {
|
||||
if (!this.loginPromptComponent)
|
||||
this.loginPromptComponent = new LoginPrompt(this.connection);
|
||||
|
||||
this.loginPromptComponent.mountOn(this.mountElement);
|
||||
this.loginPromptComponent.onPasswordSubmitted = (p) => {
|
||||
setAuth(p);
|
||||
this.connection.connect(p);
|
||||
setItem("auth:token", p);
|
||||
this.connection.connect();
|
||||
};
|
||||
}
|
||||
|
|
@ -38,8 +38,10 @@ class KeyboardController {
|
|||
this.container.removeChild(this.container.lastChild);
|
||||
}
|
||||
|
||||
const rowsElement = document.createElement("div");
|
||||
rowsElement.classList.add("Keyboard-rows");
|
||||
const rowsParent = document.createElement("div");
|
||||
rowsParent.classList.add("Keyboard-rows");
|
||||
|
||||
const rowsFragment = document.createDocumentFragment();
|
||||
|
||||
for (let row = 0; row < layout.length; row++) {
|
||||
const rowElement = document.createElement("div");
|
||||
|
@ -49,17 +51,18 @@ class KeyboardController {
|
|||
|
||||
const colElement = document.createElement("button");
|
||||
colElement.classList.add("Keyboard-button");
|
||||
colElement.innerHTML = key;
|
||||
colElement.addEventListener("click", (event) => {
|
||||
colElement.innerText = key;
|
||||
colElement.addEventListener("click", () => {
|
||||
this.handleKeypress(key);
|
||||
});
|
||||
|
||||
rowElement.appendChild(colElement);
|
||||
}
|
||||
rowsElement.appendChild(rowElement);
|
||||
rowsFragment.appendChild(rowElement);
|
||||
}
|
||||
|
||||
this.container.appendChild(rowsElement);
|
||||
rowsParent.appendChild(rowsFragment);
|
||||
this.container.appendChild(rowsParent);
|
||||
});
|
||||
}
|
||||
|
|
@ -8,15 +8,10 @@ class LoginPrompt {
|
|||
return; // Already mounted
|
||||
|
||||
this.element = document.createRange().createContextualFragment(`
|
||||
<div class="Card Card-ui-bottom Card-centered-text">
|
||||
<div class="Card Card-ui-bottom LoginPrompt">
|
||||
<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">
|
||||
<br>
|
||||
<button id="continue-button" class="button-default full-width">Continue</button>
|
||||
</div>
|
||||
<input type="password" id="code-input" class="input full-width" placeholder="Code">
|
||||
<button id="continue-button" class="button-default full-width">Continue</button>
|
||||
</div>
|
||||
`).children[0];
|
||||
|
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 {
|
||||
constructor(url) {
|
||||
this.ws = null;
|
||||
this.log = Logger(["Connection"], ["log"]).log;
|
||||
this.messageLog = Logger(["Connection", "Message"], ["log"]).log;
|
||||
this.url = url;
|
||||
this.isReady = false;
|
||||
this.reconnectTimeout = 0;
|
||||
}
|
||||
export const ConnectionEvent = {
|
||||
Open: 0,
|
||||
Closed: 1,
|
||||
Ready: 2,
|
||||
};
|
||||
|
||||
export default {
|
||||
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) {
|
||||
return `%auth%${btoa(password)}`;
|
||||
}
|
||||
},
|
||||
|
||||
connect(password) {
|
||||
connect(password=getItem("auth:token")) {
|
||||
this.ws = new WebSocket(this.url);
|
||||
this.ws.onerror = (e) => this.log("Error", e);
|
||||
this.ws.onopen = () => {
|
||||
this.log("Open");
|
||||
this.dispatch(ConnectionEvent.Open, 1);
|
||||
this.reconnectTimeout = 0;
|
||||
this.log("Sending authentication packet");
|
||||
this.ws.send(`0${this.formatAuthString(password)}`); // send auth packet
|
||||
};
|
||||
this.ws.onmessage = ({ data }) => {
|
||||
if (data === "1") {
|
||||
if (this.onHandshakeCompleted)
|
||||
this.onHandshakeCompleted();
|
||||
|
||||
this.isReady = true;
|
||||
this.dispatch(ConnectionEvent.Ready, 1);
|
||||
this.log("Handshake complete");
|
||||
}
|
||||
};
|
||||
this.ws.onclose = ({ code }) => {
|
||||
this.dispatch(ConnectionEvent.Closed, code);
|
||||
|
||||
if (code === 4001) {// code for bad auth
|
||||
this.log("Closed due to bad auth - skipping reconnect");
|
||||
return;
|
||||
|
@ -44,7 +51,7 @@ class Connection {
|
|||
this.log(`Closed - will reconnect in ${this.reconnectTimeout}ms`);
|
||||
setTimeout(() => this.connect(password), this.reconnectTimeout);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
sendMessage(code, params=[]) {
|
||||
let message = code;
|
||||
|
@ -57,11 +64,38 @@ class Connection {
|
|||
}
|
||||
|
||||
this.ws.send(message);
|
||||
}
|
||||
},
|
||||
|
||||
disconnect() {
|
||||
this.ws.close();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
export default Connection;
|
||||
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());
|
||||
}
|
||||
|
||||
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 = {};
|
||||
|
||||
for (const type of types) {
|
||||
|
@ -50,4 +50,4 @@ export function domLog(message) {
|
|||
document.body.appendChild(document.createTextNode(message));
|
||||
}
|
||||
|
||||
export default Logger;
|
||||
export default logger;
|
|
@ -108,6 +108,15 @@ body {
|
|||
padding: 12px;
|
||||
}
|
||||
|
||||
/* LoginPrompt */
|
||||
|
||||
.LoginPrompt {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 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