Skip to content

Commit

Permalink
Add ASCII/hex validation to macro data input.
Browse files Browse the repository at this point in the history
  • Loading branch information
gbmhunter committed May 17, 2024
1 parent a3ec456 commit 32ff234
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 13 deletions.
54 changes: 47 additions & 7 deletions src/model/Terminals/RightDrawer/Macros/Macro.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { makeAutoObservable } from "mobx";
import { App } from "src/model/App";
import { z } from "zod";

export enum MacroDataType {
ASCII = 'ASCII',
HEX = 'HEX',
ASCII = "ASCII",
HEX = "HEX",
}

export class Macro {
Expand All @@ -14,6 +15,8 @@ export class Macro {
data: string;
isSettingsModalOpen: boolean = false;

errorMsg: string = '';

constructor(app: App, name: string, data: string) {
this.app = app;
this.name = name;
Expand All @@ -24,25 +27,62 @@ export class Macro {

setDataType(dataType: MacroDataType) {
this.dataType = dataType;
this.validateData();
}

setData(data: string) {
this.data = data;
this.validateData();
}

validateData() {
// Validate the data
// If it's ASCII, make sure it's valid ASCII
// If it's HEX, make sure it's valid HEX
let validation;
if (this.dataType === MacroDataType.ASCII) {
// Validate ASCII
validation = z.string().safeParse(this.data);
} else if (this.dataType === MacroDataType.HEX) {
// Validate HEX
validation = z.string().regex(/^([0-9A-Fa-f]*)$/, "Must be a valid hex number.").safeParse(this.data);
} else {
throw new Error("Invalid data type");
}

console.log("Validation:", validation);

if (validation.success) {
this.errorMsg = '';
} else {
// We want to keep this simple, just show the first
// error message
this.errorMsg = validation.error.errors[0].message;
}
}

send() {
console.log('Send macro data:', this.data);
console.log("Send macro data:", this.data);
// Send the data to the serial port
// Convert string to Uint8Array
// If the user presses enter in the multiline text field, it will add a newline character
// (0x0A or 10) to the string.
const data = new TextEncoder().encode(this.data);
console.log('Data:', data);
let data;
if (this.dataType === MacroDataType.ASCII) {
data = new TextEncoder().encode(this.data);
} else if (this.dataType === MacroDataType.HEX) {
// Remove spaces and new lines
const hexString = this.data.replace(/[\s\n]/g, '');
// Convert to Uint8Array
data = new Uint8Array(hexString.match(/.{1,2}/g)!.map((byte) => parseInt(byte, 16)));
} else {
throw new Error("Invalid data type.");
}
console.log("Data:", data);
this.app.writeBytesToSerialPort(data);
}

setIsSettingsModalOpen(isOpen: boolean) {
console.log('Set isSettingsModalOpen:', isOpen);
console.log("Set isSettingsModalOpen:", isOpen);
this.isSettingsModalOpen = isOpen;
}
}
12 changes: 12 additions & 0 deletions src/model/Util/Util.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { expect, test, describe, beforeEach } from 'vitest';

import { textAreaToBytes } from './Util';

describe('util tests', () => {

test('basic ascii to bytes', () => {
const bytes = textAreaToBytes('a', '\n');
expect(bytes).toStrictEqual(Uint8Array.from([97]));
});

});
7 changes: 7 additions & 0 deletions src/model/Util/Util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ export function stringToUint8Array(str: string) {
return arr;
}

export function textAreaToBytes(str: string, newLinesChar: string) {
// Replace all instances of LF with the newLinesChar
str = str.replace(/\n/g, newLinesChar);
// Convert to Uint8Array
return stringToUint8Array(str);
}

/**
* Given a HTML element, this will find the index of the element within its parent's children array.
*
Expand Down
24 changes: 18 additions & 6 deletions src/view/Terminals/RightDrawer/MacroSettingsModalView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ export default observer((props: Props) => {
>
<div style={{ padding: "20px", backgroundColor: "#202020", width: "80%", maxHeight: "80%", display: "flex", flexDirection: "column", gap: "20px" }}>
<span>Macro Settings</span>
{/* BACKSPACE */}
{/* ================================================================= */}
{/* TREAT DATA AS */}
{/* ================================================================= */}
<FormControl>
<FormLabel>Treat data as:</FormLabel>
<RadioGroup
Expand All @@ -43,16 +45,23 @@ export default observer((props: Props) => {
macro.setDataType(e.target.value as any);
}}
>
{/* SEND LF */}
{/* ASCII */}
<Tooltip title="Treat the data as ASCII." placement="right" arrow>
<FormControlLabel value={MacroDataType.ASCII} control={<Radio />} label="ASCII" />
</Tooltip>
{/* SEND CR */}
<Tooltip title="Treat te data as HEX." placement="right" arrow>
{/* HEX */}
<Tooltip title="Treat the data as HEX." placement="right" arrow>
<FormControlLabel value={MacroDataType.HEX} control={<Radio />} label="HEX" />
</Tooltip>
</RadioGroup>
</FormControl>
{/* ================================================================= */}
{/* MACRO DATA */}
{/* ================================================================= */}
<Tooltip title={<div>
If ASCII, all printable characters are allowed. New lines (e.g. if you press Enter in the text field) will be sent as LF, CR or CRLF depending on what is selected in the TX Settings.<br/><br/>
If HEX, only the characters 0-9 and A-F, spaces and new lines are allowed. Spaces and new lines will be ignored. There must be an even number of characters as to make up a complete number of bytes (e.g. 08 A2 FF).
</div>} enterDelay={500} arrow>
<TextField
variant="outlined"
label="Macro Data"
Expand All @@ -63,12 +72,15 @@ export default observer((props: Props) => {
}}
multiline={true}
minRows={10}
value={macroController.macroToDisplayInModal?.data}
onChange={(e) => macroController.macroToDisplayInModal?.setData(e.target.value)}
value={macro.data}
helperText={macro.errorMsg}
error={macro.errorMsg !== ""}
onChange={(e) => macro.setData(e.target.value)}
onKeyDown={(e) => {
e.stopPropagation(); // Don't want the global keydown event to trigger
}}
/>
</Tooltip>
<div className="button-row" style={{ display: 'flex', justifyContent: 'end', alignItems: 'center', gap: '10px' }}>
<Button
variant="contained"
Expand Down

0 comments on commit 32ff234

Please sign in to comment.