diff --git a/package-lock.json b/package-lock.json index 57234d1..2029da1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "lcd-simulator-electron", - "version": "v0.2.4-alpha", + "version": "v1.0.0-rc1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1410,106 +1410,6 @@ "fs-extra": "^9.0.1" } }, - "@emotion/cache": { - "version": "10.0.29", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.29.tgz", - "integrity": "sha512-fU2VtSVlHiF27empSbxi1O2JFdNWZO+2NFHfwO0pxgTep6Xa3uGb+3pVKfLww2l/IBGLNEZl5Xf/++A4wAYDYQ==", - "dev": true, - "requires": { - "@emotion/sheet": "0.9.4", - "@emotion/stylis": "0.8.5", - "@emotion/utils": "0.11.3", - "@emotion/weak-memoize": "0.2.5" - } - }, - "@emotion/core": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@emotion/core/-/core-10.1.1.tgz", - "integrity": "sha512-ZMLG6qpXR8x031NXD8HJqugy/AZSkAuMxxqB46pmAR7ze47MhNJ56cdoX243QPZdGctrdfo+s08yZTiwaUcRKA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.5.5", - "@emotion/cache": "^10.0.27", - "@emotion/css": "^10.0.27", - "@emotion/serialize": "^0.11.15", - "@emotion/sheet": "0.9.4", - "@emotion/utils": "0.11.3" - } - }, - "@emotion/css": { - "version": "10.0.27", - "resolved": "https://registry.npmjs.org/@emotion/css/-/css-10.0.27.tgz", - "integrity": "sha512-6wZjsvYeBhyZQYNrGoR5yPMYbMBNEnanDrqmsqS1mzDm1cOTu12shvl2j4QHNS36UaTE0USIJawCH9C8oW34Zw==", - "dev": true, - "requires": { - "@emotion/serialize": "^0.11.15", - "@emotion/utils": "0.11.3", - "babel-plugin-emotion": "^10.0.27" - } - }, - "@emotion/hash": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", - "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", - "dev": true - }, - "@emotion/memoize": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", - "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", - "dev": true - }, - "@emotion/serialize": { - "version": "0.11.16", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-0.11.16.tgz", - "integrity": "sha512-G3J4o8by0VRrO+PFeSc3js2myYNOXVJ3Ya+RGVxnshRYgsvErfAOglKAiy1Eo1vhzxqtUvjCyS5gtewzkmvSSg==", - "dev": true, - "requires": { - "@emotion/hash": "0.8.0", - "@emotion/memoize": "0.7.4", - "@emotion/unitless": "0.7.5", - "@emotion/utils": "0.11.3", - "csstype": "^2.5.7" - }, - "dependencies": { - "csstype": { - "version": "2.6.17", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.17.tgz", - "integrity": "sha512-u1wmTI1jJGzCJzWndZo8mk4wnPTZd1eOIYTYvuEyOQGfmDl3TrabCCfKnOC86FZwW/9djqTl933UF/cS425i9A==", - "dev": true - } - } - }, - "@emotion/sheet": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-0.9.4.tgz", - "integrity": "sha512-zM9PFmgVSqBw4zL101Q0HrBVTGmpAxFZH/pYx/cjJT5advXguvcgjHFTCaIO3enL/xr89vK2bh0Mfyj9aa0ANA==", - "dev": true - }, - "@emotion/stylis": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", - "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==", - "dev": true - }, - "@emotion/unitless": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", - "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==", - "dev": true - }, - "@emotion/utils": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.11.3.tgz", - "integrity": "sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw==", - "dev": true - }, - "@emotion/weak-memoize": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz", - "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==", - "dev": true - }, "@eslint/eslintrc": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.2.tgz", @@ -3142,6 +3042,12 @@ } } }, + "@types/uuid": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.1.tgz", + "integrity": "sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg==", + "dev": true + }, "@types/verror": { "version": "1.10.4", "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.4.tgz", @@ -4269,24 +4175,6 @@ "object.assign": "^4.1.0" } }, - "babel-plugin-emotion": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-10.2.2.tgz", - "integrity": "sha512-SMSkGoqTbTyUTDeuVuPIWifPdUGkTk1Kf9BWRiXIOIcuyMfsdp2EjeiiFvOzX8NOBvEh/ypKYvUh2rkgAJMCLA==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.0.0", - "@emotion/hash": "0.8.0", - "@emotion/memoize": "0.7.4", - "@emotion/serialize": "^0.11.16", - "babel-plugin-macros": "^2.0.0", - "babel-plugin-syntax-jsx": "^6.18.0", - "convert-source-map": "^1.5.0", - "escape-string-regexp": "^1.0.5", - "find-root": "^1.1.0", - "source-map": "^0.5.7" - } - }, "babel-plugin-istanbul": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz", @@ -4382,12 +4270,6 @@ "@babel/helper-define-polyfill-provider": "^0.2.2" } }, - "babel-plugin-syntax-jsx": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", - "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=", - "dev": true - }, "babel-plugin-syntax-object-rest-spread": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", @@ -7184,16 +7066,6 @@ "utila": "~0.4" } }, - "dom-helpers": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", - "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.8.7", - "csstype": "^3.0.2" - } - }, "dom-serializer": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", @@ -9244,12 +9116,6 @@ "pkg-dir": "^3.0.0" } }, - "find-root": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", - "dev": true - }, "find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -16415,34 +16281,6 @@ "workbox-webpack-plugin": "5.1.4" } }, - "react-toast-notifications": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/react-toast-notifications/-/react-toast-notifications-2.4.4.tgz", - "integrity": "sha512-FNekr4IIeZZ+9B7LO4Wdqfp16jX6yH6A3HMajbPHpfPwhmcqIX///wPhdbcef9bQaa+NZwdyCHKeCSC6eFnduw==", - "dev": true, - "requires": { - "@emotion/core": "^10.0.14", - "react-transition-group": "^4.4.1" - } - }, - "react-transition-group": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz", - "integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.5.5", - "dom-helpers": "^5.0.1", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2" - } - }, - "react-windows-ui": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/react-windows-ui/-/react-windows-ui-2.0.7.tgz", - "integrity": "sha512-HGsWzforvfcJlAG4WSfThxCp4lvMWeF1ffBh9WOsOQArgOa/zkAAi4ivy3X1GqQFQQhskLgmgxx9wfgwy4ZlJQ==", - "dev": true - }, "read-config-file": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/read-config-file/-/read-config-file-6.0.0.tgz", @@ -19482,9 +19320,7 @@ "uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "optional": true + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" }, "v8-compile-cache": { "version": "2.3.0", diff --git a/package.json b/package.json index f5dcb8c..4b1d6fc 100644 --- a/package.json +++ b/package.json @@ -39,12 +39,14 @@ "@types/node": "^14.17.3", "@types/react": "^17.0.9", "@types/react-dom": "^17.0.6", + "@types/uuid": "^8.3.1", "concurrently": "^5.3.0", "cross-env": "^7.0.3", "electron": "^13.0.0", "electron-builder": "^22.10.5", "electron-devtools-installer": "^3.2.0", "electronmon": "^1.1.2", + "uuid": "^8.3.2", "react": "^17.0.2", "react-dom": "^17.0.2", "react-hot-toast": "^2.1.1", diff --git a/public/electron.js b/public/electron.js index c8e37ce..d095334 100644 --- a/public/electron.js +++ b/public/electron.js @@ -1,4 +1,4 @@ -const { app, BrowserWindow } = require("electron"); +const { app, BrowserWindow, shell } = require("electron"); const isDev = require("electron-is-dev"); const path = require("path"); @@ -41,6 +41,11 @@ function createWindow() { } else { callback(selectedPort.portId); } + }); + + mainWindow.webContents.setWindowOpenHandler(({url}) => { + shell.openExternal(url); + return { action: 'deny' }; }) // Load from localhost if in development diff --git a/src/App.css b/src/App.css index b3b2fb8..9e4e76d 100644 --- a/src/App.css +++ b/src/App.css @@ -4,7 +4,6 @@ } html { - overflow-y: overlay; background-color: rgb(223, 223, 223); } diff --git a/src/App.tsx b/src/App.tsx index 7090cf2..4466639 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,13 +2,13 @@ import { useEffect, useState, useRef, useCallback } from 'react'; import { CommandParser, LCDCommand, - isDebugNumberCommand, - isDebugTextCommand, - DebugNumberCommand, - DebugTextCommand, + isDisplayCommand, + isDebugCommand, + DebugCommand, + DisplayCommand, } from './classes/CommandParser'; import { DebugCommandView } from './components/DebugCommandView'; -import { PrimaryButton, PivotItem, Pivot, Text, AnimationStyles } from '@fluentui/react'; +import { PrimaryButton, PivotItem, Pivot, Text, AnimationStyles, Icon } from '@fluentui/react'; import { DisplayCommandView } from './components/DisplayCommandView'; import "./App.css"; import "./Fonts.css"; @@ -21,15 +21,15 @@ import { Startup } from './components/Startup'; function App() { const [serialPort, setSerialPort] = useState(); - const [debugCommands, setDebugCommands] = useState<(DebugNumberCommand | DebugTextCommand)[]>([]); - const commands = useRef([]); + const [debugCommands, setDebugCommands] = useState([]); + const commands = useRef([]); const [connected, setConnected] = useState(false); const lcdRef = useRef(); const readerRef = useRef>(); const writerRef = useRef>(); - const handleCOMPortSelection = async () => { + const handleCOMPortSelection = useCallback(async () => { try { const serialPort = await navigator.serial.requestPort(); setConnected(true); @@ -39,15 +39,15 @@ function App() { setConnected(false); toast.error("No microcontroller found!"); } - } + }, []); const openSerialPort = useCallback(async (serialPort: SerialPort) => { try { await serialPort.open({ baudRate: 460800, parity: "none", stopBits: 1, dataBits: 8, flowControl: "none" }); while (serialPort.readable && serialPort.writable) { const commandParser = new CommandParser(); - const reader = await serialPort.readable.getReader(); - const writer = await serialPort.writable.getWriter(); + const reader = serialPort.readable.getReader(); + const writer = serialPort.writable.getWriter(); readerRef.current = reader; writerRef.current = writer; @@ -55,15 +55,16 @@ function App() { commandParser.onNewCommand = (command: LCDCommand) => { if (!lcdRef.current) return; const lcdManager = lcdRef.current; - if (isDebugNumberCommand(command) || isDebugTextCommand(command)) { - if (debugCommands.length < 200) setDebugCommands(state => state.concat([command])); - } else { + if (isDebugCommand(command)) { + setDebugCommands(state => state.concat([command])); + } + if (isDisplayCommand(command)) { commands.current.push(command); lcdManager.executeCommand(command); } }; - const ack = new Uint8Array([7]); + const acknowledge = new Uint8Array([7]); try { while (true) { @@ -74,7 +75,7 @@ function App() { break; } if (value) { - writer.write(ack); + writer.write(acknowledge); commandParser.parseValue(value); } } @@ -83,7 +84,7 @@ function App() { reader.releaseLock(); writer.releaseLock(); serialPort.close(); - } catch (e) { }; + } catch (error) { }; setSerialPort(undefined); setConnected(false); toast.error("Lost connection to microcontroller!"); @@ -92,12 +93,12 @@ function App() { } catch (error) { try { serialPort.close(); - } catch (e) { }; + } catch (error) { }; setSerialPort(undefined); setConnected(false); toast.error("Could not establish a connection with the microcontroller."); } - }, [debugCommands.length]); + }, []); useEffect(() => { if (!serialPort) return; @@ -107,14 +108,20 @@ function App() { return () => { } }, [serialPort, openSerialPort]); - const clearAll = () => { + const clearAll = useCallback(() => { if (!lcdRef.current) return; const lcdManager = lcdRef.current; commands.current.length = 0; setDebugCommands(() => []); lcdManager.clearLines(); lcdManager.commandsReceived = 0; - } + }, []); + + const handleHistory = useCallback((command: DisplayCommand | undefined) => { + if(!lcdRef.current) return; + if (command === undefined) return lcdRef.current.hideHistory(); + lcdRef.current.showHistory(command); + }, []); return ( <> @@ -129,15 +136,23 @@ function App() { Open COM Port + + + +

