Skip to content

Commit

Permalink
Merge pull request #272 from gbmhunter/develop
Browse files Browse the repository at this point in the history
Release of v4.4.2.
  • Loading branch information
gbmhunter authored Oct 9, 2023
2 parents e376913 + 9d134fc commit 0ec0dd6
Show file tree
Hide file tree
Showing 24 changed files with 395 additions and 126 deletions.
22 changes: 21 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## Unreleased

## [4.4.2] - 2023-10-09

### Added

- Added more info to README.
- Escape codes that are too long now push the parser back into IDLE state, closes #270. Added setting to select max. escape code length.

### Fixed

- Tab key now gets captured by the Terminal panes and HT char code sent, closes #263.
- Removed unused imports from Typescript files.
- RX terminals no longer behave like they can capture keystrokes, closes #269.
- Improved handling of a FramingError on read(), closes #259.

### Changed

- Rearranged folder structure of view components.
- Tooltips now follow the cursor around, improving usability in settings menu, closes #261.

## [4.4.1] - 2023-10-04

### Fixed
Expand Down Expand Up @@ -409,7 +428,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Added auto-scroll to TX pane, closes #89.
- Added special delete behaviour for backspace button when in "send on enter" mode, closes #90.

[unreleased]: https://github.com/gbmhunter/NinjaTerm/compare/v4.4.1...HEAD
[unreleased]: https://github.com/gbmhunter/NinjaTerm/compare/v4.4.2...HEAD
[4.4.2]: https://github.com/gbmhunter/NinjaTerm/compare/v4.4.1...v4.4.2
[4.4.1]: https://github.com/gbmhunter/NinjaTerm/compare/v4.4.0...v4.4.1
[4.4.0]: https://github.com/gbmhunter/NinjaTerm/compare/v4.3.1...v4.4.0
[4.3.1]: https://github.com/gbmhunter/NinjaTerm/compare/v4.3.0...v4.3.1
Expand Down
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ Arduino sketches in `arduino-serial` allow you to program different applications

