refactor everything to typescript

We don't need React -- we'll reinvent it ourselves!

Signed-off-by: Sean Cross <sean@xobs.io>
This commit is contained in:
Sean Cross 2023-12-02 11:17:23 +08:00
parent 31f0dff822
commit ceb2d55543
9 changed files with 387 additions and 114 deletions

2
package-lock.json generated
View File

@ -1,5 +1,5 @@
{
"name": "ui-barebones",
"name": "ui",
"lockfileVersion": 3,
"requires": true,
"packages": {

View File

@ -1,16 +1,9 @@
import { FarpatchWidget } from './interfaces';
import { UartWidget } from './interface/uart';
import { DashboardWidget } from './interface/dashboard';
var FarpatchWidgets: { [key: string]: FarpatchWidget } = {
"dashboard": new DashboardWidget(),
"uart": new UartWidget(),
};
import { FarpatchWidget, farpatchWidgets } from './interfaces';
// TODO: Get the current widget from the address bar, if one exists
var currentWidgetName: string = "dashboard";
var currentWidget: FarpatchWidget = FarpatchWidgets[currentWidgetName];
var currentWidgetView: HTMLElement = document.getElementById(currentWidgetName + "-view") as HTMLElement;
var currentWidget: FarpatchWidget = farpatchWidgets[0];
var widgetViews: HTMLElement[] = [];
// var currentWidgetView: HTMLElement = document.getElementById(currentWidgetName + "-view") as HTMLElement;
function addToggleSidebarListener(element: HTMLElement) {
// Hide or show the sidebar. MaterialUI calls this toggling between
@ -31,97 +24,88 @@ function addToggleSidebarListener(element: HTMLElement) {
}, false);
}
// On load, add a listener for mouse clicks on the navigation bar.
function setupNavItem(element: HTMLElement) {
// Get the base name of the element in order to set up the callback.
var elementBase = element.id.replace("-button", "");
if (typeof (FarpatchWidgets[elementBase]) === 'undefined') {
console.log("No widget found for " + elementBase);
// return;
} else {
console.log("Setting up widget for " + elementBase);
var elementContent = document.getElementById(elementBase + "-view");
if (typeof (elementContent) === 'undefined' || elementContent === null) {
console.log("No element found for " + elementBase + "-view");
return;
}
FarpatchWidgets[elementBase].onInit();
function deactivateWidget(widget: FarpatchWidget) {
widget.navItem.classList.remove("sidenav-item-active");
widgetViews[widget.index].classList.remove("main-content-active");
widget.onBlur(widgetViews[widget.index]);
}
if (FarpatchWidgets[elementBase] === currentWidget) {
element.classList.add("sidenav-item-active");
FarpatchWidgets[elementBase].onFocus(elementContent);
currentWidgetView = elementContent;
elementContent.classList.add("main-content-active");
} else {
console.log("Not initializing widget " + elementBase);
}
function activateWidget(widget: FarpatchWidget) {
widget.navItem.classList.add("sidenav-item-active");
widgetViews[widget.index].classList.add("main-content-active");
widget.onFocus(widgetViews[widget.index]);
}
function switchToWidget(widget: FarpatchWidget) {
if (widget === currentWidget) {
return;
}
deactivateWidget(currentWidget);
currentWidget = widget;
activateWidget(widget);
}
// Hook "click" for each navigation item. The listener will:
// 1. Loop through each nav item and remove the "active" class for
// each inactive item and add it to the newly-clicked item.
// 2. Unhide the correct view to activate it
element.addEventListener('click', function () {
var main = document.getElementsByTagName("main")[0];
var navItems = document.querySelectorAll(".sidenav-item");
var elementBase = element.id.replace("-button", "");
if (typeof (FarpatchWidgets[elementBase]) !== 'undefined') {
if (FarpatchWidgets[elementBase] !== currentWidget) {
var newElementContent = document.getElementById(elementBase + "-view");
if (typeof (newElementContent) === 'undefined' || newElementContent === null) {
console.log("No element found for " + elementBase + "-view");
return;
}
currentWidget.onBlur(currentWidgetView);
FarpatchWidgets[elementBase].onFocus(newElementContent);
currentWidget = FarpatchWidgets[elementBase];
currentWidgetName = elementBase;
currentWidgetView = newElementContent;
}
}
for (var i = 0; i < navItems.length; i++) {
var navItem = navItems[i];
if (navItem === element) {
if (!navItem.classList.contains("sidenav-item-active")) {
navItem.classList.add("sidenav-item-active");
}
} else {
navItem.classList.remove("sidenav-item-active");
}
}
var mainViews = document.querySelectorAll(".main-content-active");
for (var i = 0; i < mainViews.length; i++) {
var mainView = mainViews[i];
mainView.classList.remove("main-content-active");
}
var elementBase = element.id.replace("-button", "");
var elementContent = document.getElementById(elementBase + "-view");
if (typeof (elementContent) === 'undefined' || elementContent === null) {
console.log("No element found for " + elementBase + "-view");
return;
}
elementContent.classList.add("main-content-active");
// On load, add a listener for mouse clicks on the navigation bar.
function setupNavItem(widget: FarpatchWidget) {
var w = widget;
widget.navItem.addEventListener('click', function () {
switchToWidget(w);
}, false);
}
document.addEventListener('DOMContentLoaded', function () {
var navItems = document.querySelectorAll(".sidenav-item");
for (var i = 0; i < navItems.length; i++) {
// For the "toggle" button, don't add a listener. Instead, have it
// toggle the visibility of the sidebar.
if (navItems[i].classList.contains("sidenav-item-toggle")) {
addToggleSidebarListener(navItems[i] as HTMLElement);
} else {
setupNavItem(navItems[i] as HTMLElement);
}
// Populate the page
var body = document.getElementsByTagName("body")[0];
var sidenav = document.createElement("nav");
var mainView: HTMLElement = document.createElement("main");
sidenav.classList.add("sidenav");
var sidenavList = document.createElement("ul");
sidenavList.classList.add("sidenav-nav");
for (var i = 0; i < farpatchWidgets.length; i++) {
var widget = farpatchWidgets[i];
widget.onInit();
var widgetView = document.createElement("div");
widgetView.classList.add("main-content");
widgetView.id = widget.name + "-view";
widgetViews.push(widgetView);
mainView.appendChild(widgetView);
sidenavList.appendChild(widget.navItem);
setupNavItem(widget);
}
// var navItems = document.querySelectorAll(".main-viewport");
// for (var i = 0; i < navItems.length; i++) {
// addNavListener(navItems[i]);
// }
// Add the button to collapse the sidebar
var sidebarFiller = document.createElement("li");
sidebarFiller.classList.add("sidenav-item-filler");
sidenavList.appendChild(sidebarFiller);
var toggleSidebar = document.createElement("li");
toggleSidebar.classList.add("sidenav-item");
toggleSidebar.classList.add("sidenav-item-toggle");
toggleSidebar.id = "rail-toggle-button";
var toggleSidebarLink = document.createElement("a");
toggleSidebarLink.classList.add("sidenav-link");
var toggleSidebarIcon = document.createElement("span");
toggleSidebarIcon.classList.add("las");
toggleSidebarIcon.classList.add("la-3x");
toggleSidebarIcon.classList.add("la-bars");
toggleSidebarIcon.classList.add("icon");
var toggleSidebarText = document.createElement("span");
toggleSidebarText.classList.add("link-text");
toggleSidebarText.innerText = "Hide Sidebar";
toggleSidebarLink.appendChild(toggleSidebarIcon);
toggleSidebarLink.appendChild(toggleSidebarText);
toggleSidebar.appendChild(toggleSidebarLink);
sidenavList.appendChild(toggleSidebar);
addToggleSidebarListener(toggleSidebar);
sidenav.appendChild(sidenavList);
body.appendChild(sidenav);
body.appendChild(mainView);
currentWidget = farpatchWidgets[0];
activateWidget(farpatchWidgets[0]);
}, false);

View File

@ -1,10 +1,24 @@
import { FarpatchWidget } from "../interfaces";
import { FarpatchWidget, makeNavView as makeNavItem } from "../interfaces";
export class DashboardWidget implements FarpatchWidget {
view: Element = document.createElement("div");
index: number = 0;
view: HTMLElement = document.createElement("div");
navItem: HTMLElement;
name: string;
icon: string = "home";
title: string = "Dashboard";
onInit(): void {
this.view.innerHTML = `
constructor(name: string) {
this.name = name;
this.navItem = makeNavItem(name, this.icon, this.title);
}
updateIndex(index: number): void {
this.index = index;
}
onInit(): void {
this.view.innerHTML = `
<div class="fieldset-item">
<picture aria-hidden="true">
<svg viewBox="0 0 24 24">
@ -30,14 +44,14 @@ export class DashboardWidget implements FarpatchWidget {
</div>
</div>
`;
console.log("Initialized Dashboard Widget");
}
onFocus(element: HTMLElement): void {
console.log("Displaying Dashboard Widget");
element.appendChild(this.view);
}
onBlur(element: HTMLElement): void {
console.log("Archiving Dashboard Widget");
element.removeChild(this.view);
}
console.log("Initialized Dashboard Widget");
}
onFocus(element: HTMLElement): void {
console.log("Displaying Dashboard Widget");
element.appendChild(this.view);
}
onBlur(element: HTMLElement): void {
console.log("Archiving Dashboard Widget");
element.removeChild(this.view);
}
}

76
src/interface/debug.ts Normal file
View File

@ -0,0 +1,76 @@
import { FarpatchWidget, makeNavView } from "../interfaces";
import { Terminal } from '@xterm/xterm';
import { FitAddon } from '@xterm/addon-fit';
import { SerializeAddon } from '@xterm/addon-serialize';
export class DebugWidget implements FarpatchWidget {
name: string;
icon: string = "scroll";
title: string = "Debug";
index: number = 0;
view: HTMLElement;
navItem: HTMLElement;
terminal: Terminal;
fitAddon: FitAddon;
serializeAddon: SerializeAddon;
resizeFunction: () => void;
initialized: boolean = false;
constructor(name: string) {
this.name = name;
this.navItem = makeNavView(name, this.icon, this.title);
this.view = document.createElement("div");
this.view.classList.add("terminal");
this.terminal = new Terminal({ theme: { background: "#000000" } });
this.fitAddon = new FitAddon();
this.serializeAddon = new SerializeAddon();
this.resizeFunction = this.resizeTerminal.bind(this);
}
updateIndex(index: number): void {
this.index = index;
}
onInit(): void {
console.log("Initialized Debug Widget");
}
onFocus(element: HTMLElement): void {
console.log("Displaying Debug Widget");
if (!this.initialized) {
// Ensure the parent frame doesn't get any scrollbars, since we're taking up the whole view
element.style.overflow = "hidden";
console.log("Initializing xterm.js");
// var terminalContainer = document.createElement("div");
// this.view.appendChild(terminalContainer);
this.terminal.loadAddon(this.fitAddon);
this.terminal.loadAddon(this.serializeAddon);
this.terminal.onKey((e) => {
console.log("Key pressed: " + e.key);
this.terminal.write(e.key);
if (e.key === '\r') {
this.terminal.write('\n');
}
});
this.terminal.open(this.view);
this.terminal.write('Hello from \x1B[1;3;31mxterm.js\x1B[0m $ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n');
this.initialized = true;
}
element.appendChild(this.view);
window.addEventListener('resize', this.resizeFunction);
window.setTimeout(() => {
this.terminal.focus();
this.resizeFunction();
}, 10);
}
onBlur(element: HTMLElement): void {
console.log("Archiving UART Widget");
element.removeChild(this.view);
window.removeEventListener('resize', this.resizeFunction);
}
// Whenever the window is resized, update the size of the terminal
resizeTerminal() {
this.fitAddon.fit();
}
}

76
src/interface/rtt.ts Normal file
View File

@ -0,0 +1,76 @@
import { FarpatchWidget, makeNavView } from "../interfaces";
import { Terminal } from '@xterm/xterm';
import { FitAddon } from '@xterm/addon-fit';
import { SerializeAddon } from '@xterm/addon-serialize';
export class RttWidget implements FarpatchWidget {
name: string;
icon: string = "microchip";
title: string = "RTT";
index: number = 0;
view: HTMLElement;
navItem: HTMLElement;
terminal: Terminal;
fitAddon: FitAddon;
serializeAddon: SerializeAddon;
resizeFunction: () => void;
initialized: boolean = false;
constructor(name: string) {
this.name = name;
this.navItem = makeNavView(name, this.icon, this.title);
this.view = document.createElement("div");
this.view.classList.add("terminal");
this.terminal = new Terminal({ theme: { background: "#000000" } });
this.fitAddon = new FitAddon();
this.serializeAddon = new SerializeAddon();
this.resizeFunction = this.resizeTerminal.bind(this);
}
updateIndex(index: number): void {
this.index = index;
}
onInit(): void {
console.log("Initialized RTT Widget");
}
onFocus(element: HTMLElement): void {
console.log("Displaying RTT Widget");
if (!this.initialized) {
// Ensure the parent frame doesn't get any scrollbars, since we're taking up the whole view
element.style.overflow = "hidden";
console.log("Initializing xterm.js");
// var terminalContainer = document.createElement("div");
// this.view.appendChild(terminalContainer);
this.terminal.loadAddon(this.fitAddon);
this.terminal.loadAddon(this.serializeAddon);
this.terminal.onKey((e) => {
console.log("Key pressed: " + e.key);
this.terminal.write(e.key);
if (e.key === '\r') {
this.terminal.write('\n');
}
});
this.terminal.open(this.view);
this.terminal.write('Hello from \x1B[1;3;31mxterm.js\x1B[0m $ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n');
this.initialized = true;
}
element.appendChild(this.view);
window.addEventListener('resize', this.resizeFunction);
window.setTimeout(() => {
this.terminal.focus();
this.resizeFunction();
}, 10);
}
onBlur(element: HTMLElement): void {
console.log("Archiving UART Widget");
element.removeChild(this.view);
window.removeEventListener('resize', this.resizeFunction);
}
// Whenever the window is resized, update the size of the terminal
resizeTerminal() {
this.fitAddon.fit();
}
}

57
src/interface/settings.ts Normal file
View File

@ -0,0 +1,57 @@
import { FarpatchWidget, makeNavView as makeNavItem } from "../interfaces";
export class SettingsWidget implements FarpatchWidget {
index: number = 0;
view: HTMLElement = document.createElement("div");
navItem: HTMLElement;
name: string;
icon: string = "sliders-h";
title: string = "Settings";
constructor(name: string) {
this.name = name;
this.navItem = makeNavItem(name, this.icon, this.title);
}
updateIndex(index: number): void {
this.index = index;
}
onInit(): void {
this.view.innerHTML = `
<div class="fieldset-item">
<picture aria-hidden="true">
<svg viewBox="0 0 24 24">
<title>A note icon</title>
<path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"/>
</svg>
</picture>
<div class="input-stack">
<label
for="media-volume"
id="media-volume"
aria-hidden="true">
Media volume
</label>
<input
name="media-volume"
aria-labelledby="media-volume"
type="range"
value="3"
max="10"
style="--track-fill: 30%"
>
</div>
</div>
`;
console.log("Initialized Dashboard Widget");
}
onFocus(element: HTMLElement): void {
console.log("Displaying Dashboard Widget");
element.appendChild(this.view);
}
onBlur(element: HTMLElement): void {
console.log("Archiving Dashboard Widget");
element.removeChild(this.view);
}
}

View File

@ -1,16 +1,25 @@
import { FarpatchWidget } from "../interfaces";
import { FarpatchWidget, makeNavView } from "../interfaces";
import { Terminal } from '@xterm/xterm';
import { FitAddon } from '@xterm/addon-fit';
import { SerializeAddon } from '@xterm/addon-serialize';
export class UartWidget implements FarpatchWidget {
name: string;
icon: string = "keyboard";
title: string = "UART";
index: number = 0;
view: HTMLElement;
navItem: HTMLElement;
terminal: Terminal;
fitAddon: FitAddon;
serializeAddon: SerializeAddon;
resizeFunction: () => void;
initialized: boolean = false;
constructor() {
constructor(name: string) {
this.name = name;
this.navItem = makeNavView(name, this.icon, this.title);
this.view = document.createElement("div");
this.view.classList.add("terminal");
this.terminal = new Terminal({ theme: { background: "#000000" } });
@ -18,6 +27,11 @@ export class UartWidget implements FarpatchWidget {
this.serializeAddon = new SerializeAddon();
this.resizeFunction = this.resizeTerminal.bind(this);
}
updateIndex(index: number): void {
this.index = index;
}
onInit(): void {
console.log("Initialized UART Widget");
}

View File

@ -1,5 +1,57 @@
import { UartWidget } from "./interface/uart";
import { DashboardWidget } from "./interface/dashboard";
import { RttWidget } from "./interface/rtt";
import { DebugWidget } from "./interface/debug";
import { SettingsWidget } from "./interface/settings";
export interface FarpatchWidget {
index: number,
name: string,
view: HTMLElement,
navItem: HTMLElement,
icon: string,
title: string,
updateIndex(index: number): void,
onInit(): void,
onFocus(element: HTMLElement): void,
onBlur(element: HTMLElement): void,
}
export function makeNavView(name: string, icon: string, title: string): HTMLElement {
var navView: HTMLElement = document.createElement("li");
navView.classList.add("sidenav-item");
navView.id = name + "-button";
var navViewLink = document.createElement("a");
navViewLink.classList.add("sidenav-link");
var navViewIcon = document.createElement("span");
navViewIcon.classList.add("las");
navViewIcon.classList.add("la-3x");
navViewIcon.classList.add("la-" + icon);
navViewIcon.classList.add("icon");
var navViewText = document.createElement("span");
navViewText.classList.add("link-text");
navViewText.innerText = title;
navViewLink.appendChild(navViewIcon);
navViewLink.appendChild(navViewText);
navView.appendChild(navViewLink);
return navView;
}
export const farpatchWidgets: FarpatchWidget[] = [
new DashboardWidget("dashboard"),
new UartWidget("uart"),
new RttWidget("rtt"),
new DebugWidget("debug"),
new SettingsWidget("settings"),
];
for (var i = 0; i < farpatchWidgets.length; i++) {
farpatchWidgets[i].updateIndex(i);
}

View File

@ -13,7 +13,7 @@
</head>
<body>
<nav class="sidenav">
<!-- <nav class="sidenav">
<ul class="sidenav-nav">
<li class="sidenav-item" id="dashboard-button">
<a class="sidenav-link">
@ -75,7 +75,7 @@
<div class="main-content" id="settings-view">
<div>[Settings goes here]</div>
</div>
</main>
</main> -->
</body>
</html>