+ If you can't connect to the microcontroller at all you probably have to install the FTDI drivers which you can find by clicking this link. +

+
+
- + - setDebugCommands(() => [])} clearAll={clearAll} commands={debugCommands} /> + - - commands.current.length = 0} clearAll={clearAll} commands={commands.current} /> + + diff --git a/src/classes/CommandParser/CommandParser.ts b/src/classes/CommandParser/CommandParser.ts index 3a36412..cac5496 100644 --- a/src/classes/CommandParser/CommandParser.ts +++ b/src/classes/CommandParser/CommandParser.ts @@ -85,7 +85,7 @@ export class CommandParser { let text: string, row: number, column: number, - data: string, + columnData: number[] = [], mode: DisplayCommandModes; switch (this.commandBuffer[1]) { case DISPLAY_COMMAND.GLCD_DISPLAYCHARS: @@ -94,34 +94,33 @@ export class CommandParser { text += String.fromCharCode(this.commandBuffer[i]); } - return new DisplayCharCommand(text, "normal"); + return new DisplayCharCommand(text, "normal", "DisplayChars"); case DISPLAY_COMMAND.GLCD_SETTEXTCURSOR: row = this.commandBuffer[3]; column = this.commandBuffer[4]; - return new DisplaySetCursorCommand(row, column); + return new DisplaySetCursorCommand(row, column, "SetTextCursor"); case DISPLAY_COMMAND.GLCD_SETROW: row = this.commandBuffer[3]; - return new DisplaySetCursorCommand(row, null); + return new DisplaySetCursorCommand(row, null, "SetRow"); case DISPLAY_COMMAND.GLCD_SETCOLUMN: column = this.commandBuffer[3]; - return new DisplaySetCursorCommand(null, column); + return new DisplaySetCursorCommand(null, column, "SetColumn"); case DISPLAY_COMMAND.GLCD_PRINTCOLUMN: - data = String.fromCharCode(this.commandBuffer[3]); + columnData.push(this.commandBuffer[3]); - return new DisplayPrintColumnCommand(data); + return new DisplayPrintColumnCommand(columnData, "PrintColumn"); case DISPLAY_COMMAND.GLCD_PRINTMULCOLUMN: row = this.commandBuffer[3]; column = this.commandBuffer[4]; - data = ""; for (let i = 5; i < this.commandBuffer.length; i++) { - data += String.fromCharCode(this.commandBuffer[i]); + columnData.push(this.commandBuffer[i]); } - return new DisplayPrintMulColumnCommand(data, row, column); + return new DisplayPrintMulColumnCommand(columnData, row, column, "PrintMulColumn"); case DISPLAY_COMMAND.GLCD_PRINTTEXT: case DISPLAY_COMMAND.GLCD_PRINTTEXTINVERS: mode = this.commandBuffer[1] === DISPLAY_COMMAND.GLCD_PRINTTEXT ? "normal" : "inverse"; row = this.commandBuffer[3]; @@ -132,19 +131,19 @@ export class CommandParser { text += String.fromCharCode(this.commandBuffer[i]); } - return new DisplayTextCommand(text, row, column, mode); + return new DisplayTextCommand(text, row, column, mode, "PrintText" + (mode === "normal" ? "" : " (inverse)")); case DISPLAY_COMMAND.GLCD_PRINTCHAR: case DISPLAY_COMMAND.GLCD_PRINTCHARINVERS: mode = this.commandBuffer[1] === DISPLAY_COMMAND.GLCD_PRINTCHAR ? "normal" : "inverse"; text = String.fromCharCode(this.commandBuffer[3]); - return new DisplayCharCommand(text, mode); + return new DisplayCharCommand(text, mode, "PrintChar" + (mode === "normal" ? "" : " (inverse)")); case DISPLAY_COMMAND.GLCD_PRINTGRAFIKLINE: // NOT IMPLEMENTED - return new DisplayGraphicLine(0, ""); + return new DisplayGraphicLine(0, [], "PrintGrafikLine"); case DISPLAY_COMMAND.GLCD_CLEARROW: row = this.commandBuffer[3]; - return new DisplayClearRowCommand(row); + return new DisplayClearRowCommand(row, "ClearRow"); case DISPLAY_COMMAND.GLCD_CLEARLCD: - return new DisplayClearCommand(); + return new DisplayClearCommand("ClearLCD"); default: toast.error("Received command not recognized! (For more info check developer console)"); // DEBUG console.log(this.commandBuffer); diff --git a/src/classes/CommandParser/DebugCommands.ts b/src/classes/CommandParser/DebugCommands.ts index 565ca0b..7895ed0 100644 --- a/src/classes/CommandParser/DebugCommands.ts +++ b/src/classes/CommandParser/DebugCommands.ts @@ -1,13 +1,35 @@ +import { fillZeroes } from "../../utils"; import { LCDCommand } from "./LCDCommand"; +export abstract class DebugCommand extends LCDCommand { + abstract printText: () => string; + abstract printNumber: () => string; +} + +export const isDebugCommand = (command: LCDCommand): command is DebugCommand => { + return (command as DebugCommand).printText !== undefined; +} + export type DebugTextModes = "normal" | "error" | "ok"; -export class DebugTextCommand extends LCDCommand { +export class DebugTextCommand extends DebugCommand { mode: DebugTextModes; text: string; + printText = () => { + let icon = ""; + if (this.mode === "error") { + icon = "❌ "; + } + if (this.mode === "ok") { + icon = "✔ "; + } + return icon + this.text; + }; + printNumber = () => ""; + constructor(text: string, mode: DebugTextModes) { - super("DebugTextCommand"); + super("DebugTextCommand", "DebugText"); this.text = text; this.mode = mode; } @@ -22,13 +44,33 @@ export type DebugNumberModes = "u8bin" | "u16bin" | "u32bin" | "u8dez" | "u16dez"; -export class DebugNumberCommand extends LCDCommand { +export class DebugNumberCommand extends DebugCommand { mode: DebugNumberModes; text: string; number: number; + printText = () => this.text; + printNumber = () => { + if (this.mode === "u8hex") { + return `0x${fillZeroes(this.number.toString(16), 2)}`; + } + if (this.mode === "u16hex") { + return `0x${fillZeroes(this.number.toString(16), 4)}`; + } + if (this.mode === "u32hex") { + return `0x${fillZeroes(this.number.toString(16), 8)}`; + } + if (this.mode === "u8bin") { + return `0b${fillZeroes(this.number.toString(2), 8)}`; + } + if (this.mode === "u16bin") { + return `0b${fillZeroes(this.number.toString(2), 16)}`; + } + return this.number.toString(); + }; + constructor(text: string, number: number, mode: DebugNumberModes) { - super("DebugNumberCommand"); + super("DebugNumberCommand", "DebugNumber"); this.mode = mode; this.text = text; diff --git a/src/classes/CommandParser/DisplayCommands.ts b/src/classes/CommandParser/DisplayCommands.ts index 1f04c36..2703747 100644 --- a/src/classes/CommandParser/DisplayCommands.ts +++ b/src/classes/CommandParser/DisplayCommands.ts @@ -1,15 +1,29 @@ +import { fillZeroes } from "../../utils"; import { LCDCommand } from "./LCDCommand"; +export abstract class DisplayCommand extends LCDCommand { + abstract print: () => string; + view: Uint8ClampedArray = new Uint8ClampedArray(); + cursorRow: number = 0; + cursorColumn: number = 0; +} + +export const isDisplayCommand = (command: LCDCommand): command is DisplayCommand => { + return (command as DisplayCommand).cursorColumn !== undefined; +} + export type DisplayCommandModes = "normal" | "inverse"; -export class DisplayTextCommand extends LCDCommand { +export class DisplayTextCommand extends DisplayCommand { mode: DisplayCommandModes; row: number; column: number; text: string; - constructor(text: string, row: number, column: number, mode: DisplayCommandModes) { - super("DisplayTextCommand"); + print = () => `text: ${this.text}\nposition: (${this.row}, ${this.column})`; + + constructor(text: string, row: number, column: number, mode: DisplayCommandModes, initialCommand: string) { + super("DisplayTextCommand", initialCommand); this.text = text; this.row = row; this.column = column; @@ -21,13 +35,15 @@ export const isDisplayTextCommand = (command: LCDCommand): command is DisplayTex return command.type === "DisplayTextCommand"; } -export class DisplayPrintMulColumnCommand extends LCDCommand { +export class DisplayPrintMulColumnCommand extends DisplayCommand { row: number; column: number; - data: string; + data: number[]; + + print = () => `data: [${this.data.map(d => fillZeroes(d.toString(16), 2)).join(", ")}]`; - constructor(data: string, row: number, column: number) { - super("DisplayPrintMulColumnCommand") + constructor(data: number[], row: number, column: number, initialCommand: string) { + super("DisplayPrintMulColumnCommand", initialCommand) this.data = data; this.row = row; this.column = column; @@ -38,11 +54,13 @@ export const isDisplayPrintMulColumnCommand = (command: LCDCommand): command is return command.type === "DisplayPrintMulColumnCommand"; } -export class DisplayPrintColumnCommand extends LCDCommand { - data: string; +export class DisplayPrintColumnCommand extends DisplayCommand { + data: number[]; - constructor(data: string) { - super("DisplayPrintColumnCommand") + print = () => `data: [${this.data.map(d => fillZeroes(d.toString(16), 2)).join(", ")}]`; + + constructor(data: number[], initialCommand: string) { + super("DisplayPrintColumnCommand", initialCommand) this.data = data; } } @@ -51,12 +69,14 @@ export const isDisplayPrintColumnCommand = (command: LCDCommand): command is Dis return command.type === "DisplayPrintColumnCommand"; } -export class DisplaySetCursorCommand extends LCDCommand { +export class DisplaySetCursorCommand extends DisplayCommand { row: number | null; column: number | null; - constructor(row: number | null, column: number | null) { - super("DisplaySetCursorCommand"); + print = () => `position: (${this.row ?? "inherit"}, ${this.column ?? "inherit"})`; + + constructor(row: number | null, column: number | null, initialCommand: string) { + super("DisplaySetCursorCommand", initialCommand); this.row = row; this.column = column; } @@ -66,9 +86,11 @@ export const isDisplaySetCursorCommand = (command: LCDCommand): command is Displ return command.type === "DisplaySetCursorCommand"; } -export class DisplayClearCommand extends LCDCommand { - constructor() { - super("DisplayClearCommand"); +export class DisplayClearCommand extends DisplayCommand { + print = () => "clear all"; + + constructor(initialCommand: string) { + super("DisplayClearCommand", initialCommand); } } @@ -76,12 +98,14 @@ export const isDisplayClearCommand = (command: LCDCommand): command is DisplayCl return command.type === "DisplayClearCommand"; } -export class DisplayCharCommand extends LCDCommand { +export class DisplayCharCommand extends DisplayCommand { mode: DisplayCommandModes; text: string; - constructor(text: string, mode: DisplayCommandModes) { - super("DisplayCharCommand"); + print = () => `text: ${this.text}`; + + constructor(text: string, mode: DisplayCommandModes, initialCommand: string) { + super("DisplayCharCommand", initialCommand); this.text = text; this.mode = mode; } @@ -91,11 +115,13 @@ export const isDisplayCharCommand = (command: LCDCommand): command is DisplayCha return command.type === "DisplayCharCommand"; } -export class DisplayClearRowCommand extends LCDCommand { +export class DisplayClearRowCommand extends DisplayCommand { row: number; - constructor(row: number) { - super("DisplayClearRowCommand"); + print = () => `cleared row: ${this.row}`; + + constructor(row: number, initialCommand: string) { + super("DisplayClearRowCommand", initialCommand); this.row = row; } } @@ -104,12 +130,14 @@ export const isDisplayClearRowCommand = (command: LCDCommand): command is Displa return command.type === "DisplayClearRowCommand"; }; -export class DisplayGraphicLine extends LCDCommand { +export class DisplayGraphicLine extends DisplayCommand { colCount: number; - data: string; + data: number[]; + + print = () => `data: [${this.data.map(d => d.toString(16)).join(", ")}]`; - constructor(colCount: number, data: string) { - super("DisplayGraphicLine"); + constructor(colCount: number, data: number[], initialCommand: string) { + super("DisplayGraphicLine", initialCommand); this.colCount = colCount; this.data = data; diff --git a/src/classes/CommandParser/LCDCommand.ts b/src/classes/CommandParser/LCDCommand.ts index fc95465..a6e7ccd 100644 --- a/src/classes/CommandParser/LCDCommand.ts +++ b/src/classes/CommandParser/LCDCommand.ts @@ -1,10 +1,25 @@ +import { v4 as uuidv4 } from 'uuid'; +import { fillZeroes } from '../../utils'; + export class LCDCommand { type: CommandTypes; timestamp: Date; + initialCommand: string; + id: string; - constructor(type: CommandTypes) { + constructor(type: CommandTypes, initialCommand: string) { this.type = type; this.timestamp = new Date(); + this.initialCommand = initialCommand; + this.id = uuidv4(); + } + + printTime() { + return this.timestamp.toLocaleTimeString() + ":" + fillZeroes("" + this.timestamp.getMilliseconds(), 3); + } + + getKey() { + return this.id; } } diff --git a/src/classes/WebGLLCDRenderer/Block.ts b/src/classes/WebGLLCDRenderer/Block.ts deleted file mode 100644 index b9d3a12..0000000 --- a/src/classes/WebGLLCDRenderer/Block.ts +++ /dev/null @@ -1,17 +0,0 @@ -export class Block { - constructor(data: Uint8ClampedArray) { - - } - - set(x: number, y: number) { - - }; - - get(x: number, y: number) { - - }; - - fromByteArray() { - - }; -}; \ No newline at end of file diff --git a/src/classes/WebGLLCDRenderer/WebGLLCDRenderer.ts b/src/classes/WebGLLCDRenderer/WebGLLCDRenderer.ts index 6ebdffe..03ae90e 100644 --- a/src/classes/WebGLLCDRenderer/WebGLLCDRenderer.ts +++ b/src/classes/WebGLLCDRenderer/WebGLLCDRenderer.ts @@ -3,12 +3,13 @@ import { LCDCharBuffer } from "./LCDCharBuffer"; import toast from 'react-hot-toast'; import { frag } from './frag'; import { vert } from './vert'; -import { isDisplayCharCommand, isDisplayClearCommand, isDisplayTextCommand, LCDCommand } from "../CommandParser"; +import { isDisplayCharCommand, isDisplayClearCommand, isDisplayTextCommand, DisplayCommand } from "../CommandParser"; import { isDisplayClearRowCommand, isDisplayGraphicLine, isDisplayPrintColumnCommand, isDisplayPrintMulColumnCommand, isDisplaySetCursorCommand } from "../CommandParser/DisplayCommands"; export class WebGLLCDRenderer { readonly gl: WebGL2RenderingContext; readonly view: Uint8ClampedArray; + readonly viewBuffer: Uint8ClampedArray; readonly texture: WebGLTexture | null; readonly width: number; readonly height: number; @@ -22,14 +23,18 @@ export class WebGLLCDRenderer { dirty: boolean = false; raf: number = -1; commandsReceived: number = 0; - onReceiveCommand: () => void = () => { }; + showingHistory: boolean = false; + onReceiveCommand: (command: DisplayCommand) => void = () => { }; onDraw: () => void = () => {}; constructor(gl: WebGL2RenderingContext, width: number, height: number, charBuffer: LCDCharBuffer) { this.gl = gl; this.width = width; this.height = height; - this.view = new Uint8ClampedArray(width * height); + + this.view = new Uint8ClampedArray(this.width * this.height); + this.viewBuffer = new Uint8ClampedArray(this.width * this.height); + this.charBuffer = charBuffer; this.texture = gl.createTexture(); @@ -78,13 +83,14 @@ export class WebGLLCDRenderer { this.vertexBuffer = vertexBuffer; this.shaderProgram = shaderProgram; + + this.dirty = true; } setCursor(row: number | null, column: number | null) { this.cursorRow = row ?? this.cursorRow; this.cursorColumn = column ?? this.cursorColumn; - this.onReceiveCommand(); this.commandsReceived++; } @@ -99,7 +105,6 @@ export class WebGLLCDRenderer { this.dirty = true; - this.onReceiveCommand(); this.commandsReceived++; } @@ -109,46 +114,77 @@ export class WebGLLCDRenderer { setBlockData(block: Uint8ClampedArray, row: number, column: number, width: number, height: number, mode: "normal" | "inverse" = "normal") { let index = 0; for (let y = row; y < row + 8; y++) { + if (y > this.height) break; for (let x = column; x < column + 6; x++) { + if (x > this.width) continue; let color = 0; if (x < width + column && y < height + row) { color = block[index++] ?? 0; } const i = x + y * this.width; if (mode === "inverse") color = 255 - color; - this.view[i] = color; + this.viewBuffer[i] = color; } } } + setColumnData(column: number[]) { + let index = 0; + for (let x = 0; x < column.length; x++) { + for (let y = this.cursorRow * 8; y < this.cursorRow * 8 + 8; y++) { + if (y > this.height) break; + this.viewBuffer[this.cursorColumn * 6 + (y + x * 8) * this.width] = ((column[x] >> index++) & 0x01) * 255; + } + index = 0; + } + + this.dirty = true; + } + insertText(text: string, mode: "normal" | "inverse" = "normal") { this.insertTextAt(text, this.cursorRow, this.cursorColumn, mode); } - clearLines() { + clearLines(buffer: Uint8ClampedArray = new Uint8ClampedArray(this.width * this.height)) { for (let i = 0; i < this.view.length; i++) { - this.view[i] = 0; - } + this.viewBuffer[i] = buffer[i]; + }; this.dirty = true; - this.onReceiveCommand(); this.commandsReceived++; } clearRow(row: number) { this.dirty = true; - for (let j = 0; j < 8; j++) { + for (let j = 1; j < 9; j++) { for (let i = 0; i < this.width; i++) { - this.view[row * 8 + 1 + i + j * this.width] = 0; + this.viewBuffer[row * 8 + i + j * this.width] = 0; } } - this.onReceiveCommand(); this.commandsReceived++; } + showHistory(command: DisplayCommand) { + this.showingHistory = true; + + for (let i = 0; i < this.view.length; i++) { + this.view[i] = command.view[i]; + } + + this.cursorRow = command.cursorRow; + this.cursorColumn = command.cursorColumn; + + this.dirty = true; + } + + hideHistory() { + this.showingHistory = false; + this.dirty = true; + } + startTicker() { this.draw(); this.raf = window.requestAnimationFrame(() => this.startTicker()); @@ -161,6 +197,13 @@ export class WebGLLCDRenderer { draw() { if (!this.dirty) return; + + if (!this.showingHistory) { + for (let i = 0; i < this.view.length; i++) { + this.view[i] = this.viewBuffer[i]; + } + } + const gl = this.gl; gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, this.width, this.height, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, this.view); gl.generateMipmap(gl.TEXTURE_2D); @@ -169,38 +212,57 @@ export class WebGLLCDRenderer { this.dirty = false; } - executeCommand(command: LCDCommand): void { + executeCommand(command: DisplayCommand): void { const type = command.type; if (isDisplayCharCommand(command)) { - return this.insertText(command.text, command.mode ?? "normal"); + this.insertText(command.text, command.mode ?? "normal"); }; if (isDisplayPrintColumnCommand(command)) { - // NOT IMPLEMENTED + this.setColumnData(command.data); }; if (isDisplayPrintMulColumnCommand(command)) { - // NOT IMPLEMENTED + this.setColumnData(command.data); }; if (isDisplaySetCursorCommand(command)) { - return this.setCursor(command.row, command.column); + this.setCursor(command.row, command.column); }; if (isDisplayTextCommand(command)) { - return this.insertTextAt(command.text, command.row, command.column, command.mode ?? "normal"); + this.insertTextAt(command.text, command.row, command.column, command.mode ?? "normal"); }; if (isDisplayClearRowCommand(command)) { - return this.clearRow(command.row); + this.clearRow(command.row); }; if (isDisplayGraphicLine(command)) { // NOT IMPLEMENTED + toast.error(type + " not implented in WebGLLCDRenderer!"); }; if (isDisplayClearCommand(command)) { - return this.clearLines(); + this.clearLines(); }; - - toast.error(type + " not implented in WebGLLCDRenderer!"); - console.log(command); + + command.cursorColumn = this.cursorColumn; + command.cursorRow = this.cursorRow; + command.view = new Uint8ClampedArray(this.viewBuffer); + + this.onReceiveCommand(command); + return; } + reset() { + for(let i = 0; i < this.view.length; i++) { + this.view[i] = 0; + this.viewBuffer[i] = 0; + } + + this.cursorColumn = 0; + this.cursorRow = 0; + this.commandsReceived = 0; + this.showingHistory = false; + + this.dirty = true; + } + destroy() { this.stopTicker(); this.gl.deleteProgram(this.shaderProgram); diff --git a/src/components/Card.tsx b/src/components/Card.tsx index 9ac7788..5a48d40 100644 --- a/src/components/Card.tsx +++ b/src/components/Card.tsx @@ -7,6 +7,7 @@ export const Card: FunctionComponent diff --git a/src/components/Cursor.tsx b/src/components/Cursor.tsx new file mode 100644 index 0000000..967fca3 --- /dev/null +++ b/src/components/Cursor.tsx @@ -0,0 +1,30 @@ +import { forwardRef, useEffect, useState} from "react"; + +export const Cursor = forwardRef((_, ref) => { + const [visible, setVisible] = useState(false); + + useEffect(() => { + const handle = window.setInterval(() => { + setVisible(state => !state) + }, 500); + + return () => window.clearInterval(handle); + }, []); + + return ( +
+
+ ) +}); \ No newline at end of file diff --git a/src/components/CustomToaster.tsx b/src/components/CustomToaster.tsx new file mode 100644 index 0000000..a01044f --- /dev/null +++ b/src/components/CustomToaster.tsx @@ -0,0 +1,51 @@ +import { AnimationStyles, mergeStyles, MessageBar, MessageBarType } from "@fluentui/react"; +import { useEffect } from "react"; +import toast, { resolveValue, Toaster, useToasterStore } from "react-hot-toast"; + +export const CustomToaster = () => { + const { toasts } = useToasterStore(); + + const TOAST_LIMIT = 3; + + useEffect(() => { + toasts + .filter(t => t.visible) + .filter((_, index) => index >= TOAST_LIMIT) + .forEach(t => toast.dismiss(t.id)); + }, [toasts]); + + return ( + + {(t) => { + const types = { + "error": MessageBarType.error, + "success": MessageBarType.success, + "loading": MessageBarType.blocked, + "blank": MessageBarType.info, + "custom": MessageBarType.info + }; + return ( + { + toast.dismiss(t.id); + }} + > + {resolveValue(t.message, t)} + + ) + }} + + ); +}; \ No newline at end of file diff --git a/src/components/DebugCommandView.tsx b/src/components/DebugCommandView.tsx index ab6ae98..d461ed1 100644 --- a/src/components/DebugCommandView.tsx +++ b/src/components/DebugCommandView.tsx @@ -1,50 +1,11 @@ -import { CommandBar, ICommandBarItemProps, DetailsList, IColumn, SelectionMode } from "@fluentui/react"; +import { DetailsList, IColumn, SelectionMode, Text } from "@fluentui/react"; import { FunctionComponent, useState } from "react"; -import { DebugNumberCommand, DebugNumberModes, DebugTextCommand, isDebugNumberCommand, isDebugTextCommand } from "../classes/CommandParser"; -import { printTime, fillZeroes } from '../utils'; +import { DebugCommand } from "../classes/CommandParser"; import { Card } from './Card'; -const printNumber = (number: number, mode: DebugNumberModes) => { - if (mode === "u8hex") { - return `0x${fillZeroes(number.toString(16), 2)}`; - } - if (mode === "u16hex") { - return `0x${fillZeroes(number.toString(16), 4)}`; - } - if (mode === "u32hex") { - return `0x${fillZeroes(number.toString(16), 8)}`; - } - if (mode === "u8bin") { - return `0b${fillZeroes(number.toString(2), 8)}`; - } - if (mode === "u16bin") { - return `0b${fillZeroes(number.toString(2), 16)}`; - } - return number.toString(); -} - -export const DebugCommandView: FunctionComponent<{ commands: (DebugTextCommand | DebugNumberCommand)[], clear: () => void, clearAll: () => void }> = ({ commands, clear, clearAll }) => { +export const DebugCommandView: FunctionComponent<{ commands: DebugCommand[]}> = ({ commands }) => { const [reverse, setReverse] = useState(true); - const commandsCopy = reverse ? commands.slice().reverse() : commands.slice(); - - const commandBarItems: ICommandBarItemProps[] = [ - { - key: "clearDebug", - text: "Clear debug log", - iconProps: { - iconName: "Clear" - }, - onClick: clear - }, - { - key: "clearAll", - text: "Clear all", - iconProps: { - iconName: "Clear" - }, - onClick: clearAll - } - ]; + const commandsCopy = reverse ? [...commands].reverse() : [...commands]; const columns: IColumn[] = [ { @@ -56,7 +17,7 @@ export const DebugCommandView: FunctionComponent<{ commands: (DebugTextCommand | data: "timestamp", isSorted: true, isSortedDescending: !reverse, - onRender: (command: DebugTextCommand | DebugNumberCommand) => printTime(command.timestamp), + onRender: (command: DebugCommand) => command.printTime(), onColumnClick: () => setReverse(state => !state) }, { @@ -67,21 +28,7 @@ export const DebugCommandView: FunctionComponent<{ commands: (DebugTextCommand | data: "string", fieldName: "text", isMultiline: true, - onRender: (command: DebugTextCommand | DebugNumberCommand) => { - if (isDebugTextCommand(command)) { - let color = ""; - let icon = ""; - if (command.mode === "error") { - icon = "❌ "; - color = "darkred"; - } - if (command.mode === "ok") { - icon = "✔ "; - color = "darkgreen"; - } - return {icon + command.text} - } - } + onRender: (command: DebugCommand) => command.printText() }, { key: "number", @@ -89,25 +36,29 @@ export const DebugCommandView: FunctionComponent<{ commands: (DebugTextCommand | data: "number", minWidth: 50, fieldName: "number", - onRender: (command: DebugTextCommand | DebugNumberCommand) => { - if (isDebugNumberCommand(command)) { - return printNumber(command.number, command.mode); - } - } + isMultiline: true, + onRender: (command: DebugCommand) => command.printNumber() } - ] + ]; return ( -
+
item.id} + setKey="id" + isPlaceholderData /> + { + commandsCopy.length === 0 ? ( +

We haven't received anything yet! 😢

+ ) : null + }
- ) } \ No newline at end of file diff --git a/src/components/DisplayCommandView.tsx b/src/components/DisplayCommandView.tsx index 328bc00..4db86b0 100644 --- a/src/components/DisplayCommandView.tsx +++ b/src/components/DisplayCommandView.tsx @@ -1,54 +1,34 @@ -import { CommandBar, DetailsList, IColumn, ICommandBarItemProps, SelectionMode } from "@fluentui/react"; -import { useEffect } from "react"; +import { DetailsList, DetailsListLayoutMode, IColumn, SelectionMode, Selection, PrimaryButton, Text } from "@fluentui/react"; +import { Fragment, useCallback, useEffect } from "react"; import { FunctionComponent, useState } from "react"; -import { LCDCommand } from "../classes/CommandParser"; -import { printTime } from "../utils"; +import { DisplayCommand } from "../classes/CommandParser"; import { Card } from "./Card"; +import { Tooltip } from './Tooltip'; -export const DisplayCommandView: FunctionComponent<{ commands: LCDCommand[], clear: () => void, clearAll: () => void }> = ({ commands, clear, clearAll }) => { +export const DisplayCommandView: FunctionComponent<{ commands: DisplayCommand[], showHistory: (command: DisplayCommand | undefined) => void }> = ({ commands, showHistory }) => { const [reverse, setReverse] = useState(true); - const [commandsCopy, setCommandsCopy] = useState([]); + const [commandsCopy, setCommandsCopy] = useState([]); + const [freeze, setFreeze] = useState(false); useEffect(() => { + if (freeze) return; + setCommandsCopy(() => reverse ? [...commands].reverse() : [...commands]); const handle = window.setInterval(() => { setCommandsCopy(() => reverse ? [...commands].reverse() : [...commands]); - }, 500); + }, 1000); return () => { window.clearInterval(handle); } - }, [commands, reverse]); + }, [commands, reverse, freeze]); - useEffect(() => { - setCommandsCopy(() => reverse ? commands.slice().reverse() : commands.slice()); - }, [reverse, commands]); + const toggleFreeze = useCallback(() => { + setFreeze(pause => !pause); + }, []); - const commandBardItems: ICommandBarItemProps[] = [ - { - key: "clearDisplay", - text: "Clear display log", - iconProps: { - iconName: "Clear" - }, - onClick: () => { - clear(); - commands.length = 0; - setCommandsCopy([...commands]) - } - }, - { - key: "clearAll", - text: "Clear all", - iconProps: { - iconName: "Clear" - }, - onClick: () => { - clearAll(); - commands.length = 0; - setCommandsCopy([...commands]) - } - } - ]; + const handleReverse = useCallback(() => { + setReverse(state => !state); + }, []); const columns: IColumn[] = [ { @@ -60,40 +40,65 @@ export const DisplayCommandView: FunctionComponent<{ commands: LCDCommand[], cle data: "timestamp", isSorted: true, isSortedDescending: !reverse, - onRender: (command: LCDCommand) => printTime(command.timestamp), - onColumnClick: () => setReverse(state => !state) + onRender: (command: DisplayCommand) => command.printTime(), + onColumnClick: handleReverse }, { key: "type", name: "Type", minWidth: 50, - maxWidth: 150, + maxWidth: 100, data: "string", - fieldName: "type", - isMultiline: true + fieldName: "initialCommand", + isMultiline: true, }, { - key: "text", - name: "Text", + key: "info", + name: "Info", minWidth: 50, - maxWidth: 150, data: "string", - fieldName: "text", + onRender: (command: DisplayCommand) => command.print().split('\n').map((s, i) => {s}
), isMultiline: true }, ] + const selection = new Selection({ + onSelectionChanged: () => { + const selected = selection.getSelection()[0] as DisplayCommand | undefined; + setFreeze(selected !== undefined); + showHistory(selected); + }, + getKey: (item: any) => (item as DisplayCommand).id + }); + return ( -
+
item.id} + getCellValueKey={(item: any) => item.id} /> + { + commandsCopy.length === 0 ? ( +

We haven't received anything yet! 😢

+ ) : null + }
- + + + ) } diff --git a/src/components/FlashLight.tsx b/src/components/FlashLight.tsx new file mode 100644 index 0000000..5d2e8bd --- /dev/null +++ b/src/components/FlashLight.tsx @@ -0,0 +1,19 @@ +import { DetailedHTMLProps, forwardRef, HTMLAttributes } from "react"; + +export const FlashLight = forwardRef, HTMLDivElement>>((props, ref) => { + return ( +
+ ) +}); \ No newline at end of file diff --git a/src/components/LCDView.tsx b/src/components/LCDView.tsx index 5a8d8c1..6da4a1c 100644 --- a/src/components/LCDView.tsx +++ b/src/components/LCDView.tsx @@ -1,16 +1,41 @@ import { Card } from "./Card"; -import { forwardRef, useEffect, useImperativeHandle, useRef } from "react"; -import { CommandBar, ICommandBarItemProps, Text } from '@fluentui/react'; +import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef } from "react"; +import { Checkbox, Icon, PrimaryButton } from '@fluentui/react'; import { WebGLLCDRenderer, font5x7, LCDCharBuffer } from "../classes/WebGLLCDRenderer"; import { Tooltip } from "./Tooltip"; import toast from 'react-hot-toast'; +import { DisplayCommand, isDisplayClearCommand } from "../classes/CommandParser"; +import { Cursor } from "./Cursor"; +import { FlashLight } from "./FlashLight"; +import { MonoNumber, MonoNumberRef } from "./MonoNumber"; -export const LCDView = forwardRef((_, ref) => { +export const LCDView = forwardRef void}>(({reset}, ref) => { const canvasRef = useRef(null); const lcdWebGLRenderer = useRef(); const lightRef = useRef(null); const lightRaf = useRef(-1); - const cmdRef = useRef(null); + const rowRef = useRef(null); + const columnRef = useRef(null); + const cmdRef = useRef(null); + const rogcd = useRef(false); + const cursorRef = useRef(null); + + const handleReset = useCallback(() => { + if (lcdWebGLRenderer.current) { + lcdWebGLRenderer.current.reset(); + reset(); + }; + }, [reset]); + + const handleResetOnLCDClear = useCallback((_: any, checked: boolean = false) => { + rogcd.current = checked; + }, []); + + const handleCursorToggle = useCallback((_: any, checked: boolean = false) => { + if(cursorRef.current) { + cursorRef.current.style.visibility = checked ? "visible" : "hidden"; + } + }, []); useEffect(() => { if (!canvasRef.current) return; @@ -27,7 +52,11 @@ export const LCDView = forwardRef((_, ref) => const renderer = new WebGLLCDRenderer(gl, 128, 64, charBuffer); renderer.startTicker(); - renderer.onReceiveCommand = () => { + renderer.onReceiveCommand = (command: DisplayCommand) => { + if (isDisplayClearCommand(command) && rogcd.current) { + return handleReset(); + } + if (!lightRef.current) return; window.clearTimeout(lightRaf.current); @@ -43,9 +72,19 @@ export const LCDView = forwardRef((_, ref) => } renderer.onDraw = () => { - if (!cmdRef.current) return; - - cmdRef.current.innerHTML = renderer.commandsReceived.toString(); + if (rowRef.current && columnRef.current) { + rowRef.current.setNumber(renderer.cursorRow); + columnRef.current.setNumber(renderer.cursorColumn); + }; + + if (cmdRef.current) { + cmdRef.current.setNumber(renderer.commandsReceived); + }; + + if (cursorRef.current) { + cursorRef.current.style.left = 7 + renderer.cursorColumn * 21.1875 + 'px'; + cursorRef.current.style.top = 3.5 + renderer.cursorRow * 28.25 + 'px'; + } } lcdWebGLRenderer.current = renderer; @@ -54,28 +93,13 @@ export const LCDView = forwardRef((_, ref) => renderer.destroy(); window.clearTimeout(lightRaf.current); } - }, []); - - const commandBarItems: ICommandBarItemProps[] = [ - { - key: "reset", - text: "Reset LCD", - iconProps: { - iconName: "RevToggleKey" - }, - onClick: () => { - if (lcdWebGLRenderer.current) { - lcdWebGLRenderer.current.clearLines(); - lcdWebGLRenderer.current.commandsReceived = 0; - } - } - }, - ]; + }, [handleReset]); useImperativeHandle(ref, () => lcdWebGLRenderer.current); - return ( + return useMemo(() => ( +
((_, ref) =>
-
- RX: -
+ + + +
+ + + +
-
- CMD(s): 0 +
+ +
- + + + + + + + + +
- ) + ), [handleCursorToggle, handleReset, handleResetOnLCDClear]); }); \ No newline at end of file diff --git a/src/components/MonoNumber.tsx b/src/components/MonoNumber.tsx new file mode 100644 index 0000000..4a7ae66 --- /dev/null +++ b/src/components/MonoNumber.tsx @@ -0,0 +1,52 @@ +import { forwardRef, useCallback, useImperativeHandle, useRef } from "react"; +import { Text } from "@fluentui/react"; +import { fillZeroes } from "../utils"; + +export interface MonoNumberRef { setNumber: (number: number) => void }; + +const clamp = (number: number, min: number, max: number) => { + return Math.min(Math.max(number, min), max); +} + +export const MonoNumber = forwardRef(({ min, max, defaultValue, digits }, ref) => { + const numberRef = useRef(null); + + useImperativeHandle(ref, () => ({ + setNumber: (number: number) => { + if (numberRef.current) { + setNumber(number); + } + } + })); + + + const setNumber = useCallback((number: number) => { + const text = fillZeroes(clamp(number, 0, 10 ** digits).toString(), digits); + if (numberRef.current) { + if (number <= max && number >= min) { + numberRef.current.style.color = "green"; + numberRef.current.innerHTML = text; + } else { + numberRef.current.style.color = "red"; + numberRef.current.innerHTML = text; + } + } + + return text; + }, [max, min, digits]); + + return ( + + + { + setNumber(defaultValue) + } + + + ); +}); \ No newline at end of file diff --git a/src/components/Startup.tsx b/src/components/Startup.tsx index be58068..c441883 100644 --- a/src/components/Startup.tsx +++ b/src/components/Startup.tsx @@ -4,6 +4,12 @@ export const Startup: FC<{}> = () => { const [open, setOpen] = useState(true); const [transition, setTransition] = useState(true); useEffect(() => { + if (process.env.NODE_ENV === "development") { + setOpen(false); + + return () => {}; + } + const handle = window.setTimeout(() => { setTransition(false); }, 2000); diff --git a/src/components/Tooltip.tsx b/src/components/Tooltip.tsx index 1635bdf..74afc0d 100644 --- a/src/components/Tooltip.tsx +++ b/src/components/Tooltip.tsx @@ -12,7 +12,6 @@ export const Tooltip: FC<{content?: ITooltipHostProps["content"]}> = ({ children calloutProps={{ gapSpace: 0 }} - styles={{}} > {children} diff --git a/src/index.tsx b/src/index.tsx index 348de72..5521a1c 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,45 +1,16 @@ import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; -import toast, { resolveValue, Toaster } from 'react-hot-toast'; import { initializeIcons } from '@fluentui/font-icons-mdl2'; -import { AnimationStyles, FabricBase, mergeStyles, MessageBar, MessageBarType } from '@fluentui/react'; +import { FabricBase } from '@fluentui/react'; +import { CustomToaster } from './components/CustomToaster'; initializeIcons(); ReactDOM.render( - - {(t) => { - const types = { - "error": MessageBarType.error, - "success": MessageBarType.success, - "loading": MessageBarType.blocked, - "blank": MessageBarType.info, - "custom": MessageBarType.info - }; - return ( - toast.dismiss(t.id)} - > - {resolveValue(t.message, t)} - - ) - }} - + , document.getElementById('root') diff --git a/src/utils/index.ts b/src/utils/index.ts index ffecee7..a3dbd53 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,7 +1,3 @@ -export const printTime = (timestamp: Date) => { - return timestamp.toLocaleTimeString() + ":" + fillZeroes("" + timestamp.getMilliseconds(), 3); -} - export const fillZeroes = (string: string, length: number) => { if (string.length === length) return string;