1. Update the version number in `package.json`.
1. Update the CHANGELOG (don't forget the links right at the bottom of the page).
1. Create pull request merging `develop` into `main`.
1. Commit changes and push to `develop`.
1. Create pull request on GitHub merging `develop` into `main`.
1. Once the build on `develop` has been successfully run, merge the `develop` branch into `main` via the merge request.
1. Tag the branch on main with the version number, e.g. `v4.1.0`.
1. Create a release on GitHub pointing to the tag.
Expand All @@ -50,7 +51,7 @@ Arduino sketches in `arduino-serial` allow you to program different applications

## Deployment

Netlify is used to deploy and host the static NinjaTerm HTML/JS.
Netlify is used to deploy and host the static NinjaTerm HTML/JS. Netlify automatically deploys when the `main` branch is updated. Netlify also creates preview deploys on pull requests (link will be automatically posted into the PR comments).

## Code Architecture

Expand All @@ -60,15 +61,14 @@ Create React App (CRA) with the typescript PWA template [docs here](https://crea
npx create-react-app my-app --template cra-template-pwa-typescript
```

MobX is used to store the application state. The application model is under `src/model/`.
The React based user interface code is under `src/view`.

## GitHub Pages

The `docs/` folder contains the source code for the NinjaTerm homepage, hosted by GitHub Pages. This is automatically build and deployed with new commits pushed to `main`.
MobX is used to store the application state (model). The React component redraw themselves based on the state of the model. The application model is under `src/model/`.

## Theme Colors

* DC3545 (red): Primary colour, used for logo.
* `#DC3545` (red): Primary colour, used for logo.
* `#E47F37` (orange): Secondary colour, used for buttons on homepage.

## Extensions

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ninjaterm",
"version": "4.4.1",
"version": "4.4.2",
"private": true,
"dependencies": {
"@emotion/react": "^11.11.1",
Expand Down
4 changes: 2 additions & 2 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import {
import ReactGA from "react-ga4";

import { App } from './model/App';
import AppView from './AppView';
import HomepageView from './HomepageView';
import AppView from './view/AppView';
import HomepageView from './view/Homepage/HomepageView';

// Google Analytics
ReactGA.initialize("G-SDMMGN71FN");
Expand Down
105 changes: 59 additions & 46 deletions src/model/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,12 @@ import { makeAutoObservable, runInAction } from 'mobx';

import StopIcon from '@mui/icons-material/Stop';
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
import { VariantType, enqueueSnackbar } from 'notistack';

import packageDotJson from '../../package.json'
// eslint-disable-next-line import/no-cycle
import { Settings } from './Settings/Settings';
import Terminal from './Terminal/Terminal';
import * as serviceWorkerRegistration from '../serviceWorkerRegistration';

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://cra.link/PWA
// serviceWorkerRegistration.unregister();
// serviceWorkerRegistration.register();
import Snackbar from './Snackbar';

declare global {
interface String {
Expand Down Expand Up @@ -101,11 +94,11 @@ export class App {

closedPromise: Promise<void> | null;

snackBarOpen: boolean;

// Version of the NinjaTerm app. Read from package.json
version: string;

snackbar: Snackbar;

constructor(
testing = false
) {
Expand All @@ -115,11 +108,11 @@ export class App {

this.settings = new Settings(this);

// Need to create terminals before settings, as the settings
// will configure the terminals
this.txRxTerminal = new Terminal(this.settings);
this.rxTerminal = new Terminal(this.settings);
this.txTerminal = new Terminal(this.settings);
this.snackbar = new Snackbar();

this.txRxTerminal = new Terminal(this.settings, this.snackbar, true);
this.rxTerminal = new Terminal(this.settings, this.snackbar, false); // Not focusable
this.txTerminal = new Terminal(this.settings, this.snackbar, true);

this.numBytesReceived = 0;
this.numBytesTransmitted = 0;
Expand All @@ -129,11 +122,9 @@ export class App {
this.reader = null;
this.closedPromise = null;

this.snackBarOpen = false;

console.log('Started NinjaTerm.')

// this.runTestMode();
// this.runTestModeBytes0To255();
// This is fired whenever a serial port that has been allowed access
// dissappears (i.e. USB serial), even if we are not connected to it.
// navigator.serial.addEventListener("disconnect", (event) => {
Expand Down Expand Up @@ -166,8 +157,22 @@ export class App {
}, 200);
}

setSnackBarOpen(trueFalse: boolean) {
this.snackBarOpen = trueFalse;
/** Function used for testing when you don't have an Arduino handy.
* Sets up a interval timer to add fake RX data.
* Change as needed for testing!
*/
runTestModeBytes0To255() {
console.log('runTestMode2() called.');
this.settings.dataProcessing.visibleData.fields.ansiEscapeCodeParsingEnabled.value = false;
this.settings.dataProcessing.applyChanges();
let testCharIdx = 0;
setInterval(() => {
this.parseRxData(Uint8Array.from([ testCharIdx ]));
testCharIdx += 1;
if (testCharIdx === 256) {
testCharIdx = 0;
}
}, 200);
}

setSettingsDialogOpen(trueFalse: boolean) {
Expand Down Expand Up @@ -195,7 +200,7 @@ export class App {
localPort = await navigator.serial.requestPort();
} catch (error) {
console.log('Error occurred. error=', error);
this.sendToSnackbar('User cancelled port selection.', 'error');
this.snackbar.sendToSnackbar('User cancelled port selection.', 'error');
return;
}
console.log('Got local port, now setting state...');
Expand Down Expand Up @@ -223,21 +228,21 @@ export class App {
const msg = 'Serial port is already in use by another program.\n'
+ 'Reported error from port.open():\n'
+ `${error}`
this.sendToSnackbar(msg, 'error');
this.snackbar.sendToSnackbar(msg, 'error');
console.log(msg);
} else {
const msg = `Unrecognized DOMException error with name=${error.name} occurred when trying to open serial port.\n`
+ 'Reported error from port.open():\n'
+ `${error}`
this.sendToSnackbar(msg, 'error');
this.snackbar.sendToSnackbar(msg, 'error');
console.log(msg);
}
} else {
// Type of error not recognized or seen before
const msg = `Unrecognized error occurred when trying to open serial port.\n`
+ 'Reported error from port.open():\n'
+ `${error}`
this.sendToSnackbar(msg, 'error');
this.snackbar.sendToSnackbar(msg, 'error');
console.log(msg);
}

Expand All @@ -246,7 +251,7 @@ export class App {
return;
}
console.log('Serial port opened.');
this.sendToSnackbar('Serial port opened.', 'success');
this.snackbar.sendToSnackbar('Serial port opened.', 'success');
this.setPortState(PortState.OPENED);
// This will automatically close the settings window if the user is currently in it,
// clicks "Open" and the port opens successfully.
Expand Down Expand Up @@ -281,31 +286,42 @@ export class App {
// This is called if the USB serial device is removed whilst
// reading
console.log('reader.read() threw an error. error=', error, 'port.readable="', this.port?.readable, '" (null indicates fatal error)');
// These error are described at https://wicg.github.io/serial/
// @ts-ignore:
if (error instanceof DOMException) {
console.log('Exception was DOMException. error.name=', error.name);
// BufferOverrunError: Rendering couldn't get up with input data,
// potentially make buffer size to port.open() larger or speed up processing/rendering
// if this occurs often. This is non-fatal, readable will not be null
if (error.name === 'BufferOverrunError') {
this.sendToSnackbar('RX buffer overrun occurred. Too much data is coming in for the app to handle.\n'
this.snackbar.sendToSnackbar('RX buffer overrun occurred. Too much data is coming in for the app to handle.\n'
+ 'Returned error from reader.read():\n'
+ `${error}`,
'warning');
} else if (error.name === 'BreakError') {
this.sendToSnackbar('Encountered break signal.\n'
this.snackbar.sendToSnackbar('Encountered break signal.\n'
+ 'Returned error from reader.read():\n'
+ `${error}`,
'warning');
} else {
} else if (error.name === 'FramingError') {
this.snackbar.sendToSnackbar('Encountered framing error.\n'
+ 'Returned error from reader.read():\n'
+ `${error}`,
'warning');
} else if (error.name === 'ParityError') {
this.snackbar.sendToSnackbar('Encountered parity error.\n'
+ 'Returned error from reader.read():\n'
+ `${error}`,
'warning');
} else {
const msg = `Unrecognized DOMException error with name=${error.name} occurred when trying to read from serial port.\n`
+ 'Reported error from port.read():\n'
+ `${error}`;
this.sendToSnackbar(msg, 'error');
this.snackbar.sendToSnackbar(msg, 'error');
console.log(msg);
}
} else {
this.sendToSnackbar(`Serial port was removed unexpectedly.\nReturned error from reader.read():\n${error}`, 'error');
this.snackbar.sendToSnackbar(`Serial port was removed unexpectedly.\nReturned error from reader.read():\n${error}`, 'error');
}

// this.setPortState(PortState.CLOSED);
Expand All @@ -327,21 +343,8 @@ export class App {
}

/**
* Enqueues a message to the snackbar used for temporary status updates to the user.
* In normal operation this is called from the readUntilClose() function above.
*
* @param msg The message you want to display. Use "\n" to insert new lines.
* @param variant The variant (e.g. error, warning) of snackbar you want to display.
*/
sendToSnackbar(msg: string, variant: VariantType) {
enqueueSnackbar(
msg,
{
variant: variant,
style: { whiteSpace: 'pre-line' } // This allows the new lines in the string above to also be carried through to the displayed message
});
}

/**
* Unit tests call this instead of mocking out the serial port read() function
* as setting up the deferred promise was too tricky.
*
Expand Down Expand Up @@ -369,7 +372,7 @@ export class App {
await this.closedPromise;

this.setPortState(PortState.CLOSED);
this.sendToSnackbar('Serial port closed.', 'success');
this.snackbar.sendToSnackbar('Serial port closed.', 'success');
this.reader = null;
this.closedPromise = null;
}
Expand All @@ -387,6 +390,13 @@ export class App {
*/
async handleKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {
// console.log('handleKeyDown() called. event=', event, this);

// Prevent Tab press from moving focus to another element on screen
// Do this even if port is not opened
if (event.key === 'Tab') {
event.preventDefault();
}

if (this.portState === PortState.OPENED) {
// Serial port is open, let's send it to the serial
// port
Expand Down Expand Up @@ -430,8 +440,11 @@ export class App {
} else if (event.key === 'ArrowDown') {
// Send "ESC[B" (go down 1)
bytesToWrite.push(0x1B, '['.charCodeAt(0), 'B'.charCodeAt(0));
// If we get here, we don't know what to do with the key press
} else if (event.key === 'Tab') {
// Send horizontal tab, HT, 0x09
bytesToWrite.push(0x09);
} else {
// If we get here, we don't know what to do with the key press
console.log('Unsupported char! event=', event);
return;
}
Expand Down
6 changes: 6 additions & 0 deletions src/model/Settings/DataProcessingSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ class Data {
errorMsg: '',
rule: 'required',
},
maxEscapeCodeLengthChars: {
value: 10,
hasError: false,
errorMsg: '',
rule: 'required|integer|min:2', // Min. is two, one for the escape byte and then a single char.
},
terminalWidthChars: {
value: 120, // 80 is standard
hasError: false,
Expand Down
32 changes: 32 additions & 0 deletions src/model/Snackbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { makeAutoObservable } from 'mobx';
import { VariantType, enqueueSnackbar } from 'notistack';

export default class Snackbar {

snackBarOpen: boolean;

constructor() {
this.snackBarOpen = false;
makeAutoObservable(this); // Make sure this near the end
};

setSnackBarOpen(trueFalse: boolean) {
this.snackBarOpen = trueFalse;
}

/**
* Enqueues a message to the snackbar used for temporary status updates to the user.
*
* @param msg The message you want to display. Use "\n" to insert new lines.
* @param variant The variant (e.g. error, warning) of snackbar you want to display.
*/
sendToSnackbar(msg: string, variant: VariantType) {
enqueueSnackbar(
msg,
{
variant: variant,
style: { whiteSpace: 'pre-line' } // This allows the new lines in the string above to also be carried through to the displayed message
});
}

}
Loading

0 comments on commit 0ec0dd6

Please sign in to comment.