From 4f78784ea4d3bee8f0cd8bf0ae7c7a8427f01e65 Mon Sep 17 00:00:00 2001 From: Geoffrey Hunter Date: Tue, 11 Jun 2024 20:15:17 +1200 Subject: [PATCH 01/26] Wrap disabled buttons with tooltips in divs. Add debug message. --- .../Baud115200Test/Baud115200Test.ino | 2 +- .../ProfileSettings/ProfileSettingsView.tsx | 98 ++++++++++--------- .../SingleTerminal/SingleTerminalView.tsx | 1 + 3 files changed, 55 insertions(+), 46 deletions(-) diff --git a/arduino-serial/Baud115200Test/Baud115200Test.ino b/arduino-serial/Baud115200Test/Baud115200Test.ino index 806ecf72..22c96300 100644 --- a/arduino-serial/Baud115200Test/Baud115200Test.ino +++ b/arduino-serial/Baud115200Test/Baud115200Test.ino @@ -19,7 +19,7 @@ void loop() { Serial.println("fast "); // Wait until data is sent Serial.flush(); -// delay(DISPLAY_PERIOD); + delay(DISPLAY_PERIOD); } diff --git a/src/view/Settings/ProfileSettings/ProfileSettingsView.tsx b/src/view/Settings/ProfileSettings/ProfileSettingsView.tsx index 750982e6..0250f0f1 100644 --- a/src/view/Settings/ProfileSettings/ProfileSettingsView.tsx +++ b/src/view/Settings/ProfileSettings/ProfileSettingsView.tsx @@ -94,49 +94,55 @@ function ProfileSettingsView(props: Props) { enterDelay={500} arrow > - +
+ +
- +
+ +
{/* =============================================================================== */} {/* PROFILE NAME */} {/* =============================================================================== */} - { - profilesSettings.setProfileName(event.target.value); - }} - > +
+ { + profilesSettings.setProfileName(event.target.value); + }} + > +
@@ -157,17 +163,19 @@ function ProfileSettingsView(props: Props) { - +
+ +
diff --git a/src/view/Terminals/SingleTerminal/SingleTerminalView.tsx b/src/view/Terminals/SingleTerminal/SingleTerminalView.tsx index 048b8771..fe4b7094 100644 --- a/src/view/Terminals/SingleTerminal/SingleTerminalView.tsx +++ b/src/view/Terminals/SingleTerminal/SingleTerminalView.tsx @@ -187,6 +187,7 @@ export default observer((props: Props) => { if (!terminalDiv?.current?.offsetHeight) { return; } + console.log('handleResize() called'); terminal.setTerminalViewHeightPx(terminalDiv?.current?.offsetHeight); }; handleResize(); From 82f06b2516d4a604070504cfb713c75977b7cc42 Mon Sep 17 00:00:00 2001 From: Geoffrey Hunter Date: Thu, 13 Jun 2024 13:26:07 +1200 Subject: [PATCH 02/26] Start adding accordions to right-hand drawer in terminal view. --- .../Terminals/RightDrawer/RightDrawerView.tsx | 257 ++++++++++-------- 1 file changed, 137 insertions(+), 120 deletions(-) diff --git a/src/view/Terminals/RightDrawer/RightDrawerView.tsx b/src/view/Terminals/RightDrawer/RightDrawerView.tsx index 913e73e5..f119ea02 100644 --- a/src/view/Terminals/RightDrawer/RightDrawerView.tsx +++ b/src/view/Terminals/RightDrawer/RightDrawerView.tsx @@ -1,14 +1,15 @@ -import { observer } from "mobx-react-lite"; -import { ResizableBox } from "react-resizable"; -import "react-resizable/css/styles.css"; -import { Button, FormControl, FormControlLabel, InputAdornment, InputLabel, MenuItem, Select, Switch, Tooltip } from "@mui/material"; +import { observer } from 'mobx-react-lite'; +import { ResizableBox } from 'react-resizable'; +import 'react-resizable/css/styles.css'; +import { Accordion, AccordionDetails, AccordionSummary, Button, FormControl, FormControlLabel, InputAdornment, InputLabel, MenuItem, Select, Switch, Tooltip } from '@mui/material'; +import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward'; -import { App } from "src/model/App"; -import MacroView from "./MacroRowView"; -import MacroSettingsModalView from "./MacroSettingsModalView"; -import ApplyableTextFieldView from "src/view/Components/ApplyableTextFieldView"; -import { DataViewConfiguration, dataViewConfigEnumToDisplayName } from "src/model/Settings/DisplaySettings/DisplaySettings"; -import { PortState } from "src/model/Settings/PortConfigurationSettings/PortConfigurationSettings"; +import { App } from 'src/model/App'; +import MacroView from './MacroRowView'; +import MacroSettingsModalView from './MacroSettingsModalView'; +import ApplyableTextFieldView from 'src/view/Components/ApplyableTextFieldView'; +import { DataViewConfiguration, dataViewConfigEnumToDisplayName } from 'src/model/Settings/DisplaySettings/DisplaySettings'; +import { PortState } from 'src/model/Settings/PortConfigurationSettings/PortConfigurationSettings'; interface Props { app: App; @@ -26,130 +27,146 @@ export default observer((props: Props) => { } > {/* ResizableBox requires a single child component */} -
-
{/* Spacer to prevent select input title from being clipped */} - {/* ======================================================= */} - {/* DATA VIEW CONFIGURATION */} - {/* ======================================================= */} - - Controls how to display the TX and RX data. Different use cases require different view configurations. -
    -
  • Single terminal: TX and RX data is combined in the same pane. Useful for terminal style applications when escape codes are used.
  • -
  • - Separate TX/RX terminals: TX and RX data are kept in separate panes. Useful for when you have a lot of incoming basic RX data and what to still see the data you - are sending. -
  • -
-
- } - placement="left" - > - - Data View Configuration - - - -
- {/* =============================================================================== */} - {/* CHAR SIZE */} - {/* =============================================================================== */} - - px, - }} - applyableTextField={app.settings.displaySettings.charSizePx} - sx={{ width: "100px" }} - /> - - {/* =============================================================================== */} - {/* LOCAL TX ECHO SWITCH */} - {/* =============================================================================== */} - { - app.settings.rxSettings.setLocalTxEcho(e.target.checked); - }} + + Data View Configuration + + + +
+ {/* =============================================================================== */} + {/* CHAR SIZE */} + {/* =============================================================================== */} + + px, + }} + applyableTextField={app.settings.displaySettings.charSizePx} + sx={{ width: '100px' }} + /> + + {/* =============================================================================== */} + {/* LOCAL TX ECHO SWITCH */} + {/* =============================================================================== */} + { + app.settings.rxSettings.setLocalTxEcho(e.target.checked); + }} + /> + } + label="Local TX Echo" /> - } - label="Local TX Echo" - /> -
- {/* ==================================================================== */} - {/* SEND BREAK BUTTON */} - {/* ==================================================================== */} - - - - - - {/* =============================================================================== */} - {/* MACROS */} - {/* =============================================================================== */} -

Macros

-
- {macroRows} -
- +
+ {/* ==================================================================== */} + {/* SEND BREAK BUTTON */} + {/* ==================================================================== */} + + + + + + + + + }>Macros + + {/* =============================================================================== */} + {/* MACROS */} + {/* =============================================================================== */} +
+ {macroRows} +
+ +
+
); From 02f00001d0b9c7aae811620782783746f5976041 Mon Sep 17 00:00:00 2001 From: Geoffrey Hunter Date: Thu, 13 Jun 2024 16:38:24 +1200 Subject: [PATCH 03/26] Add quick serial port settings to right-hand terminal drawer. --- .../Terminals/RightDrawer/RightDrawerView.tsx | 253 +++++++++++++++++- 1 file changed, 249 insertions(+), 4 deletions(-) diff --git a/src/view/Terminals/RightDrawer/RightDrawerView.tsx b/src/view/Terminals/RightDrawer/RightDrawerView.tsx index f119ea02..407c0091 100644 --- a/src/view/Terminals/RightDrawer/RightDrawerView.tsx +++ b/src/view/Terminals/RightDrawer/RightDrawerView.tsx @@ -1,15 +1,42 @@ import { observer } from 'mobx-react-lite'; import { ResizableBox } from 'react-resizable'; import 'react-resizable/css/styles.css'; -import { Accordion, AccordionDetails, AccordionSummary, Button, FormControl, FormControlLabel, InputAdornment, InputLabel, MenuItem, Select, Switch, Tooltip } from '@mui/material'; +import { + Accordion, + AccordionDetails, + AccordionSummary, + Autocomplete, + Button, + ButtonPropsColorOverrides, + FormControl, + FormControlLabel, + InputAdornment, + InputLabel, + Link, + MenuItem, + Select, + Switch, + TextField, + Tooltip, +} from '@mui/material'; import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward'; +import { OverridableStringUnion } from '@mui/types'; -import { App } from 'src/model/App'; +import { App, PortType } from 'src/model/App'; import MacroView from './MacroRowView'; import MacroSettingsModalView from './MacroSettingsModalView'; import ApplyableTextFieldView from 'src/view/Components/ApplyableTextFieldView'; import { DataViewConfiguration, dataViewConfigEnumToDisplayName } from 'src/model/Settings/DisplaySettings/DisplaySettings'; -import { PortState } from 'src/model/Settings/PortConfigurationSettings/PortConfigurationSettings'; +import { + DEFAULT_BAUD_RATES, + FlowControl, + NUM_DATA_BITS_OPTIONS, + Parity, + PortState, + STOP_BIT_OPTIONS, + StopBits, +} from 'src/model/Settings/PortConfigurationSettings/PortConfigurationSettings'; +import { portStateToButtonProps } from 'src/view/Components/PortStateToButtonProps'; interface Props { app: App; @@ -59,8 +86,226 @@ export default observer((props: Props) => { >
{/* Spacer to prevent select input title from being clipped */} - }>Quick Settings + }>Quick Port Settings +
+ For more port settings, go to the{' '} + { + console.log('Hello'); + }} + > + Port Settings view + + . +
+
+
+ {/* ============================================================== */} + {/* BAUD RATE */} + {/* ============================================================== */} + + option.toString())} + renderInput={(params) => ( + { + e.stopPropagation(); + }} // Prevents the global keydown event from being triggered + /> + )} + disabled={app.portState !== PortState.CLOSED} + sx={{ m: 1, width: 160 }} + size="small" + onChange={(event: any, newValue: string | null) => { + console.log('onChange() called. newValue: ', newValue); + }} + inputValue={app.settings.portConfiguration.baudRateInputValue} + onInputChange={(event, newInputValue) => { + console.log('newInputValue: ', newInputValue); + app.settings.portConfiguration.setBaudRateInputValue(newInputValue); + }} + /> + + {/* ============================================================== */} + {/* NUM. DATA BITS */} + {/* ============================================================== */} + + + Num. data bits + + + + {/* ============================================================== */} + {/* PARITY */} + {/* ============================================================== */} + + + Parity + + + + {/* ============================================================== */} + {/* STOP BITS */} + {/* ============================================================== */} + + + Stop bits + + + + {/* ============================================================== */} + {/* FLOW CONTROL */} + {/* ============================================================== */} + + + Flow control + + + +
+ +
+ {/* =============================================================== */} + {/* SELECT PORT BUTTON */} + {/* =============================================================== */} + + {/* =============================================================== */} + {/* OPEN/CLOSE BUTTON */} + {/* =============================================================== */} + +
+ + + {/* ======================================================= */} + {/* OTHER QUICK SETTINGS */} + {/* ======================================================= */} + + }>Other Quick Settings + +
+ For more options, go to the{' '} + { + console.log('Hello'); + }} + > + Settings view + + . +
+
{/* ======================================================= */} {/* DATA VIEW CONFIGURATION */} {/* ======================================================= */} From d17a6c42da53301b797eab9a759aa54c61ca60bb Mon Sep 17 00:00:00 2001 From: Geoffrey Hunter Date: Thu, 13 Jun 2024 16:45:46 +1200 Subject: [PATCH 04/26] Style the accordions. --- .../Terminals/RightDrawer/RightDrawerView.tsx | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/view/Terminals/RightDrawer/RightDrawerView.tsx b/src/view/Terminals/RightDrawer/RightDrawerView.tsx index 407c0091..429e7dd7 100644 --- a/src/view/Terminals/RightDrawer/RightDrawerView.tsx +++ b/src/view/Terminals/RightDrawer/RightDrawerView.tsx @@ -4,7 +4,6 @@ import 'react-resizable/css/styles.css'; import { Accordion, AccordionDetails, - AccordionSummary, Autocomplete, Button, ButtonPropsColorOverrides, @@ -18,6 +17,7 @@ import { Switch, TextField, Tooltip, + styled, } from '@mui/material'; import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward'; import { OverridableStringUnion } from '@mui/types'; @@ -38,6 +38,30 @@ import { } from 'src/model/Settings/PortConfigurationSettings/PortConfigurationSettings'; import { portStateToButtonProps } from 'src/view/Components/PortStateToButtonProps'; +import MuiAccordionSummary, { + AccordionSummaryProps, +} from '@mui/material/AccordionSummary'; + +// This code was modified from https://mui.com/material-ui/react-accordion/#customization +const AccordionSummary = styled((props: AccordionSummaryProps) => ( + } + {...props} + /> +))(({ theme }) => ({ + backgroundColor: + theme.palette.mode === 'dark' + ? 'rgba(255, 255, 255, .05)' + : 'rgba(0, 0, 0, .03)', + flexDirection: 'row-reverse', + '& .MuiAccordionSummary-expandIconWrapper.Mui-expanded': { + transform: 'rotate(90deg)', + }, + '& .MuiAccordionSummary-content': { + marginLeft: theme.spacing(1), + }, +})); + interface Props { app: App; } From 5ca679ece301fcb1614d5e5d7c72c879d90eafeb Mon Sep 17 00:00:00 2001 From: Geoffrey Hunter Date: Thu, 13 Jun 2024 17:42:17 +1200 Subject: [PATCH 05/26] Fix broken e2e tests. --- src/view/Terminals/RightDrawer/RightDrawerView.tsx | 4 ++-- tests/Macros.spec.ts | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/view/Terminals/RightDrawer/RightDrawerView.tsx b/src/view/Terminals/RightDrawer/RightDrawerView.tsx index 429e7dd7..56f9b380 100644 --- a/src/view/Terminals/RightDrawer/RightDrawerView.tsx +++ b/src/view/Terminals/RightDrawer/RightDrawerView.tsx @@ -110,7 +110,7 @@ export default observer((props: Props) => { >
{/* Spacer to prevent select input title from being clipped */} - }>Quick Port Settings + } data-testid="test">Quick Port Settings
For more port settings, go to the{' '} @@ -425,7 +425,7 @@ export default observer((props: Props) => { - }>Macros + } data-testid="macros-accordion-summary">Macros {/* =============================================================================== */} {/* MACROS */} diff --git a/tests/Macros.spec.ts b/tests/Macros.spec.ts index 322d64c0..960cc57a 100644 --- a/tests/Macros.spec.ts +++ b/tests/Macros.spec.ts @@ -8,6 +8,9 @@ test.describe('macros', () => { await appTestHarness.setupPage(); await appTestHarness.openPortAndGoToTerminalView(); + // Open the macros accordion + await page.getByTestId("macros-accordion-summary").click(); + await expect(await page.getByTestId('macro-data-0')).toHaveValue('Hello\\n'); // Click on macro's "more settings" await page.getByTestId('macro-more-settings-0').click(); @@ -39,6 +42,8 @@ test.describe('macros', () => { await appTestHarness.setupPage(); await appTestHarness.openPortAndGoToTerminalView(); + await page.getByTestId("macros-accordion-summary").click(); + // Change the value of MACRO 0 await page.getByTestId('macro-data-0').fill('new value'); @@ -54,6 +59,8 @@ test.describe('macros', () => { await appTestHarness.setupPage(); await appTestHarness.openPortAndGoToTerminalView(); + await page.getByTestId("macros-accordion-summary").click(); + await page.getByTestId('macro-data-0').fill('abc123\\n'); // Hit the send button await page.getByTestId('macro-0-send-button').click(); @@ -68,6 +75,8 @@ test.describe('macros', () => { await appTestHarness.setupPage(); await appTestHarness.openPortAndGoToTerminalView(); + await page.getByTestId("macros-accordion-summary").click(); + await page.getByTestId('macro-more-settings-0').click(); // Uncheck the process escape chars checkbox await page.getByTestId('macro-process-escape-chars-cb').uncheck(); @@ -87,6 +96,8 @@ test.describe('macros', () => { await appTestHarness.setupPage(); await appTestHarness.openPortAndGoToTerminalView(); + await page.getByTestId("macros-accordion-summary").click(); + // Change macro 0 to hex await page.getByTestId('macro-more-settings-0').click(); // Check the hex radio button From 18856a74b975be0bbbde2b1563307ebe65bdace9 Mon Sep 17 00:00:00 2001 From: Geoffrey Hunter Date: Thu, 13 Jun 2024 21:32:36 +1200 Subject: [PATCH 06/26] Fix MUI Link components. --- .../Terminals/RightDrawer/RightDrawerView.tsx | 59 ++++++++++--------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/src/view/Terminals/RightDrawer/RightDrawerView.tsx b/src/view/Terminals/RightDrawer/RightDrawerView.tsx index 56f9b380..f5322410 100644 --- a/src/view/Terminals/RightDrawer/RightDrawerView.tsx +++ b/src/view/Terminals/RightDrawer/RightDrawerView.tsx @@ -22,7 +22,7 @@ import { import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward'; import { OverridableStringUnion } from '@mui/types'; -import { App, PortType } from 'src/model/App'; +import { App, MainPanes, PortType } from 'src/model/App'; import MacroView from './MacroRowView'; import MacroSettingsModalView from './MacroSettingsModalView'; import ApplyableTextFieldView from 'src/view/Components/ApplyableTextFieldView'; @@ -38,29 +38,22 @@ import { } from 'src/model/Settings/PortConfigurationSettings/PortConfigurationSettings'; import { portStateToButtonProps } from 'src/view/Components/PortStateToButtonProps'; -import MuiAccordionSummary, { - AccordionSummaryProps, -} from '@mui/material/AccordionSummary'; +import MuiAccordionSummary, { AccordionSummaryProps } from '@mui/material/AccordionSummary'; +import { SettingsCategories } from 'src/model/Settings/Settings'; // This code was modified from https://mui.com/material-ui/react-accordion/#customization -const AccordionSummary = styled((props: AccordionSummaryProps) => ( - } - {...props} - /> -))(({ theme }) => ({ - backgroundColor: - theme.palette.mode === 'dark' - ? 'rgba(255, 255, 255, .05)' - : 'rgba(0, 0, 0, .03)', - flexDirection: 'row-reverse', - '& .MuiAccordionSummary-expandIconWrapper.Mui-expanded': { - transform: 'rotate(90deg)', - }, - '& .MuiAccordionSummary-content': { - marginLeft: theme.spacing(1), - }, -})); +const AccordionSummary = styled((props: AccordionSummaryProps) => } {...props} />)( + ({ theme }) => ({ + backgroundColor: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, .05)' : 'rgba(0, 0, 0, .03)', + flexDirection: 'row-reverse', + '& .MuiAccordionSummary-expandIconWrapper.Mui-expanded': { + transform: 'rotate(90deg)', + }, + '& .MuiAccordionSummary-content': { + marginLeft: theme.spacing(1), + }, + }) +); interface Props { app: App; @@ -110,14 +103,18 @@ export default observer((props: Props) => { >
{/* Spacer to prevent select input title from being clipped */} - } data-testid="test">Quick Port Settings + } data-testid="test"> + Quick Port Settings + -
+
For more port settings, go to the{' '} { - console.log('Hello'); + // Show Settings->Port Configuration view + app.setShownMainPane(MainPanes.SETTINGS); + app.settings.setActiveSettingsCategory(SettingsCategories.PORT_CONFIGURATION); }} > Port Settings view @@ -320,9 +317,11 @@ export default observer((props: Props) => {
For more options, go to the{' '} { - console.log('Hello'); + // Show the Settings view. The sub-view selected will be whatever + // was last selected by the user. + app.setShownMainPane(MainPanes.SETTINGS); }} > Settings view @@ -425,7 +424,9 @@ export default observer((props: Props) => { - } data-testid="macros-accordion-summary">Macros + } data-testid="macros-accordion-summary"> + Macros + {/* =============================================================================== */} {/* MACROS */} From 5939e2b92ea7f505e2d2b63c472372f7950d084c Mon Sep 17 00:00:00 2001 From: Geoffrey Hunter Date: Thu, 13 Jun 2024 21:47:10 +1200 Subject: [PATCH 07/26] Make accordions controlled. --- .../Terminals/RightDrawer/RightDrawer.ts | 16 ++++++++++ .../Terminals/RightDrawer/RightDrawerView.tsx | 29 +++++++++++++++---- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/model/Terminals/RightDrawer/RightDrawer.ts b/src/model/Terminals/RightDrawer/RightDrawer.ts index 9ca70c70..bdc60c83 100644 --- a/src/model/Terminals/RightDrawer/RightDrawer.ts +++ b/src/model/Terminals/RightDrawer/RightDrawer.ts @@ -6,10 +6,26 @@ export default class RightDrawer { macroController: MacroController; + quickPortSetttingsIsExpanded = false; + otherQuickSettingsIsExpanded = false; + macrosIsExpanded = false; + constructor(app: App) { this.macroController = new MacroController(app); makeAutoObservable(this); // Make sure this near the end } + + handleQuickPortSettingsAccordionChange = (event: React.SyntheticEvent, isExpanded: boolean) => { + this.quickPortSetttingsIsExpanded = isExpanded; + }; + + handleOtherQuickSettingsAccordionChange = (event: React.SyntheticEvent, isExpanded: boolean) => { + this.otherQuickSettingsIsExpanded = isExpanded; + }; + + handleMacrosAccordionChange = (event: React.SyntheticEvent, isExpanded: boolean) => { + this.macrosIsExpanded = isExpanded; + }; } diff --git a/src/view/Terminals/RightDrawer/RightDrawerView.tsx b/src/view/Terminals/RightDrawer/RightDrawerView.tsx index f5322410..19fb266d 100644 --- a/src/view/Terminals/RightDrawer/RightDrawerView.tsx +++ b/src/view/Terminals/RightDrawer/RightDrawerView.tsx @@ -62,6 +62,8 @@ interface Props { export default observer((props: Props) => { const { app } = props; + const rightDrawer = app.terminals.rightDrawer; + // Create macro rows const macroRows = app.terminals.rightDrawer.macroController.macrosArray.map((macro, index) => { return ; @@ -102,7 +104,12 @@ export default observer((props: Props) => { }} >
{/* Spacer to prevent select input title from being clipped */} - + } data-testid="test"> Quick Port Settings @@ -311,7 +318,12 @@ export default observer((props: Props) => { {/* ======================================================= */} {/* OTHER QUICK SETTINGS */} {/* ======================================================= */} - + }>Other Quick Settings
@@ -423,14 +435,19 @@ export default observer((props: Props) => { - + {/* =============================================================================== */} + {/* MACROS */} + {/* =============================================================================== */} + } data-testid="macros-accordion-summary"> Macros - {/* =============================================================================== */} - {/* MACROS */} - {/* =============================================================================== */}
{macroRows}
From 370a440a7853fe8fa2f06cde8b5929cbd65ffce1 Mon Sep 17 00:00:00 2001 From: Geoffrey Hunter Date: Fri, 14 Jun 2024 16:28:53 +1200 Subject: [PATCH 08/26] Refactor app data storage to use single key in local storage. --- local-storage-data/profiles-2ce70b.json | 715 ++++++++++++++++++ src/model/App.tsx | 18 +- .../ProfileManager/ProfileManager.spec.ts | 8 +- src/model/ProfileManager/ProfileManager.ts | 193 +++-- .../DisplaySettings/DisplaySettings.ts | 8 +- .../GeneralSettings/GeneralSettings.ts | 8 +- .../PortConfigurationSettings.ts | 8 +- .../ProfileSettings/ProfileSettings.ts | 6 +- src/model/Settings/RxSettings/RxSettings.ts | 8 +- src/model/Settings/TxSettings/TxSettings.ts | 8 +- .../RightDrawer/Macros/MacroController.ts | 8 +- .../Terminals/RightDrawer/RightDrawer.ts | 59 +- .../ProfileSettings/ProfileSettingsView.tsx | 2 +- .../Terminals/RightDrawer/RightDrawerView.tsx | 7 +- 14 files changed, 934 insertions(+), 122 deletions(-) create mode 100644 local-storage-data/profiles-2ce70b.json diff --git a/local-storage-data/profiles-2ce70b.json b/local-storage-data/profiles-2ce70b.json new file mode 100644 index 00000000..41f4497d --- /dev/null +++ b/local-storage-data/profiles-2ce70b.json @@ -0,0 +1,715 @@ +[ + { + "name": "New profile 1", + "rootConfig": { + "version": 1, + "terminal": { + "macroController": { + "version": 1, + "macroConfigs": [ + { + "version": 1, + "name": "", + "dataType": "ASCII", + "data": "Hello\\n", + "processEscapeChars": true, + "sendOnEnterValueForEveryNewLineInTextBox": true + }, + { + "version": 1, + "name": "", + "dataType": "HEX", + "data": "deadbeef", + "processEscapeChars": true, + "sendOnEnterValueForEveryNewLineInTextBox": true + }, + { + "version": 1, + "name": "", + "dataType": "ASCII", + "data": "", + "processEscapeChars": true, + "sendOnEnterValueForEveryNewLineInTextBox": true + }, + { + "version": 1, + "name": "", + "dataType": "ASCII", + "data": "", + "processEscapeChars": true, + "sendOnEnterValueForEveryNewLineInTextBox": true + }, + { + "version": 1, + "name": "", + "dataType": "ASCII", + "data": "", + "processEscapeChars": true, + "sendOnEnterValueForEveryNewLineInTextBox": true + }, + { + "version": 1, + "name": "", + "dataType": "ASCII", + "data": "", + "processEscapeChars": true, + "sendOnEnterValueForEveryNewLineInTextBox": true + }, + { + "version": 1, + "name": "", + "dataType": "ASCII", + "data": "", + "processEscapeChars": true, + "sendOnEnterValueForEveryNewLineInTextBox": true + }, + { + "version": 1, + "name": "", + "dataType": "ASCII", + "data": "", + "processEscapeChars": true, + "sendOnEnterValueForEveryNewLineInTextBox": true + } + ] + } + }, + "lastUsedSerialPort": { + "serialPortInfo": {}, + "portState": 2 + }, + "settings": { + "portSettings": { + "version": 2, + "baudRate": 115200, + "numDataBits": 8, + "parity": "none", + "stopBits": 1, + "flowControl": "none", + "connectToSerialPortAsSoonAsItIsSelected": true, + "resumeConnectionToLastSerialPortOnStartup": true, + "reopenSerialPortIfUnexpectedlyClosed": true + }, + "txSettings": { + "version": 1, + "enterKeyPressBehavior": 0, + "backspaceKeyPressBehavior": 0, + "deleteKeyPressBehavior": 2, + "send0x01Thru0x1AWhenCtrlAThruZPressed": true, + "sendEscCharWhenAltKeyPressed": true + }, + "rxSettings": { + "version": 1, + "dataType": 0, + "ansiEscapeCodeParsingEnabled": true, + "maxEscapeCodeLengthChars": 10, + "localTxEcho": false, + "newLineCursorBehavior": 2, + "swallowNewLine": true, + "carriageReturnCursorBehavior": 0, + "swallowCarriageReturn": true, + "nonVisibleCharDisplayBehavior": 1, + "numberType": "Hex", + "endianness": "Little Endian", + "numberSeparator": " ", + "preventValuesWrappingAcrossRows": true, + "insertNewLineOnMatchedValue": false, + "newLineMatchValueAsHex": "00", + "newLinePlacementOnHexValue": 0, + "padValues": true, + "paddingCharacter": 1, + "numPaddingChars": -1, + "numBytesPerHexNumber": 1, + "hexCase": 0, + "prefixHexValuesWith0x": false, + "floatStringConversionMethod": "toString()", + "floatNumOfDecimalPlaces": 5 + }, + "displaySettings": { + "version": 1, + "charSizePx": 14, + "verticalRowPaddingPx": 5, + "terminalWidthChars": 120, + "scrollbackBufferSizeRows": 2000, + "dataViewConfiguration": 0 + }, + "generalSettings": { + "version": 1, + "whenPastingOnWindowsReplaceCRLFWithLF": true, + "whenCopyingToClipboardDoNotAddLFIfRowWasCreatedDueToWrapping": true + } + } + } + }, + { + "name": "ssddf", + "rootConfig": { + "version": 1, + "terminal": { + "macroController": { + "version": 1, + "macroConfigs": [ + { + "version": 1, + "name": "", + "dataType": "ASCII", + "data": "Hello\\n", + "processEscapeChars": true, + "sendOnEnterValueForEveryNewLineInTextBox": true + }, + { + "version": 1, + "name": "", + "dataType": "HEX", + "data": "deadbeef", + "processEscapeChars": true, + "sendOnEnterValueForEveryNewLineInTextBox": true + }, + { + "version": 1, + "name": "", + "dataType": "ASCII", + "data": "", + "processEscapeChars": true, + "sendOnEnterValueForEveryNewLineInTextBox": true + }, + { + "version": 1, + "name": "", + "dataType": "ASCII", + "data": "", + "processEscapeChars": true, + "sendOnEnterValueForEveryNewLineInTextBox": true + }, + { + "version": 1, + "name": "", + "dataType": "ASCII", + "data": "", + "processEscapeChars": true, + "sendOnEnterValueForEveryNewLineInTextBox": true + }, + { + "version": 1, + "name": "", + "dataType": "ASCII", + "data": "", + "processEscapeChars": true, + "sendOnEnterValueForEveryNewLineInTextBox": true + }, + { + "version": 1, + "name": "", + "dataType": "ASCII", + "data": "", + "processEscapeChars": true, + "sendOnEnterValueForEveryNewLineInTextBox": true + }, + { + "version": 1, + "name": "", + "dataType": "ASCII", + "data": "", + "processEscapeChars": true, + "sendOnEnterValueForEveryNewLineInTextBox": true + } + ] + } + }, + "lastUsedSerialPort": { + "serialPortInfo": {}, + "portState": 2 + }, + "settings": { + "portSettings": { + "version": 2, + "baudRate": 115200, + "numDataBits": 8, + "parity": "none", + "stopBits": 1, + "flowControl": "none", + "connectToSerialPortAsSoonAsItIsSelected": true, + "resumeConnectionToLastSerialPortOnStartup": true, + "reopenSerialPortIfUnexpectedlyClosed": true + }, + "txSettings": { + "version": 1, + "enterKeyPressBehavior": 0, + "backspaceKeyPressBehavior": 0, + "deleteKeyPressBehavior": 2, + "send0x01Thru0x1AWhenCtrlAThruZPressed": true, + "sendEscCharWhenAltKeyPressed": true + }, + "rxSettings": { + "version": 1, + "dataType": 0, + "ansiEscapeCodeParsingEnabled": true, + "maxEscapeCodeLengthChars": 10, + "localTxEcho": false, + "newLineCursorBehavior": 2, + "swallowNewLine": true, + "carriageReturnCursorBehavior": 0, + "swallowCarriageReturn": true, + "nonVisibleCharDisplayBehavior": 1, + "numberType": "Hex", + "endianness": "Little Endian", + "numberSeparator": " ", + "preventValuesWrappingAcrossRows": true, + "insertNewLineOnMatchedValue": false, + "newLineMatchValueAsHex": "00", + "newLinePlacementOnHexValue": 0, + "padValues": true, + "paddingCharacter": 1, + "numPaddingChars": -1, + "numBytesPerHexNumber": 1, + "hexCase": 0, + "prefixHexValuesWith0x": false, + "floatStringConversionMethod": "toString()", + "floatNumOfDecimalPlaces": 5 + }, + "displaySettings": { + "version": 1, + "charSizePx": 14, + "verticalRowPaddingPx": 5, + "terminalWidthChars": 120, + "scrollbackBufferSizeRows": 2000, + "dataViewConfiguration": 0 + }, + "generalSettings": { + "version": 1, + "whenPastingOnWindowsReplaceCRLFWithLF": true, + "whenCopyingToClipboardDoNotAddLFIfRowWasCreatedDueToWrapping": true + } + } + } + }, + { + "name": "-2.3", + "rootConfig": { + "version": 1, + "terminal": { + "macroController": { + "version": 1, + "macroConfigs": [ + { + "version": 1, + "name": "", + "dataType": "ASCII", + "data": "Hello\\n", + "processEscapeChars": true, + "sendOnEnterValueForEveryNewLineInTextBox": true + }, + { + "version": 1, + "name": "", + "dataType": "HEX", + "data": "deadbeef", + "processEscapeChars": true, + "sendOnEnterValueForEveryNewLineInTextBox": true + }, + { + "version": 1, + "name": "", + "dataType": "ASCII", + "data": "", + "processEscapeChars": true, + "sendOnEnterValueForEveryNewLineInTextBox": true + }, + { + "version": 1, + "name": "", + "dataType": "ASCII", + "data": "", + "processEscapeChars": true, + "sendOnEnterValueForEveryNewLineInTextBox": true + }, + { + "version": 1, + "name": "", + "dataType": "ASCII", + "data": "", + "processEscapeChars": true, + "sendOnEnterValueForEveryNewLineInTextBox": true + }, + { + "version": 1, + "name": "", + "dataType": "ASCII", + "data": "", + "processEscapeChars": true, + "sendOnEnterValueForEveryNewLineInTextBox": true + }, + { + "version": 1, + "name": "", + "dataType": "ASCII", + "data": "", + "processEscapeChars": true, + "sendOnEnterValueForEveryNewLineInTextBox": true + }, + { + "version": 1, + "name": "", + "dataType": "ASCII", + "data": "", + "processEscapeChars": true, + "sendOnEnterValueForEveryNewLineInTextBox": true + } + ] + } + }, + "lastUsedSerialPort": { + "serialPortInfo": {}, + "portState": 2 + }, + "settings": { + "portSettings": { + "version": 2, + "baudRate": 115200, + "numDataBits": 8, + "parity": "none", + "stopBits": 1, + "flowControl": "none", + "connectToSerialPortAsSoonAsItIsSelected": true, + "resumeConnectionToLastSerialPortOnStartup": true, + "reopenSerialPortIfUnexpectedlyClosed": true + }, + "txSettings": { + "version": 1, + "enterKeyPressBehavior": 0, + "backspaceKeyPressBehavior": 0, + "deleteKeyPressBehavior": 2, + "send0x01Thru0x1AWhenCtrlAThruZPressed": true, + "sendEscCharWhenAltKeyPressed": true + }, + "rxSettings": { + "version": 1, + "dataType": 0, + "ansiEscapeCodeParsingEnabled": true, + "maxEscapeCodeLengthChars": 10, + "localTxEcho": false, + "newLineCursorBehavior": 2, + "swallowNewLine": true, + "carriageReturnCursorBehavior": 0, + "swallowCarriageReturn": true, + "nonVisibleCharDisplayBehavior": 1, + "numberType": "Hex", + "endianness": "Little Endian", + "numberSeparator": " ", + "preventValuesWrappingAcrossRows": true, + "insertNewLineOnMatchedValue": false, + "newLineMatchValueAsHex": "00", + "newLinePlacementOnHexValue": 0, + "padValues": true, + "paddingCharacter": 1, + "numPaddingChars": -1, + "numBytesPerHexNumber": 1, + "hexCase": 0, + "prefixHexValuesWith0x": false, + "floatStringConversionMethod": "toString()", + "floatNumOfDecimalPlaces": 5 + }, + "displaySettings": { + "version": 1, + "charSizePx": 14, + "verticalRowPaddingPx": 5, + "terminalWidthChars": 120, + "scrollbackBufferSizeRows": 2000, + "dataViewConfiguration": 0 + }, + "generalSettings": { + "version": 1, + "whenPastingOnWindowsReplaceCRLFWithLF": true, + "whenCopyingToClipboardDoNotAddLFIfRowWasCreatedDueToWrapping": true + } + } + } + }, + { + "name": "New profile 3", + "rootConfig": { + "version": 1, + "terminal": { + "macroController": { + "version": 1, + "macroConfigs": [ + { + "version": 1, + "name": "", + "dataType": "ASCII", + "data": "Hello\\n", + "processEscapeChars": true, + "sendOnEnterValueForEveryNewLineInTextBox": true + }, + { + "version": 1, + "name": "", + "dataType": "HEX", + "data": "deadbeef", + "processEscapeChars": true, + "sendOnEnterValueForEveryNewLineInTextBox": true + }, + { + "version": 1, + "name": "", + "dataType": "ASCII", + "data": "", + "processEscapeChars": true, + "sendOnEnterValueForEveryNewLineInTextBox": true + }, + { + "version": 1, + "name": "", + "dataType": "ASCII", + "data": "", + "processEscapeChars": true, + "sendOnEnterValueForEveryNewLineInTextBox": true + }, + { + "version": 1, + "name": "", + "dataType": "ASCII", + "data": "", + "processEscapeChars": true, + "sendOnEnterValueForEveryNewLineInTextBox": true + }, + { + "version": 1, + "name": "", + "dataType": "ASCII", + "data": "", + "processEscapeChars": true, + "sendOnEnterValueForEveryNewLineInTextBox": true + }, + { + "version": 1, + "name": "", + "dataType": "ASCII", + "data": "", + "processEscapeChars": true, + "sendOnEnterValueForEveryNewLineInTextBox": true + }, + { + "version": 1, + "name": "", + "dataType": "ASCII", + "data": "", + "processEscapeChars": true, + "sendOnEnterValueForEveryNewLineInTextBox": true + } + ] + } + }, + "lastUsedSerialPort": { + "serialPortInfo": {}, + "portState": 2 + }, + "settings": { + "portSettings": { + "version": 2, + "baudRate": 115200, + "numDataBits": 8, + "parity": "none", + "stopBits": 1, + "flowControl": "none", + "connectToSerialPortAsSoonAsItIsSelected": true, + "resumeConnectionToLastSerialPortOnStartup": true, + "reopenSerialPortIfUnexpectedlyClosed": true + }, + "txSettings": { + "version": 1, + "enterKeyPressBehavior": 0, + "backspaceKeyPressBehavior": 0, + "deleteKeyPressBehavior": 2, + "send0x01Thru0x1AWhenCtrlAThruZPressed": true, + "sendEscCharWhenAltKeyPressed": true + }, + "rxSettings": { + "version": 1, + "dataType": 0, + "ansiEscapeCodeParsingEnabled": true, + "maxEscapeCodeLengthChars": 10, + "localTxEcho": false, + "newLineCursorBehavior": 2, + "swallowNewLine": true, + "carriageReturnCursorBehavior": 0, + "swallowCarriageReturn": true, + "nonVisibleCharDisplayBehavior": 1, + "numberType": "Hex", + "endianness": "Little Endian", + "numberSeparator": " ", + "preventValuesWrappingAcrossRows": true, + "insertNewLineOnMatchedValue": false, + "newLineMatchValueAsHex": "00", + "newLinePlacementOnHexValue": 0, + "padValues": true, + "paddingCharacter": 1, + "numPaddingChars": -1, + "numBytesPerHexNumber": 1, + "hexCase": 0, + "prefixHexValuesWith0x": false, + "floatStringConversionMethod": "toString()", + "floatNumOfDecimalPlaces": 5 + }, + "displaySettings": { + "version": 1, + "charSizePx": 14, + "verticalRowPaddingPx": 5, + "terminalWidthChars": 120, + "scrollbackBufferSizeRows": 2000, + "dataViewConfiguration": 0 + }, + "generalSettings": { + "version": 1, + "whenPastingOnWindowsReplaceCRLFWithLF": true, + "whenCopyingToClipboardDoNotAddLFIfRowWasCreatedDueToWrapping": true + } + } + } + }, + { + "name": "Hello", + "rootConfig": { + "version": 1, + "terminal": { + "macroController": { + "version": 1, + "macroConfigs": [ + { + "version": 1, + "name": "", + "dataType": "ASCII", + "data": "Hello\\n", + "processEscapeChars": true, + "sendOnEnterValueForEveryNewLineInTextBox": true + }, + { + "version": 1, + "name": "", + "dataType": "HEX", + "data": "deadbeef", + "processEscapeChars": true, + "sendOnEnterValueForEveryNewLineInTextBox": true + }, + { + "version": 1, + "name": "", + "dataType": "ASCII", + "data": "", + "processEscapeChars": true, + "sendOnEnterValueForEveryNewLineInTextBox": true + }, + { + "version": 1, + "name": "", + "dataType": "ASCII", + "data": "", + "processEscapeChars": true, + "sendOnEnterValueForEveryNewLineInTextBox": true + }, + { + "version": 1, + "name": "", + "dataType": "ASCII", + "data": "", + "processEscapeChars": true, + "sendOnEnterValueForEveryNewLineInTextBox": true + }, + { + "version": 1, + "name": "", + "dataType": "ASCII", + "data": "", + "processEscapeChars": true, + "sendOnEnterValueForEveryNewLineInTextBox": true + }, + { + "version": 1, + "name": "", + "dataType": "ASCII", + "data": "", + "processEscapeChars": true, + "sendOnEnterValueForEveryNewLineInTextBox": true + }, + { + "version": 1, + "name": "", + "dataType": "ASCII", + "data": "", + "processEscapeChars": true, + "sendOnEnterValueForEveryNewLineInTextBox": true + } + ] + } + }, + "lastUsedSerialPort": { + "serialPortInfo": { + "usbProductId": 67, + "usbVendorId": 9025 + }, + "portState": 2 + }, + "settings": { + "portSettings": { + "version": 2, + "baudRate": 115200, + "numDataBits": 8, + "parity": "none", + "stopBits": 1, + "flowControl": "none", + "connectToSerialPortAsSoonAsItIsSelected": true, + "resumeConnectionToLastSerialPortOnStartup": true, + "reopenSerialPortIfUnexpectedlyClosed": true + }, + "txSettings": { + "version": 1, + "enterKeyPressBehavior": 0, + "backspaceKeyPressBehavior": "1", + "deleteKeyPressBehavior": 2, + "send0x01Thru0x1AWhenCtrlAThruZPressed": true, + "sendEscCharWhenAltKeyPressed": true + }, + "rxSettings": { + "version": 1, + "dataType": 0, + "ansiEscapeCodeParsingEnabled": true, + "maxEscapeCodeLengthChars": 10, + "localTxEcho": false, + "newLineCursorBehavior": 2, + "swallowNewLine": true, + "carriageReturnCursorBehavior": 0, + "swallowCarriageReturn": true, + "nonVisibleCharDisplayBehavior": 1, + "numberType": "Hex", + "endianness": "Little Endian", + "numberSeparator": " ", + "preventValuesWrappingAcrossRows": true, + "insertNewLineOnMatchedValue": false, + "newLineMatchValueAsHex": "00", + "newLinePlacementOnHexValue": 0, + "padValues": true, + "paddingCharacter": 1, + "numPaddingChars": -1, + "numBytesPerHexNumber": 1, + "hexCase": 0, + "prefixHexValuesWith0x": false, + "floatStringConversionMethod": "toString()", + "floatNumOfDecimalPlaces": 5 + }, + "displaySettings": { + "version": 1, + "charSizePx": 14, + "verticalRowPaddingPx": 5, + "terminalWidthChars": 120, + "scrollbackBufferSizeRows": 2000, + "dataViewConfiguration": 0 + }, + "generalSettings": { + "version": 1, + "whenPastingOnWindowsReplaceCRLFWithLF": true, + "whenCopyingToClipboardDoNotAddLFIfRowWasCreatedDueToWrapping": true + } + } + } + } +] diff --git a/src/model/App.tsx b/src/model/App.tsx index 35e6bd5c..5170d65e 100644 --- a/src/model/App.tsx +++ b/src/model/App.tsx @@ -208,7 +208,7 @@ export class App { if (this.portState === PortState.CLOSED_BUT_WILL_REOPEN) { // Check to see if this is the serial port we want to reopen - const lastUsedPortInfo = this.profileManager.currentAppConfig.lastUsedSerialPort; + const lastUsedPortInfo = this.profileManager.appData.currentAppConfig.lastUsedSerialPort; if (lastUsedPortInfo === null) { return; } @@ -233,7 +233,7 @@ export class App { let approvedPorts = await navigator.serial.getPorts(); // const lastUsedSerialPort = this.appStorage.data.lastUsedSerialPort; - const lastUsedSerialPort = this.profileManager.currentAppConfig.lastUsedSerialPort; + const lastUsedSerialPort = this.profileManager.appData.currentAppConfig.lastUsedSerialPort; if (lastUsedSerialPort === null) { // Did not find last used serial port data in local storage, so do nothing return; @@ -303,9 +303,9 @@ export class App { this.serialPortInfo = this.port.getInfo(); // Save the info for this port, so we can automatically re-open // it on app re-open in the future - let lastUsedSerialPort = this.profileManager.currentAppConfig.lastUsedSerialPort; + let lastUsedSerialPort = this.profileManager.appData.currentAppConfig.lastUsedSerialPort; lastUsedSerialPort.serialPortInfo = JSON.parse(JSON.stringify(this.serialPortInfo)); - this.profileManager.saveAppConfig(); + this.profileManager.saveAppData(); }); if (this.settings.portConfiguration.connectToSerialPortAsSoonAsItIsSelected) { await this.openPort(); @@ -404,9 +404,9 @@ export class App { this.closedPromise = this.readUntilClosed(); }); - const lastUsedSerialPort = this.profileManager.currentAppConfig.lastUsedSerialPort; + const lastUsedSerialPort = this.profileManager.appData.currentAppConfig.lastUsedSerialPort; lastUsedSerialPort.portState = PortState.OPENED; - this.profileManager.saveAppConfig(); + this.profileManager.saveAppData(); // Create custom GA4 event to see how many ports have // been opened in NinjaTerm :-) @@ -553,9 +553,9 @@ export class App { } this.reader = null; this.closedPromise = null; - const lastUsedSerialPort = this.profileManager.currentAppConfig.lastUsedSerialPort; + const lastUsedSerialPort = this.profileManager.appData.currentAppConfig.lastUsedSerialPort; lastUsedSerialPort.portState = PortState.CLOSED; - this.profileManager.saveAppConfig(); + this.profileManager.saveAppData(); } else if (this.lastSelectedPortType === PortType.FAKE) { this.fakePortController.closePort(); } else { @@ -575,7 +575,7 @@ export class App { * - Pressing "f" while on the Port Configuration settings. */ async handleKeyDown(event: React.KeyboardEvent) { - console.log('handleKeyDown() called. event.key=', event.key); + // console.log('handleKeyDown() called. event.key=', event.key); // SPECIAL TESTING "FAKE PORTS" if (this.shownMainPane === MainPanes.SETTINGS && this.settings.activeSettingsCategory === SettingsCategories.PORT_CONFIGURATION && event.key === 'f') { this.fakePortController.setIsDialogOpen(true); diff --git a/src/model/ProfileManager/ProfileManager.spec.ts b/src/model/ProfileManager/ProfileManager.spec.ts index abc5931a..6ad59523 100644 --- a/src/model/ProfileManager/ProfileManager.spec.ts +++ b/src/model/ProfileManager/ProfileManager.spec.ts @@ -13,15 +13,15 @@ describe("profile manager tests", () => { test("default profile should be created", () => { const app = new App(); const profileManager = new ProfileManager(app); - expect(profileManager.profiles.length).toEqual(1); - expect(profileManager.profiles[0].name).toEqual("Default profile"); + expect(profileManager.appData.profiles.length).toEqual(1); + expect(profileManager.appData.profiles[0].name).toEqual("Default profile"); }); test("new profile can be created", () => { const app = new App(); const profileManager = new ProfileManager(app); profileManager.newProfile(); - expect(profileManager.profiles.length).toEqual(2); - expect(profileManager.profiles[1].name).toEqual("New profile 1"); + expect(profileManager.appData.profiles.length).toEqual(2); + expect(profileManager.appData.profiles[1].name).toEqual("New profile 1"); }); }); diff --git a/src/model/ProfileManager/ProfileManager.ts b/src/model/ProfileManager/ProfileManager.ts index bf49526e..0b3d12b8 100644 --- a/src/model/ProfileManager/ProfileManager.ts +++ b/src/model/ProfileManager/ProfileManager.ts @@ -1,13 +1,14 @@ -import { makeAutoObservable } from "mobx"; - -import { DisplaySettingsConfig } from "../Settings/DisplaySettings/DisplaySettings"; -import { GeneralSettingsConfig } from "../Settings/GeneralSettings/GeneralSettings"; -import { PortConfigurationConfig, PortState } from "../Settings/PortConfigurationSettings/PortConfigurationSettings"; -import { RxSettingsConfig } from "../Settings/RxSettings/RxSettings"; -import { TxSettingsConfig } from "../Settings/TxSettings/TxSettings"; -import { MacroControllerConfig } from "../Terminals/RightDrawer/Macros/MacroController"; -import { App } from "../App"; -import { VariantType } from "notistack"; +import { makeAutoObservable } from 'mobx'; + +import { DisplaySettingsConfig } from '../Settings/DisplaySettings/DisplaySettings'; +import { GeneralSettingsConfig } from '../Settings/GeneralSettings/GeneralSettings'; +import { PortConfigurationConfig, PortState } from '../Settings/PortConfigurationSettings/PortConfigurationSettings'; +import { RxSettingsConfig } from '../Settings/RxSettings/RxSettings'; +import { TxSettingsConfig } from '../Settings/TxSettings/TxSettings'; +import { MacroControllerConfig } from '../Terminals/RightDrawer/Macros/MacroController'; +import { App } from '../App'; +import { VariantType } from 'notistack'; +import { RightDrawerConfig } from '../Terminals/RightDrawer/RightDrawer'; export class LastUsedSerialPort { serialPortInfo: Partial = {}; @@ -18,10 +19,11 @@ export class LastUsedSerialPort { * Everything in this class must be POD (plain old data) and serializable to JSON. */ export class RootConfig { - version = 1; + version = 2; terminal = { macroController: new MacroControllerConfig(), + rightDrawer: new RightDrawerConfig(), }; lastUsedSerialPort: LastUsedSerialPort = new LastUsedSerialPort(); @@ -41,7 +43,7 @@ export class RootConfig { * embedded device). The class is serializable to JSON. */ export class Profile { - name: string = ""; + name: string = ''; rootConfig: RootConfig = new RootConfig(); constructor(name: string) { @@ -50,10 +52,13 @@ export class Profile { } } -const PROFILES_STORAGE_KEY = "profiles"; +/** + * This class represents all the data that the app needs to store/load from + * local storage (i.e. the root object). It must be serializable to JSON. + */ +export class AppData { -export class ProfileManager { - app: App; + version = 1; profiles: Profile[] = []; @@ -63,42 +68,64 @@ export class ProfileManager { */ currentAppConfig: RootConfig = new RootConfig(); + constructor() { + makeAutoObservable(this); + } +} + +const PROFILES_STORAGE_KEY = 'profiles'; + +const APP_DATA_STORAGE_KEY = 'appData'; + +export class ProfileManager { + app: App; + + appData: AppData = new AppData(); + _profileChangeCallbacks: (() => void)[] = []; /** * Represents the name of the last profile that was applied to the app. Used for displaying * in various places such as the toolbar. */ - lastAppliedProfileName: string = "No profile"; + lastAppliedProfileName: string = 'No profile'; constructor(app: App) { this.app = app; - addEventListener("storage", (event) => { - console.log("Caught storage event. event.key: ", event.key, " event.newValue: ", event.newValue); + addEventListener('storage', (event) => { + console.log('Caught storage event. event.key: ', event.key, ' event.newValue: ', event.newValue); if (event.key === PROFILES_STORAGE_KEY) { - console.log("Profiles changed. Reloading..."); - this._loadProfilesFromStorage(); + console.log('Profiles changed. Reloading...'); + this._loadAppDataFromStorage(); } }); // Read in profiles - this._loadProfilesFromStorage(); + this._loadAppDataFromStorage(); // Load current app config - const currentAppConfigJson = window.localStorage.getItem("currentAppConfig"); - let currentAppConfig: RootConfig; - if (currentAppConfigJson === null) { - // No config key found in users store, create one! - currentAppConfig = new RootConfig(); - // Save just-created config back to store. - window.localStorage.setItem("currentAppConfig", JSON.stringify(this.currentAppConfig)); - } else { - currentAppConfig = JSON.parse(currentAppConfigJson); - console.log("Loading current app config from local storage. currentAppConfig: ", currentAppConfig); - } - this.currentAppConfig = currentAppConfig; + // const currentAppConfigJson = window.localStorage.getItem('currentAppConfig'); + // let currentAppConfig: RootConfig; + // if (currentAppConfigJson === null) { + // // No config key found in users store, create one! + // currentAppConfig = new RootConfig(); + // // Save just-created config back to store. + // window.localStorage.setItem('currentAppConfig', JSON.stringify(this.currentAppConfig)); + // } else { + // currentAppConfig = JSON.parse(currentAppConfigJson); + // console.log('Loading current app config from local storage. currentAppConfig: ', currentAppConfig); + // if (currentAppConfig.version == 1) { + // currentAppConfig.terminal.rightDrawer = new RightDrawerConfig(); + // } else if (currentAppConfig.version == 2) { + // // Do nothing, up-to-date + // } else { + // // This shouldn't happen + // console.error('Unknown config version found: ', currentAppConfig.version); + // } + // } + // this.currentAppConfig = currentAppConfig; makeAutoObservable(this); } @@ -115,38 +142,50 @@ export class ProfileManager { this._profileChangeCallbacks.push(callback); }; - _loadProfilesFromStorage = () => { - const profilesJson = window.localStorage.getItem(PROFILES_STORAGE_KEY); + _loadAppDataFromStorage = () => { + const appDataAsJson = window.localStorage.getItem(APP_DATA_STORAGE_KEY); // let profileManagerData: ProfileManagerData; - let profiles: Profile[]; - if (profilesJson === null) { + let appData: AppData; + if (appDataAsJson === null) { // No config key found in users store, create one! - profiles = []; - profiles.push(new Profile("Default profile")); - console.log("No profiles found in local storage. Creating default profile."); + console.log('App data not found in local storage. Creating default app data.'); + appData = new AppData(); + appData.profiles = []; + appData.profiles.push(new Profile('Default profile')); // Save just-created config back to store. - window.localStorage.setItem(PROFILES_STORAGE_KEY, JSON.stringify(profiles)); + window.localStorage.setItem(APP_DATA_STORAGE_KEY, JSON.stringify(appData)); } else { - profiles = JSON.parse(profilesJson); + // A version of app data was found in local storage. Load it. + let appDataUnknownVersion = JSON.parse(appDataAsJson); + if (appDataUnknownVersion.version == 1) { + // Latest version + appData = appDataUnknownVersion; + } else { + console.error('Unknown app data version found: ', appDataUnknownVersion.version); + appData = new AppData(); + appData.profiles = []; + appData.profiles.push(new Profile('Default profile')); + // Save just-created config back to store. + window.localStorage.setItem(APP_DATA_STORAGE_KEY, JSON.stringify(appData)); + } } - // Only support the 1 active config for now - // this.activeProfile = this.profiles[0]; // Load data into class - this.profiles = profiles; + // this.profiles = profiles; + this.appData = appData; }; - saveProfiles = () => { - console.log("Saving profiles..."); - window.localStorage.setItem(PROFILES_STORAGE_KEY, JSON.stringify(this.profiles)); - }; + // saveProfiles = () => { + // console.log('Saving profiles...'); + // window.localStorage.setItem(PROFILES_STORAGE_KEY, JSON.stringify(this.profiles)); + // }; /** - * Save all profiles to local storage. + * Save the current app configuration to local storage. */ - saveAppConfig = () => { - console.log("Saving app config..."); - window.localStorage.setItem("currentAppConfig", JSON.stringify(this.currentAppConfig)); + saveAppData = () => { + console.log('Saving app data. appData: ', this.appData); + window.localStorage.setItem(APP_DATA_STORAGE_KEY, JSON.stringify(this.appData)); }; /** @@ -155,20 +194,20 @@ export class ProfileManager { newProfile = () => { // Calculate name for new profile, in the form "New profile X" where X is the next number let nextProfileNum = 1; - const newProfileName = "New profile"; - let newProfileNameToCheck = newProfileName + " " + nextProfileNum; - while (this.profiles.find((profile) => profile.name === newProfileNameToCheck) !== undefined) { + const newProfileName = 'New profile'; + let newProfileNameToCheck = newProfileName + ' ' + nextProfileNum; + while (this.appData.profiles.find((profile) => profile.name === newProfileNameToCheck) !== undefined) { nextProfileNum++; - newProfileNameToCheck = newProfileName + " " + nextProfileNum; + newProfileNameToCheck = newProfileName + ' ' + nextProfileNum; } // At this point newProfileNameToCheck is the name we want const newProfile = new Profile(newProfileNameToCheck); - this.profiles.push(newProfile); - this.saveProfiles(); + this.appData.profiles.push(newProfile); + this.saveAppData(); // Automatically save the current app state to the newly created profile // and silence the snackbar message - this.saveCurrentAppConfigToProfile(this.profiles.length - 1, true); + this.saveCurrentAppConfigToProfile(this.appData.profiles.length - 1, true); }; /** @@ -176,8 +215,8 @@ export class ProfileManager { * @param profileIdx The index of the profile to delete. */ deleteProfile = (profileIdx: number) => { - this.profiles.splice(profileIdx, 1); - this.saveProfiles(); + this.appData.profiles.splice(profileIdx, 1); + this.saveAppData(); }; /** @@ -186,25 +225,25 @@ export class ProfileManager { * @param profileIdx The index of the profile to apply to the app. */ applyProfileToApp = async (profileIdx: number) => { - const profile = this.profiles[profileIdx]; + const profile = this.appData.profiles[profileIdx]; // Check the last connected serial port of the profile and compare with // currently connected one const profileSerialPortInfoJson = JSON.stringify(profile.rootConfig.lastUsedSerialPort.serialPortInfo); - const currentSerialPortInfoJson = JSON.stringify(this.currentAppConfig.lastUsedSerialPort.serialPortInfo); + const currentSerialPortInfoJson = JSON.stringify(this.appData.currentAppConfig.lastUsedSerialPort.serialPortInfo); let weNeedToConnect = false; let matchedAvailablePorts: SerialPort[] = []; let snackbarMessage = `Profile "${profile.name}" loaded.`; let snackbarVariant: VariantType = 'success'; - if (profileSerialPortInfoJson == "{}") { + if (profileSerialPortInfoJson == '{}') { weNeedToConnect = false; } else if (profileSerialPortInfoJson === currentSerialPortInfoJson) { // Same serial port, no need to disconnect and connect weNeedToConnect = false; } else { // They are both different and the profile one is non-empty. Check to see if the profile ports is available - console.log("Port infos are both different and non-empty. Checking if ports are available..."); + console.log('Port infos are both different and non-empty. Checking if ports are available...'); const availablePorts = await navigator.serial.getPorts(); matchedAvailablePorts = availablePorts.filter((port) => JSON.stringify(port.getInfo()) === profileSerialPortInfoJson); @@ -220,7 +259,7 @@ export class ProfileManager { // There are multiple ports that match the profile port, to ambiguous, do // not connect to any weNeedToConnect = false; - snackbarMessage += '\nMultiple available ports info match the profile port info. Not connecting to any.'; + snackbarMessage += '\nMultiple available ports info match the profile port info. Not connecting to any.'; snackbarVariant = 'warning'; } } @@ -229,7 +268,7 @@ export class ProfileManager { if (weNeedToConnect) { if (this.app.portState === PortState.OPENED) { console.log('Closing port...'); - await this.app.closePort({silenceSnackbar: true}); + await this.app.closePort({ silenceSnackbar: true }); } else if (this.app.portState === PortState.CLOSED_BUT_WILL_REOPEN) { this.app.stopWaitingToReopenPort(); } @@ -237,8 +276,8 @@ export class ProfileManager { console.log('Port closed.'); // Update the current app config from the provided profile, // and then save this new app config - this.currentAppConfig = JSON.parse(JSON.stringify(profile.rootConfig)); - this.saveAppConfig(); + this.appData.currentAppConfig = JSON.parse(JSON.stringify(profile.rootConfig)); + this.saveAppData(); // Need to tell the rest of the app to update this._profileChangeCallbacks.forEach((callback) => { @@ -252,7 +291,7 @@ export class ProfileManager { console.log('Setting selected port...', matchedAvailablePorts[0]); this.app.setSelectedPort(matchedAvailablePorts[0]); console.log('Opening port...'); - await this.app.openPort({silenceSnackbar: true}); + await this.app.openPort({ silenceSnackbar: true }); snackbarMessage += '\nConnected to port with info: "' + profileSerialPortInfoJson + '".'; } @@ -264,15 +303,15 @@ export class ProfileManager { * Save the current app config to the provided profile and the save the profiles to local storage. * @param profileIdx The index of the profile to save the current app config to. */ - saveCurrentAppConfigToProfile = (profileIdx: number, noSnackbar=false) => { - console.log("Saving current app config to profile..."); - const profile = this.profiles[profileIdx]; - profile.rootConfig = JSON.parse(JSON.stringify(this.currentAppConfig)); - this.saveProfiles(); + saveCurrentAppConfigToProfile = (profileIdx: number, noSnackbar = false) => { + console.log('Saving current app config to profile...'); + const profile = this.appData.profiles[profileIdx]; + profile.rootConfig = JSON.parse(JSON.stringify(this.appData.currentAppConfig)); + this.saveAppData(); // Post message to snackbar if (!noSnackbar) { - this.app.snackbar.sendToSnackbar('Profile "' + profile.name + '" saved.', "success"); + this.app.snackbar.sendToSnackbar('Profile "' + profile.name + '" saved.', 'success'); } }; } diff --git a/src/model/Settings/DisplaySettings/DisplaySettings.ts b/src/model/Settings/DisplaySettings/DisplaySettings.ts index e9ff9606..888664cb 100644 --- a/src/model/Settings/DisplaySettings/DisplaySettings.ts +++ b/src/model/Settings/DisplaySettings/DisplaySettings.ts @@ -74,7 +74,7 @@ export default class DisplaySettings { }; _saveConfig = () => { - let config = this.profileManager.currentAppConfig.settings.displaySettings; + let config = this.profileManager.appData.currentAppConfig.settings.displaySettings; config.charSizePx = this.charSizePx.appliedValue; config.verticalRowPaddingPx = this.verticalRowPaddingPx.appliedValue; @@ -82,11 +82,11 @@ export default class DisplaySettings { config.scrollbackBufferSizeRows = this.scrollbackBufferSizeRows.appliedValue; config.dataViewConfiguration = this.dataViewConfiguration; - this.profileManager.saveAppConfig(); + this.profileManager.saveAppData(); }; _loadConfig = () => { - let configToLoad = this.profileManager.currentAppConfig.settings.displaySettings; + let configToLoad = this.profileManager.appData.currentAppConfig.settings.displaySettings; //=============================================== // UPGRADE PATH //=============================================== @@ -95,7 +95,7 @@ export default class DisplaySettings { } else { console.log(`Out-of-date config version ${configToLoad.version} found.` + ` Updating to version ${latestVersion}.`); this._saveConfig(); - configToLoad = this.profileManager.currentAppConfig.settings.displaySettings; + configToLoad = this.profileManager.appData.currentAppConfig.settings.displaySettings; } this.charSizePx.setDispValue(configToLoad.charSizePx.toString()); diff --git a/src/model/Settings/GeneralSettings/GeneralSettings.ts b/src/model/Settings/GeneralSettings/GeneralSettings.ts index 87149099..b0e54347 100644 --- a/src/model/Settings/GeneralSettings/GeneralSettings.ts +++ b/src/model/Settings/GeneralSettings/GeneralSettings.ts @@ -39,16 +39,16 @@ export default class RxSettings { }; _saveConfig = () => { - let config = this.profileManager.currentAppConfig.settings.generalSettings; + let config = this.profileManager.appData.currentAppConfig.settings.generalSettings; config.whenPastingOnWindowsReplaceCRLFWithLF = this.whenPastingOnWindowsReplaceCRLFWithLF; config.whenCopyingToClipboardDoNotAddLFIfRowWasCreatedDueToWrapping = this.whenCopyingToClipboardDoNotAddLFIfRowWasCreatedDueToWrapping; - this.profileManager.saveAppConfig(); + this.profileManager.saveAppData(); }; _loadConfig = () => { - let configToLoad = this.profileManager.currentAppConfig.settings.generalSettings; + let configToLoad = this.profileManager.appData.currentAppConfig.settings.generalSettings; //=============================================== // UPGRADE PATH //=============================================== @@ -58,7 +58,7 @@ export default class RxSettings { } else { console.log(`Out-of-date config version ${configToLoad.version} found.` + ` Updating to version ${latestVersion}.`); this._saveConfig(); - configToLoad = this.profileManager.currentAppConfig.settings.generalSettings; + configToLoad = this.profileManager.appData.currentAppConfig.settings.generalSettings; } this.whenPastingOnWindowsReplaceCRLFWithLF = configToLoad.whenPastingOnWindowsReplaceCRLFWithLF; diff --git a/src/model/Settings/PortConfigurationSettings/PortConfigurationSettings.ts b/src/model/Settings/PortConfigurationSettings/PortConfigurationSettings.ts index 861c88a4..4d8b7dd7 100644 --- a/src/model/Settings/PortConfigurationSettings/PortConfigurationSettings.ts +++ b/src/model/Settings/PortConfigurationSettings/PortConfigurationSettings.ts @@ -159,7 +159,7 @@ export default class PortConfiguration { } _loadConfig = () => { - let configToLoad = this.profileManager.currentAppConfig.settings.portSettings + let configToLoad = this.profileManager.appData.currentAppConfig.settings.portSettings //=============================================== // UPGRADE PATH //=============================================== @@ -170,7 +170,7 @@ export default class PortConfiguration { console.log(`Out-of-date config version ${configToLoad.version} found.` + ` Updating to version ${latestVersion}.`); this._saveConfig(); - configToLoad = this.profileManager.currentAppConfig.settings.portSettings + configToLoad = this.profileManager.appData.currentAppConfig.settings.portSettings } // At this point we are confident that the deserialized config matches what @@ -188,7 +188,7 @@ export default class PortConfiguration { }; _saveConfig = () => { - let config = this.profileManager.currentAppConfig.settings.portSettings; + let config = this.profileManager.appData.currentAppConfig.settings.portSettings; config.baudRate = this.baudRate; config.numDataBits = this.numDataBits; @@ -199,7 +199,7 @@ export default class PortConfiguration { config.resumeConnectionToLastSerialPortOnStartup = this.resumeConnectionToLastSerialPortOnStartup; config.reopenSerialPortIfUnexpectedlyClosed = this.reopenSerialPortIfUnexpectedlyClosed; - this.profileManager.saveAppConfig(); + this.profileManager.saveAppData(); }; /** diff --git a/src/model/Settings/ProfileSettings/ProfileSettings.ts b/src/model/Settings/ProfileSettings/ProfileSettings.ts index 308a7c3a..52afccca 100644 --- a/src/model/Settings/ProfileSettings/ProfileSettings.ts +++ b/src/model/Settings/ProfileSettings/ProfileSettings.ts @@ -43,7 +43,7 @@ export default class ProfilesSettings { // Nothing to do if no profile is selected return; } - const selectedProfile = this.profileManager.profiles[this.selectedProfiles[0] as number]; + const selectedProfile = this.profileManager.appData.profiles[this.selectedProfiles[0] as number]; if (selectedProfile.name === this.profileNameText) { // The profile name hasn't changed so nothing to do return; @@ -51,7 +51,7 @@ export default class ProfilesSettings { // If we get here profile name has changed selectedProfile.name = this.profileNameText; // Profile name has changed so save the profiles - this.profileManager.saveProfiles(); + this.profileManager.saveAppData(); } setSelectedProfiles(selectedProfiles: GridRowSelectionModel) { @@ -65,7 +65,7 @@ export default class ProfilesSettings { // Whenever the selected profile changes, update the profile name field to match the selected profile let name; if (this.selectedProfiles.length === 1) { - name = this.profileManager.profiles[this.selectedProfiles[0] as number].name; + name = this.profileManager.appData.profiles[this.selectedProfiles[0] as number].name; } else { name = ''; } diff --git a/src/model/Settings/RxSettings/RxSettings.ts b/src/model/Settings/RxSettings/RxSettings.ts index dc1f72eb..2ccf50ec 100644 --- a/src/model/Settings/RxSettings/RxSettings.ts +++ b/src/model/Settings/RxSettings/RxSettings.ts @@ -209,7 +209,7 @@ export default class RxSettings { } _loadConfig = () => { - let configToLoad = this.profileManager.currentAppConfig.settings.rxSettings + let configToLoad = this.profileManager.appData.currentAppConfig.settings.rxSettings //=============================================== // UPGRADE PATH //=============================================== @@ -220,7 +220,7 @@ export default class RxSettings { console.log(`Out-of-date config version ${configToLoad.version} found.` + ` Updating to version ${latestVersion}.`); this._saveConfig(); - configToLoad = this.profileManager.currentAppConfig.settings.rxSettings + configToLoad = this.profileManager.appData.currentAppConfig.settings.rxSettings } /** @@ -272,7 +272,7 @@ export default class RxSettings { }; _saveConfig = () => { - let config = this.profileManager.currentAppConfig.settings.rxSettings; + let config = this.profileManager.appData.currentAppConfig.settings.rxSettings; config.dataType = this.dataType; // ASCII-SPECIFIC SETTINGS @@ -306,7 +306,7 @@ export default class RxSettings { config.floatStringConversionMethod = this.floatStringConversionMethod; config.floatNumOfDecimalPlaces = this.floatNumOfDecimalPlaces.appliedValue; - this.profileManager.saveAppConfig(); + this.profileManager.saveAppData(); }; setDataType = (value: DataType) => { diff --git a/src/model/Settings/TxSettings/TxSettings.ts b/src/model/Settings/TxSettings/TxSettings.ts index 6ba7eb9d..488d146e 100644 --- a/src/model/Settings/TxSettings/TxSettings.ts +++ b/src/model/Settings/TxSettings/TxSettings.ts @@ -93,7 +93,7 @@ export default class DataProcessingSettings { } _loadConfig = () => { - let configToLoad = this.profileManager.currentAppConfig.settings.txSettings; + let configToLoad = this.profileManager.appData.currentAppConfig.settings.txSettings; //=============================================== // UPGRADE PATH //=============================================== @@ -104,7 +104,7 @@ export default class DataProcessingSettings { console.log(`Out-of-date config version ${configToLoad.version} found.` + ` Updating to version ${latestVersion}.`); this._saveConfig(); - configToLoad = this.profileManager.currentAppConfig.settings.txSettings + configToLoad = this.profileManager.appData.currentAppConfig.settings.txSettings } this.enterKeyPressBehavior = configToLoad.enterKeyPressBehavior; @@ -115,7 +115,7 @@ export default class DataProcessingSettings { }; _saveConfig = () => { - let config = this.profileManager.currentAppConfig.settings.txSettings; + let config = this.profileManager.appData.currentAppConfig.settings.txSettings; config.enterKeyPressBehavior = this.enterKeyPressBehavior; config.backspaceKeyPressBehavior = this.backspaceKeyPressBehavior; @@ -123,7 +123,7 @@ export default class DataProcessingSettings { config.send0x01Thru0x1AWhenCtrlAThruZPressed = this.send0x01Thru0x1AWhenCtrlAThruZPressed; config.sendEscCharWhenAltKeyPressed = this.sendEscCharWhenAltKeyPressed; - this.profileManager.saveAppConfig(); + this.profileManager.saveAppData(); }; setEnterKeyPressBehavior = (value: EnterKeyPressBehavior) => { diff --git a/src/model/Terminals/RightDrawer/Macros/MacroController.ts b/src/model/Terminals/RightDrawer/Macros/MacroController.ts index d8ba0c4d..0dfa28a4 100644 --- a/src/model/Terminals/RightDrawer/Macros/MacroController.ts +++ b/src/model/Terminals/RightDrawer/Macros/MacroController.ts @@ -107,17 +107,17 @@ export class MacroController { _saveConfig = () => { - let config = this.app.profileManager.currentAppConfig.terminal.macroController; + let config = this.app.profileManager.appData.currentAppConfig.terminal.macroController; config.macroConfigs = this.macrosArray.map((macro) => { return macro.toConfig(); }); - this.app.profileManager.saveAppConfig(); + this.app.profileManager.saveAppData(); }; _loadConfig() { - let configToLoad = this.app.profileManager.currentAppConfig.terminal.macroController; + let configToLoad = this.app.profileManager.appData.currentAppConfig.terminal.macroController; //=============================================== // UPGRADE PATH @@ -129,7 +129,7 @@ export class MacroController { console.log(`Out-of-date config version ${configToLoad.version} found.` + ` Updating to version ${latestVersion}.`); this._saveConfig(); - configToLoad = this.app.profileManager.currentAppConfig.terminal.macroController; + configToLoad = this.app.profileManager.appData.currentAppConfig.terminal.macroController; } // If we get here we loaded a valid config. Apply config. diff --git a/src/model/Terminals/RightDrawer/RightDrawer.ts b/src/model/Terminals/RightDrawer/RightDrawer.ts index bdc60c83..a6592f02 100644 --- a/src/model/Terminals/RightDrawer/RightDrawer.ts +++ b/src/model/Terminals/RightDrawer/RightDrawer.ts @@ -1,31 +1,86 @@ import { makeAutoObservable } from 'mobx'; import { MacroController } from './Macros/MacroController'; import { App } from 'src/model/App'; +import { ProfileManager } from 'src/model/ProfileManager/ProfileManager'; + +export class RightDrawerConfig { + /** + * Increment this version number if you need to update this data in this class. + * This will cause the app to ignore whatever is in local storage and use the defaults, + * updating to this new version. + */ + version = 1; + + quickPortSettingsIsExpanded = true; + otherQuickSettingsIsExpanded = true; + macrosIsExpanded = true; +} export default class RightDrawer { + profileManager: ProfileManager; macroController: MacroController; - quickPortSetttingsIsExpanded = false; + quickPortSettingsIsExpanded = false; otherQuickSettingsIsExpanded = false; macrosIsExpanded = false; constructor(app: App) { + this.profileManager = app.profileManager; this.macroController = new MacroController(app); + this._loadConfig(); + this.profileManager.registerOnProfileLoad(() => { + this._loadConfig(); + }); makeAutoObservable(this); // Make sure this near the end } handleQuickPortSettingsAccordionChange = (event: React.SyntheticEvent, isExpanded: boolean) => { - this.quickPortSetttingsIsExpanded = isExpanded; + this.quickPortSettingsIsExpanded = isExpanded; + this._saveConfig(); }; handleOtherQuickSettingsAccordionChange = (event: React.SyntheticEvent, isExpanded: boolean) => { this.otherQuickSettingsIsExpanded = isExpanded; + this._saveConfig(); }; handleMacrosAccordionChange = (event: React.SyntheticEvent, isExpanded: boolean) => { this.macrosIsExpanded = isExpanded; + this._saveConfig(); + }; + + _saveConfig = () => { + let config = this.profileManager.appData.currentAppConfig.terminal.rightDrawer; + + config.quickPortSettingsIsExpanded = this.quickPortSettingsIsExpanded; + config.otherQuickSettingsIsExpanded = this.otherQuickSettingsIsExpanded; + config.macrosIsExpanded = this.macrosIsExpanded; + + console.log('Saving config', config); + + this.profileManager.saveAppData(); + }; + + _loadConfig = () => { + let configToLoad = this.profileManager.appData.currentAppConfig.terminal.rightDrawer; + console.log('configToLoad', configToLoad); + //=============================================== + // UPGRADE PATH + //=============================================== + const latestVersion = new RightDrawerConfig().version; + if (configToLoad.version === latestVersion) { + // Do nothing + } else { + console.log(`Out-of-date config version ${configToLoad.version} found.` + ` Updating to version ${latestVersion}.`); + this._saveConfig(); + configToLoad = this.profileManager.appData.currentAppConfig.terminal.rightDrawer; + } + + this.quickPortSettingsIsExpanded = configToLoad.quickPortSettingsIsExpanded; + this.otherQuickSettingsIsExpanded = configToLoad.otherQuickSettingsIsExpanded; + this.macrosIsExpanded = configToLoad.macrosIsExpanded }; } diff --git a/src/view/Settings/ProfileSettings/ProfileSettingsView.tsx b/src/view/Settings/ProfileSettings/ProfileSettingsView.tsx index 0250f0f1..28bc21fd 100644 --- a/src/view/Settings/ProfileSettings/ProfileSettingsView.tsx +++ b/src/view/Settings/ProfileSettings/ProfileSettingsView.tsx @@ -19,7 +19,7 @@ interface Props { function ProfileSettingsView(props: Props) { const { profileManager, profilesSettings } = props; - const profiles = profileManager.profiles; + const profiles = profileManager.appData.profiles; // Define the columns of the profiles table const columns: GridColDef[] = [ diff --git a/src/view/Terminals/RightDrawer/RightDrawerView.tsx b/src/view/Terminals/RightDrawer/RightDrawerView.tsx index 19fb266d..f7e08e84 100644 --- a/src/view/Terminals/RightDrawer/RightDrawerView.tsx +++ b/src/view/Terminals/RightDrawer/RightDrawerView.tsx @@ -106,11 +106,14 @@ export default observer((props: Props) => {
{/* Spacer to prevent select input title from being clipped */} - } data-testid="test"> + } + data-testid="quick-port-settings-accordion-summary" + > Quick Port Settings From dc1a9a9704c8b70b4da59d9b51c2df4611b9af61 Mon Sep 17 00:00:00 2001 From: Geoffrey Hunter Date: Fri, 14 Jun 2024 21:03:06 +1200 Subject: [PATCH 09/26] Save right-hand drawer open/close state in app data. --- src/model/ProfileManager/ProfileManager.ts | 84 ++++++++----------- .../Terminals/RightDrawer/RightDrawer.ts | 2 + src/model/Terminals/Terminals.ts | 21 +++++ 3 files changed, 60 insertions(+), 47 deletions(-) diff --git a/src/model/ProfileManager/ProfileManager.ts b/src/model/ProfileManager/ProfileManager.ts index 0b3d12b8..1b5146f7 100644 --- a/src/model/ProfileManager/ProfileManager.ts +++ b/src/model/ProfileManager/ProfileManager.ts @@ -73,8 +73,6 @@ export class AppData { } } -const PROFILES_STORAGE_KEY = 'profiles'; - const APP_DATA_STORAGE_KEY = 'appData'; export class ProfileManager { @@ -93,50 +91,47 @@ export class ProfileManager { constructor(app: App) { this.app = app; - addEventListener('storage', (event) => { - console.log('Caught storage event. event.key: ', event.key, ' event.newValue: ', event.newValue); + addEventListener('storage', this.onStorageEvent); - if (event.key === PROFILES_STORAGE_KEY) { - console.log('Profiles changed. Reloading...'); - this._loadAppDataFromStorage(); - } - }); - - // Read in profiles + // Load app data from storage this._loadAppDataFromStorage(); - // Load current app config - // const currentAppConfigJson = window.localStorage.getItem('currentAppConfig'); - // let currentAppConfig: RootConfig; - // if (currentAppConfigJson === null) { - // // No config key found in users store, create one! - // currentAppConfig = new RootConfig(); - // // Save just-created config back to store. - // window.localStorage.setItem('currentAppConfig', JSON.stringify(this.currentAppConfig)); - // } else { - // currentAppConfig = JSON.parse(currentAppConfigJson); - // console.log('Loading current app config from local storage. currentAppConfig: ', currentAppConfig); - // if (currentAppConfig.version == 1) { - // currentAppConfig.terminal.rightDrawer = new RightDrawerConfig(); - // } else if (currentAppConfig.version == 2) { - // // Do nothing, up-to-date - // } else { - // // This shouldn't happen - // console.error('Unknown config version found: ', currentAppConfig.version); - // } - // } - // this.currentAppConfig = currentAppConfig; - makeAutoObservable(this); } - setActiveProfile = (profile: Profile) => { - // this.activeProfile = profile; - // Need to tell the rest of the app to update - this._profileChangeCallbacks.forEach((callback) => { - callback(); - }); - }; + /** + * Function should be registered as a listener for the 'storage' event. It will check + * to see if the profile data in storage has changed and if so, reload the profiles. + * @param event The storage event. + * @returns + */ + onStorageEvent = (event: StorageEvent) => { + console.log('Caught storage event. event.key: ', event.key, ' event.newValue: ', event.newValue); + + if (event.key === APP_DATA_STORAGE_KEY) { + console.log('App data changed from another process. Checking if profiles changed...'); + // Check if the profiles changed + const appDataAsJson = window.localStorage.getItem(APP_DATA_STORAGE_KEY); + if (appDataAsJson === null) { + console.error('App data not found in local storage.'); + return; + } + const appDataInStorage = JSON.parse(appDataAsJson); + if (JSON.stringify(appDataInStorage.profiles) !== JSON.stringify(this.appData.profiles)) { + console.log('Profiles changed. Reloading profiles...'); + // Reload just the profiles, we don't want to overwrite the current app config + this.appData.profiles = appDataInStorage.profiles; + } + } + } + + // setActiveProfile = (profile: Profile) => { + // // this.activeProfile = profile; + // // Need to tell the rest of the app to update + // this._profileChangeCallbacks.forEach((callback) => { + // callback(); + // }); + // }; registerOnProfileLoad = (callback: () => void) => { this._profileChangeCallbacks.push(callback); @@ -158,7 +153,8 @@ export class ProfileManager { // A version of app data was found in local storage. Load it. let appDataUnknownVersion = JSON.parse(appDataAsJson); if (appDataUnknownVersion.version == 1) { - // Latest version + // Latest version, don't need to convert anything + // or save it back appData = appDataUnknownVersion; } else { console.error('Unknown app data version found: ', appDataUnknownVersion.version); @@ -171,15 +167,9 @@ export class ProfileManager { } // Load data into class - // this.profiles = profiles; this.appData = appData; }; - // saveProfiles = () => { - // console.log('Saving profiles...'); - // window.localStorage.setItem(PROFILES_STORAGE_KEY, JSON.stringify(this.profiles)); - // }; - /** * Save the current app configuration to local storage. */ diff --git a/src/model/Terminals/RightDrawer/RightDrawer.ts b/src/model/Terminals/RightDrawer/RightDrawer.ts index a6592f02..53d5994d 100644 --- a/src/model/Terminals/RightDrawer/RightDrawer.ts +++ b/src/model/Terminals/RightDrawer/RightDrawer.ts @@ -11,6 +11,8 @@ export class RightDrawerConfig { */ version = 1; + showRightDrawer = true; + quickPortSettingsIsExpanded = true; otherQuickSettingsIsExpanded = true; macrosIsExpanded = true; diff --git a/src/model/Terminals/Terminals.ts b/src/model/Terminals/Terminals.ts index efed3529..e0746025 100644 --- a/src/model/Terminals/Terminals.ts +++ b/src/model/Terminals/Terminals.ts @@ -8,6 +8,8 @@ import RightDrawer from './RightDrawer/RightDrawer'; export default class Terminals { + app: App; + txRxTerminal: SingleTerminal; rxTerminal: SingleTerminal; @@ -21,6 +23,8 @@ export default class Terminals { showRightDrawer = true; constructor(app: App) { + this.app = app; + this.txRxTerminal = new SingleTerminal('tx-rx-terminal', true, app.settings.rxSettings, app.settings.displaySettings, app.handleTerminalKeyDown); this.rxTerminal = new SingleTerminal('rx-terminal', false, app.settings.rxSettings, app.settings.displaySettings, app.handleTerminalKeyDown); // Not focusable this.txTerminal = new SingleTerminal('tx-terminal', true, app.settings.rxSettings, app.settings.displaySettings, app.handleTerminalKeyDown); @@ -29,6 +33,11 @@ export default class Terminals { this.filterText = new ApplyableTextField('', z.string()); this.filterText.setOnApplyChanged(this.onFilterTextApply); + this._loadConfig(); + this.app.profileManager.registerOnProfileLoad(() => { + this._loadConfig(); + }); + makeAutoObservable(this); // Make sure this near the end } @@ -43,5 +52,17 @@ export default class Terminals { setShowRightDrawer(show: boolean) { this.showRightDrawer = show; + this._saveConfig(); } + + _saveConfig = () => { + let config = this.app.profileManager.appData.currentAppConfig.terminal.rightDrawer; + config.showRightDrawer = this.showRightDrawer; + this.app.profileManager.saveAppData(); + }; + + _loadConfig = () => { + let configToLoad = this.app.profileManager.appData.currentAppConfig.terminal.rightDrawer; + this.showRightDrawer = configToLoad.showRightDrawer; + }; } From 14b2a967f88e9f4b25f537416a559d3620ce94be Mon Sep 17 00:00:00 2001 From: Geoffrey Hunter Date: Fri, 14 Jun 2024 21:21:52 +1200 Subject: [PATCH 10/26] Clicking on the data type in status bar takes you to settings. --- CHANGELOG.md | 10 ++++++++++ src/view/AppView.module.css | 5 +++++ src/view/AppView.tsx | 13 ++++++++++++- 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fb7fbcb..d4adb7a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## Unreleased +### Added + +- Clicking on the data type in the bottom status bar takes you to the settings view where you can change the data type. +- Added accordions to the terminal right-hand drawer, and added more controls such as quick port settings. +- The terminal right-hand drawer now remembers if it was open or closed. This info is also saved in each profile. + +### Changed + +- Improved the way app data is stored in local storage. + ## [4.17.1] - 2024-06-11 ### Changed diff --git a/src/view/AppView.module.css b/src/view/AppView.module.css index 9dfb69c2..144425fc 100644 --- a/src/view/AppView.module.css +++ b/src/view/AppView.module.css @@ -44,3 +44,8 @@ 10% { background-color: var(--blue-bright-color); box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset var(--blue-idle-color) 0 -1px 9px, var(--blue-idle-color) 0 2px 0; } to { background-color: var(--blue-idle-color); } } + +.onHover:hover { + cursor: pointer; + background-color: #404040; +} diff --git a/src/view/AppView.tsx b/src/view/AppView.tsx index ec702d56..6d8eec23 100644 --- a/src/view/AppView.tsx +++ b/src/view/AppView.tsx @@ -30,6 +30,7 @@ import LoggingView from './Logging/LoggingView'; import { SelectionController, SelectionInfo } from '../model/SelectionController/SelectionController'; import 'src/model/WindowTypes'; import { DataType } from 'src/model/Settings/RxSettings/RxSettings'; +import { SettingsCategories } from 'src/model/Settings/Settings'; // Create dark theme for MUI const darkTheme = createTheme({ @@ -284,7 +285,17 @@ const AppView = observer((props: Props) => { }} > {/* DATA TYPE */} -
{app.settings.rxSettings.getDataTypeNameForToolbarDisplay()}
+
{ + // Go to Settings -> RX Settings where the user can change the data type + app.setShownMainPane(MainPanes.SETTINGS); + app.settings.setActiveSettingsCategory(SettingsCategories.RX_SETTINGS); + }} + style={{ padding: '0 10px' }} + > + {app.settings.rxSettings.getDataTypeNameForToolbarDisplay()} +
{/* LOGGING ON/OFF */}
{app.logging.isLogging ? 'Logging ON' : 'Logging OFF'}
From 96bf7dd2cc563230571ab273e1cd3395c1199ba1 Mon Sep 17 00:00:00 2001 From: Geoffrey Hunter Date: Fri, 14 Jun 2024 21:28:53 +1200 Subject: [PATCH 11/26] Add click capability to more status bar items. --- CHANGELOG.md | 2 +- src/view/AppView.tsx | 39 +++++++++++++++++++++++++++++++++++---- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4adb7a0..e5bfcdc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added -- Clicking on the data type in the bottom status bar takes you to the settings view where you can change the data type. +- Clicking on the many of the indicators in the bottom status bar takes you to the settings view where you can change the related settings. - Added accordions to the terminal right-hand drawer, and added more controls such as quick port settings. - The terminal right-hand drawer now remembers if it was open or closed. This info is also saved in each profile. diff --git a/src/view/AppView.tsx b/src/view/AppView.tsx index 6d8eec23..e189677a 100644 --- a/src/view/AppView.tsx +++ b/src/view/AppView.tsx @@ -298,10 +298,26 @@ const AppView = observer((props: Props) => {
{/* LOGGING ON/OFF */} -
{app.logging.isLogging ? 'Logging ON' : 'Logging OFF'}
+
{ + app.setShownMainPane(MainPanes.LOGGING); + }} + style={{ backgroundColor: app.logging.isLogging ? '#388e3c' : '', padding: '0 10px' }} + > + {app.logging.isLogging ? 'Logging ON' : 'Logging OFF'} +
{/* GRAPHING ON/OFF */} -
{app.graphing.graphingEnabled ? 'Graphing ON' : 'Graphing OFF'}
+
{ + app.setShownMainPane(MainPanes.GRAPHING); + }} + style={{ backgroundColor: app.graphing.graphingEnabled ? '#388e3c' : '', padding: '0 10px' }} + > + {app.graphing.graphingEnabled ? 'Graphing ON' : 'Graphing OFF'} +
{/* TX/RX ACTIVITY INDICATORS */} {/* Use the key prop here to make React consider this a new element everytime the number of bytes changes. This will re-trigger the flashing animation as desired. Wrap each indicator in another box, so that the keys don't collide (because they might be the same). */} @@ -315,11 +331,26 @@ const AppView = observer((props: Props) => { RX + {/* PORT CONFIG */} {/* Show port configuration in short hand, e.g. "115200 8n1" */} - {app.settings.portConfiguration.shortSerialConfigName} +
{ + app.setShownMainPane(MainPanes.SETTINGS); + app.settings.setActiveSettingsCategory(SettingsCategories.PORT_CONFIGURATION); + }} + style={{ width: '100px', padding: '0 10px' }} + > + {app.settings.portConfiguration.shortSerialConfigName} +
+ {/* PORT STATE */} - {portStateToToolbarStatusProperties[app.portState].text} +
+ {portStateToToolbarStatusProperties[app.portState].text} +
From 0cc80a5a0ec770d697dc11fb51b4a68fce0a8887 Mon Sep 17 00:00:00 2001 From: Geoffrey Hunter Date: Sat, 15 Jun 2024 15:58:26 +1200 Subject: [PATCH 12/26] Added ability to send break signal on Enter key press. --- CHANGELOG.md | 3 +- src/model/App.tsx | 2 ++ src/model/Settings/TxSettings/TxSettings.ts | 1 + .../Settings/TxSettings/TxSettingsView.tsx | 34 ++++++++++++------- 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5bfcdc6..7c4ad7b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Clicking on the many of the indicators in the bottom status bar takes you to the settings view where you can change the related settings. - Added accordions to the terminal right-hand drawer, and added more controls such as quick port settings. -- The terminal right-hand drawer now remembers if it was open or closed. This info is also saved in each profile. +- The terminal right-hand drawer now remembers if it was open or closed. This info is also saved in each profile. +- Added to option to send a break signal on Enter key press. ### Changed diff --git a/src/model/App.tsx b/src/model/App.tsx index 5170d65e..d7137014 100644 --- a/src/model/App.tsx +++ b/src/model/App.tsx @@ -801,6 +801,8 @@ export class App { } else if (this.settings.txSettings.enterKeyPressBehavior === EnterKeyPressBehavior.SEND_CRLF) { bytesToWrite.push(0x0d); bytesToWrite.push(0x0a); + } else if (this.settings.txSettings.enterKeyPressBehavior === EnterKeyPressBehavior.SEND_BREAK) { + await this.sendBreakSignal(); } else { throw Error('Unsupported enter key press behavior!'); } diff --git a/src/model/Settings/TxSettings/TxSettings.ts b/src/model/Settings/TxSettings/TxSettings.ts index 488d146e..30bb9745 100644 --- a/src/model/Settings/TxSettings/TxSettings.ts +++ b/src/model/Settings/TxSettings/TxSettings.ts @@ -5,6 +5,7 @@ export enum EnterKeyPressBehavior { SEND_LF, SEND_CR, SEND_CRLF, + SEND_BREAK, // Send the break signal (not a character) } export enum BackspaceKeyPressBehavior { diff --git a/src/view/Settings/TxSettings/TxSettingsView.tsx b/src/view/Settings/TxSettings/TxSettingsView.tsx index fce03e7b..825fcba9 100644 --- a/src/view/Settings/TxSettings/TxSettingsView.tsx +++ b/src/view/Settings/TxSettings/TxSettingsView.tsx @@ -1,8 +1,8 @@ -import { Checkbox, FormControl, FormControlLabel, FormLabel, InputAdornment, Radio, RadioGroup, Tooltip } from "@mui/material"; -import { observer } from "mobx-react-lite"; +import { Checkbox, FormControl, FormControlLabel, FormLabel, InputAdornment, Radio, RadioGroup, Tooltip } from '@mui/material'; +import { observer } from 'mobx-react-lite'; -import TxSettings, { BackspaceKeyPressBehavior, DeleteKeyPressBehavior, EnterKeyPressBehavior } from "src/model/Settings/TxSettings/TxSettings"; -import BorderedSection from "src/view/Components/BorderedSection"; +import TxSettings, { BackspaceKeyPressBehavior, DeleteKeyPressBehavior, EnterKeyPressBehavior } from 'src/model/Settings/TxSettings/TxSettings'; +import BorderedSection from 'src/view/Components/BorderedSection'; interface Props { txSettings: TxSettings; @@ -12,11 +12,15 @@ function TxSettingsView(props: Props) { const { txSettings } = props; return ( -
+
{/* =============================================================================== */} {/* ENTER PRESSED */} {/* =============================================================================== */} - + {/* BACKSPACE */} When enter is pressed: @@ -38,17 +42,21 @@ function TxSettingsView(props: Props) { } label="Send CRLF (0x0D 0x0A)" /> + {/* SEND BREAK */} + + } label="Send a break signal" /> + {/* =============================================================================== */} {/* ROW FOR DELETE AND BACKSPACE */} {/* =============================================================================== */} -
+
{/* =============================================================================== */} {/* COL1: BACKSPACE */} {/* =============================================================================== */} - + {/* BACKSPACE */} When backspace is pressed: @@ -72,7 +80,7 @@ function TxSettingsView(props: Props) { {/* =============================================================================== */} {/* COL2: DELETE */} {/* =============================================================================== */} - + When delete is pressed: -
{" "} +
{' '} {/* End of row for TX */} {/* =============================================================================== */} {/* META KEYS */} {/* =============================================================================== */} - + {/* =============================================================================== */} {/* CTRL KEYS */} {/* =============================================================================== */} @@ -120,7 +128,7 @@ function TxSettingsView(props: Props) { /> } label="Send 0x01-0x1A when Ctrl+A thru Ctrl+Z is pressed" - sx={{ marginBottom: "10px" }} + sx={{ marginBottom: '10px' }} /> {/* =============================================================================== */} @@ -142,7 +150,7 @@ function TxSettingsView(props: Props) { /> } label="Send [ESC] + when Alt- is pressed (e.g. Alt-A sends 0x1B 0x41)." - sx={{ marginBottom: "10px" }} + sx={{ marginBottom: '10px' }} /> From e17e85c362a36f23af78ea12742585eca4074640 Mon Sep 17 00:00:00 2001 From: Geoffrey Hunter Date: Sat, 15 Jun 2024 20:10:22 +1200 Subject: [PATCH 13/26] Half-way through adding better break support to macros. --- package-lock.json | 4 +- src/model/Settings/TxSettings/TxSettings.ts | 8 +- .../RightDrawer/Macros/Macro.spec.ts | 9 +- .../Terminals/RightDrawer/Macros/Macro.ts | 134 +++++++++++++++--- .../RightDrawer/Macros/MacroController.ts | 17 ++- .../Terminals/RightDrawer/MacroRowView.tsx | 4 +- .../RightDrawer/MacroSettingsModalView.tsx | 108 +++++++++++--- 7 files changed, 228 insertions(+), 56 deletions(-) diff --git a/package-lock.json b/package-lock.json index 06986afe..a5a59350 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ninjaterm", - "version": "4.16.0", + "version": "4.17.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ninjaterm", - "version": "4.16.0", + "version": "4.17.1", "dependencies": { "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", diff --git a/src/model/Settings/TxSettings/TxSettings.ts b/src/model/Settings/TxSettings/TxSettings.ts index 30bb9745..d46cadc4 100644 --- a/src/model/Settings/TxSettings/TxSettings.ts +++ b/src/model/Settings/TxSettings/TxSettings.ts @@ -2,10 +2,10 @@ import { makeAutoObservable } from 'mobx'; import { ProfileManager } from 'src/model/ProfileManager/ProfileManager'; export enum EnterKeyPressBehavior { - SEND_LF, - SEND_CR, - SEND_CRLF, - SEND_BREAK, // Send the break signal (not a character) + SEND_LF = 'Send LF', + SEND_CR = 'Send CR', + SEND_CRLF = 'Send CRLF', + SEND_BREAK = 'Send break', // Send the break signal (not a character) } export enum BackspaceKeyPressBehavior { diff --git a/src/model/Terminals/RightDrawer/Macros/Macro.spec.ts b/src/model/Terminals/RightDrawer/Macros/Macro.spec.ts index dbb65a64..c52bf1e0 100644 --- a/src/model/Terminals/RightDrawer/Macros/Macro.spec.ts +++ b/src/model/Terminals/RightDrawer/Macros/Macro.spec.ts @@ -1,14 +1,17 @@ import { expect, test, describe } from 'vitest'; -import { Macro, MacroDataType } from './Macro'; +import { Macro, MacroDataType, TxStepData } from './Macro'; describe('macro tests', () => { test('basic ascii to bytes', () => { let macro = new Macro('M1', () => '\n'); macro.setData('a'); - const bytes = macro.dataToBytes(); - expect(bytes).toStrictEqual(Uint8Array.from([97])); + const txSequence = macro.dataToBytes(); + expect(txSequence.steps.length).toBe(1); + let step = txSequence.steps[0] as TxStepData; + expect(step).toBeInstanceOf(TxStepData); + expect(step.data).toStrictEqual(Uint8Array.from([97])); }); test('new line converted to \n', () => { diff --git a/src/model/Terminals/RightDrawer/Macros/Macro.ts b/src/model/Terminals/RightDrawer/Macros/Macro.ts index 71361c89..939c1032 100644 --- a/src/model/Terminals/RightDrawer/Macros/Macro.ts +++ b/src/model/Terminals/RightDrawer/Macros/Macro.ts @@ -13,6 +13,37 @@ export class MacroConfig { data = ''; processEscapeChars = true; sendOnEnterValueForEveryNewLineInTextBox = true; + sendBreakAtEndOfEveryLineOfHex = false; +} + +function concatenate(resultConstructor: any, ...arrays: any[]) { + let totalLength = 0; + for (const arr of arrays) { + totalLength += arr.length; + } + const result = new resultConstructor(totalLength); + let offset = 0; + for (const arr of arrays) { + result.set(arr, offset); + offset += arr.length; + } + return result; +} + +export class TxStepData { + data: Uint8Array; + + constructor(data: Uint8Array) { + this.data = data; + } +} + +export class TxStepBreak { + +} + +export class TxSequence { + steps: (TxStepData | TxStepBreak)[] = []; } export class Macro { @@ -26,8 +57,13 @@ export class Macro { sendOnEnterValueForEveryNewLineInTextBox: boolean = true; + sendBreakAtEndOfEveryLineOfHex: boolean = false; + errorMsg: string = ''; + /** + * Callback function which is passed to the constructor. + */ getNewLineReplacementStr: () => string; onChange: (() => void) | null; @@ -89,8 +125,8 @@ export class Macro { * @param newLineReplacementChar Ignored if the data type is HEX. If the data type is ASCII, this string will replace all instances of LF in the data. * @returns The Uint8Array representation of the data. */ - dataToBytes = (): Uint8Array => { - let bytes; + dataToBytes = (): TxSequence => { + let txSequence = new TxSequence(); if (this.dataType === MacroDataType.ASCII) { // Replace all instances of LF with the newLinesChar // NOTE: This has to be done before JSON.parse() is called below, otherwise the LF that @@ -124,32 +160,80 @@ export class Macro { } // Convert to Uint8Array - bytes = stringToUint8Array(processedStr); + let txStepData = new TxStepData(stringToUint8Array(processedStr)); + txSequence.steps.push(txStepData); } else if (this.dataType === MacroDataType.HEX) { - // Remove all spaces and new lines - let str = this.data; - str = str.replace(/ /g, ''); - str = str.replace(/\n/g, ''); - - if (str.length === 0) { - throw new Error("Hex string is empty."); + // If "sendEnterValueForEveryNewLineInTextBox" is true, then we need to parse the hex + // per line + let linesOfHex; + if (this.sendBreakAtEndOfEveryLineOfHex) { + // Split the string into lines, since we need to send break signals + // between each line + linesOfHex = this.data.split('\n'); + } else { + // Treat the entire string as one hex string since no + // break signals are needed + linesOfHex = [this.data]; } - if (str.length % 2 !== 0) { - throw new Error("Hex string must have an even number of characters."); - } + for (let i = 0; i < linesOfHex.length; i++) { + // Remove all spaces and new lines. If break signals at the end of every line + // is disabled, there might be new lines here, and we just ignore them along + // with all spaces. + let str = linesOfHex[i]; + str = str.replace(/ /g, ''); + str = str.replace(/\n/g, ''); - if (/[^a-fA-F0-9]/u.test(str)) { - throw new Error("Hex string must only contain: the numbers 0-9 and A-F (or a-f)."); - } + if (str.length === 0) { + throw new Error("Hex string is empty."); + } - // Convert hex string to Uint8Array - bytes = this._hexStringToUint8Array(str); + if (str.length % 2 !== 0) { + throw new Error("Hex string must have an even number of characters."); + } + + if (/[^a-fA-F0-9]/u.test(str)) { + throw new Error("Hex string must only contain: the numbers 0-9 and A-F (or a-f)."); + } + + // Convert hex string to Uint8Array + let txStepData = new TxStepData(this._hexStringToUint8Array(str)); + txSequence.steps.push(txStepData); + + if (this.sendBreakAtEndOfEveryLineOfHex) { + // Add a break signal + txSequence.steps.push(new TxStepBreak()); + } + } } else { throw new Error("Invalid data type"); } - return bytes; + + // // Remove all spaces and new lines + // let str = this.data; + // str = str.replace(/ /g, ''); + // str = str.replace(/\n/g, ''); + + // if (str.length === 0) { + // throw new Error("Hex string is empty."); + // } + + // if (str.length % 2 !== 0) { + // throw new Error("Hex string must have an even number of characters."); + // } + + // if (/[^a-fA-F0-9]/u.test(str)) { + // throw new Error("Hex string must only contain: the numbers 0-9 and A-F (or a-f)."); + // } + + // // Convert hex string to Uint8Array + // bytes = this._hexStringToUint8Array(str); + // } else { + // throw new Error("Invalid data type"); + // } + + return txSequence; } /** @@ -182,6 +266,7 @@ export class Macro { this.dataType = config.dataType; this.processEscapeChars = config.processEscapeChars; this.sendOnEnterValueForEveryNewLineInTextBox = config.sendOnEnterValueForEveryNewLineInTextBox; + this.sendBreakAtEndOfEveryLineOfHex = config.sendBreakAtEndOfEveryLineOfHex } toConfig = (): MacroConfig => { @@ -192,6 +277,7 @@ export class Macro { dataType: this.dataType, processEscapeChars: this.processEscapeChars, sendOnEnterValueForEveryNewLineInTextBox: this.sendOnEnterValueForEveryNewLineInTextBox, + sendBreakAtEndOfEveryLineOfHex: this.sendBreakAtEndOfEveryLineOfHex, }; } @@ -215,4 +301,14 @@ export class Macro { this.onChange(); } } + + setSendBreakAtEndOfEveryLineOfHex(allow: boolean) { + this.sendBreakAtEndOfEveryLineOfHex = allow; + // This could change the validation status, need to + // re-validate + this.validateData(); + if (this.onChange) { + this.onChange(); + } + } } diff --git a/src/model/Terminals/RightDrawer/Macros/MacroController.ts b/src/model/Terminals/RightDrawer/Macros/MacroController.ts index 0dfa28a4..b624f223 100644 --- a/src/model/Terminals/RightDrawer/Macros/MacroController.ts +++ b/src/model/Terminals/RightDrawer/Macros/MacroController.ts @@ -1,7 +1,7 @@ import { makeAutoObservable } from "mobx"; import { App } from "src/model/App"; -import { Macro, MacroConfig, MacroDataType } from "./Macro"; +import { Macro, MacroConfig, MacroDataType, TxStepBreak, TxStepData } from "./Macro"; import { EnterKeyPressBehavior } from "src/model/Settings/TxSettings/TxSettings"; const NUM_MACROS = 8; @@ -96,13 +96,20 @@ export class MacroController { * Send the provided macro data to the serial port. * @param macro The macro to send. */ - send(macro: Macro) { + send = async (macro: Macro) => { // Send the data to the serial port // If the user presses enter in the multiline text field, it will add a newline character // (0x0A or 10) to the string. - let outputData; - outputData = macro.dataToBytes(); - this.app.writeBytesToSerialPort(outputData); + const outputData = macro.dataToBytes(); + for (let i = 0; i < outputData.steps.length; i++) { + // Determine type of item in array. If data, write to port. If break, send a break. + const currStep = outputData.steps[i]; + if (currStep instanceof TxStepData) { + this.app.writeBytesToSerialPort(currStep.data); + } else if (currStep instanceof TxStepBreak) { + await this.app.sendBreakSignal(); + } + } } _saveConfig = () => { diff --git a/src/view/Terminals/RightDrawer/MacroRowView.tsx b/src/view/Terminals/RightDrawer/MacroRowView.tsx index b6ff4c34..1a67e3d3 100644 --- a/src/view/Terminals/RightDrawer/MacroRowView.tsx +++ b/src/view/Terminals/RightDrawer/MacroRowView.tsx @@ -72,8 +72,8 @@ export default observer((props: Props) => { size="small" style={{ padding: "1px" }} disabled={app.portState !== PortState.OPENED || !macro.canSend} - onClick={() => { - macroController.send(macro); + onClick={async () => { + await macroController.send(macro); }} data-testid={`macro-${macroIdx}-send-button`} > diff --git a/src/view/Terminals/RightDrawer/MacroSettingsModalView.tsx b/src/view/Terminals/RightDrawer/MacroSettingsModalView.tsx index b3d4391d..c2412355 100644 --- a/src/view/Terminals/RightDrawer/MacroSettingsModalView.tsx +++ b/src/view/Terminals/RightDrawer/MacroSettingsModalView.tsx @@ -1,13 +1,14 @@ -import { Button, Checkbox, FormControl, FormControlLabel, FormLabel, Modal, Radio, RadioGroup, TextField, Tooltip } from "@mui/material"; -import { observer } from "mobx-react-lite"; -import "react-resizable/css/styles.css"; -import ArrowForwardIcon from "@mui/icons-material/ArrowForward"; -import CloseIcon from "@mui/icons-material/Close"; +import { Button, Checkbox, FormControl, FormControlLabel, FormLabel, InputLabel, MenuItem, Modal, Radio, RadioGroup, Select, TextField, Tooltip } from '@mui/material'; +import { observer } from 'mobx-react-lite'; +import 'react-resizable/css/styles.css'; +import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; +import CloseIcon from '@mui/icons-material/Close'; -import { MacroController } from "src/model/Terminals/RightDrawer/Macros/MacroController"; -import { App } from "src/model/App"; -import { PortState } from "src/model/Settings/PortConfigurationSettings/PortConfigurationSettings"; -import { MacroDataType } from "src/model/Terminals/RightDrawer/Macros/Macro"; +import { MacroController } from 'src/model/Terminals/RightDrawer/Macros/MacroController'; +import { App } from 'src/model/App'; +import { PortState } from 'src/model/Settings/PortConfigurationSettings/PortConfigurationSettings'; +import { MacroDataType } from 'src/model/Terminals/RightDrawer/Macros/Macro'; +import { EnterKeyPressBehavior } from 'src/model/Settings/TxSettings/TxSettings'; interface Props { app: App; @@ -30,11 +31,11 @@ export default observer((props: Props) => { }} aria-labelledby="modal-modal-title" aria-describedby="modal-modal-description" - style={{ display: "flex", alignItems: "center", justifyContent: "center" }} + style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }} > -
+
Macro Settings -
+
{/* ================================================================= */} {/* TREAT DATA AS */} {/* ================================================================= */} @@ -47,11 +48,21 @@ export default observer((props: Props) => { }} > {/* ASCII */} - + } label="ASCII" /> {/* HEX */} - + } label="HEX" /> @@ -64,6 +75,7 @@ export default observer((props: Props) => { { onChange={(e) => { macro.setProcessEscapeChars(e.target.checked); }} - data-testid='macro-process-escape-chars-cb' + data-testid="macro-process-escape-chars-cb" /> } - label='Process escape chars (\r, \n, \t, e.t.c.)' + label="Process escape chars (\r, \n, \t, e.t.c.)" /> {/* ================================================================= */} @@ -85,9 +97,9 @@ export default observer((props: Props) => { { label='Send "On Enter" value for every new line in text box' /> + {/* ================================================================= */} + {/* ENTER KEY PRESS BEHAVIOR */} + {/* ================================================================= */} + + What to send at the end of every line in the macro data text box, if the above checkbox is ticked.
+
+ This is the same setting as in the TX Settings view. +
+ } + arrow + placement="right" + enterDelay={500} + > + + On Enter Value + + + + {/* ================================================================= */} + {/* SEND BREAK AT END OF EVERY LINE OF HEX */} + {/* ================================================================= */} + + { + macro.setSendBreakAtEndOfEveryLineOfHex(e.target.checked); + }} + /> + } + label='Send the break signal at the end of every line of hex.' + disabled={macro.dataType !== MacroDataType.HEX} + /> +
{/* ================================================================= */} @@ -130,21 +196,21 @@ export default observer((props: Props) => { minRows={10} value={macro.data} helperText={macro.errorMsg} - error={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 }} /> -
+
+ } arrow enterDelay={500} > @@ -109,6 +114,7 @@ export default observer((props: Props) => { /> } label='Send "On Enter" value for every new line in text box' + disabled={macro.dataType !== MacroDataType.ASCII} /> {/* ================================================================= */} @@ -126,7 +132,11 @@ export default observer((props: Props) => { placement="right" enterDelay={500} > - + On Enter Value - + Endianness { - macro.setSendOnEnterValueForEveryNewLineInTextBox(e.target.checked); + app.settings.txSettings.setEnterKeyPressBehavior(e.target.value as EnterKeyPressBehavior); }} - /> - } - label='Send "On Enter" value for every new line in text box' - disabled={macro.dataType !== MacroDataType.ASCII} - /> - - {/* ================================================================= */} - {/* ENTER KEY PRESS BEHAVIOR */} - {/* ================================================================= */} - - What to send at the end of every line in the macro data text box, if the above checkbox is ticked.
-
- This is the same setting as in the TX Settings view. -
- } - arrow - placement="right" - enterDelay={500} + > + {Object.values(EnterKeyPressBehavior).map((item) => { + return ( + + {item} + + ); + })} + + + + + - - On Enter Value - - - - {/* ================================================================= */} - {/* SEND BREAK AT END OF EVERY LINE OF HEX */} - {/* ================================================================= */} - - { - macro.setSendBreakAtEndOfEveryLineOfHex(e.target.checked); - }} - /> - } - label='Send the break signal at the end of every line of hex.' - disabled={macro.dataType !== MacroDataType.HEX} - /> - + { + macro.setSendBreakAtEndOfEveryLineOfHex(e.target.checked); + }} + /> + } + label="Send the break signal at the end of every line of hex." + disabled={macro.dataType !== MacroDataType.HEX} + style={{ width: '250px' }} + /> + +
{/* ================================================================= */} @@ -183,12 +216,10 @@ export default observer((props: Props) => { - 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. + If ASCII, all printable characters are allowed. If the "Send On Enter sequence at the end of every line" checkbox is ticked, there will be additional bytes sent at the end of every line.

- 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). + If HEX, only the characters 0-9 and A-F, spaces and new lines are allowed. There must be an even number of characters as to make up a complete number of bytes (e.g. 08 A2 FF). If the "Send break at end of every line of hex" checkbox is ticked, each line has to contain an even number of characters rather than just the whole textbox.
} enterDelay={500} @@ -203,7 +234,7 @@ export default observer((props: Props) => { }, }} multiline={true} - minRows={10} + minRows={5} value={macro.data} helperText={macro.errorMsg} error={macro.errorMsg !== ''} From 852d01051d012d836309f0d87019819f3f796dad Mon Sep 17 00:00:00 2001 From: Geoffrey Hunter Date: Sun, 16 Jun 2024 10:55:44 +1200 Subject: [PATCH 21/26] Snackbar message when profile is loaded now tells you if already connected port matches the one specified in the profile. --- CHANGELOG.md | 1 + src/model/ProfileManager/ProfileManager.ts | 24 ++++++++++------------ 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a008f71c..2c74c3fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Changed - Improved the way app data is stored in local storage. +- Snackbar message when profile is loaded now tells you if already connected port matches the one specified in the profile. ## [4.17.1] - 2024-06-11 diff --git a/src/model/ProfileManager/ProfileManager.ts b/src/model/ProfileManager/ProfileManager.ts index 1b5146f7..52cbb5fb 100644 --- a/src/model/ProfileManager/ProfileManager.ts +++ b/src/model/ProfileManager/ProfileManager.ts @@ -38,7 +38,7 @@ export class RootConfig { } /** - * This class represents a serial port profile. It is used to store use-specific + * This class represents all the data stored in a user profile. It is used to store use-specific * settings for the application (e.g. all the settings to talk to a particular * embedded device). The class is serializable to JSON. */ @@ -117,6 +117,7 @@ export class ProfileManager { return; } const appDataInStorage = JSON.parse(appDataAsJson); + // Compare the JSON strings of the profiles to work out if they are different if (JSON.stringify(appDataInStorage.profiles) !== JSON.stringify(this.appData.profiles)) { console.log('Profiles changed. Reloading profiles...'); // Reload just the profiles, we don't want to overwrite the current app config @@ -125,14 +126,6 @@ export class ProfileManager { } } - // setActiveProfile = (profile: Profile) => { - // // this.activeProfile = profile; - // // Need to tell the rest of the app to update - // this._profileChangeCallbacks.forEach((callback) => { - // callback(); - // }); - // }; - registerOnProfileLoad = (callback: () => void) => { this._profileChangeCallbacks.push(callback); }; @@ -212,6 +205,9 @@ export class ProfileManager { /** * Apply the profile at the provided index to the current app config (i.e. update the app * to reflect the profile). + * + * Will attempt to connect to the serial port specified in the profile if it is available. + * * @param profileIdx The index of the profile to apply to the app. */ applyProfileToApp = async (profileIdx: number) => { @@ -230,7 +226,11 @@ export class ProfileManager { weNeedToConnect = false; } else if (profileSerialPortInfoJson === currentSerialPortInfoJson) { // Same serial port, no need to disconnect and connect + // Note there is a chance we are not connected to the right one due to + // ambiguity...but if already connected it is a better user experience to + // not disconnect on the high chance it is the correct port weNeedToConnect = false; + snackbarMessage += '\nAlready connected port matches one specified in profile. Leaving port connected.'; } else { // They are both different and the profile one is non-empty. Check to see if the profile ports is available console.log('Port infos are both different and non-empty. Checking if ports are available...'); @@ -246,10 +246,10 @@ export class ProfileManager { // The profile port is available weNeedToConnect = true; } else { - // There are multiple ports that match the profile port, to ambiguous, do + // There are multiple ports that match the profile port, too ambiguous, do // not connect to any weNeedToConnect = false; - snackbarMessage += '\nMultiple available ports info match the profile port info. Not connecting to any.'; + snackbarMessage += '\nMultiple available ports info match the profile port info (ambiguous). Not connecting to any.'; snackbarVariant = 'warning'; } } @@ -257,13 +257,11 @@ export class ProfileManager { // Only disconnect if we have found a valid port to connect to if (weNeedToConnect) { if (this.app.portState === PortState.OPENED) { - console.log('Closing port...'); await this.app.closePort({ silenceSnackbar: true }); } else if (this.app.portState === PortState.CLOSED_BUT_WILL_REOPEN) { this.app.stopWaitingToReopenPort(); } } - console.log('Port closed.'); // Update the current app config from the provided profile, // and then save this new app config this.appData.currentAppConfig = JSON.parse(JSON.stringify(profile.rootConfig)); From 2ac4f312e2c46d8ef51ae2f669cb57d2deede980 Mon Sep 17 00:00:00 2001 From: Geoffrey Hunter Date: Sun, 16 Jun 2024 11:14:47 +1200 Subject: [PATCH 22/26] The right-hand terminal drawer width is now saved as part of the application data and in profiles. --- CHANGELOG.md | 1 + .../Terminals/RightDrawer/RightDrawer.ts | 14 +++++++++++--- .../Terminals/RightDrawer/RightDrawerView.tsx | 19 +++++++++++++------ 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c74c3fa..a6facaa5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Added ability to send a break signal at the end of every line of hex data in a macro. - ASCII macro data now adds the "on enter" sequence to the end of every line, if enabled. - You can now disable "received break signal" warnings from the RX Settings view. +- The right-hand terminal drawer width is now saved as part of the application data and in profiles. ### Changed diff --git a/src/model/Terminals/RightDrawer/RightDrawer.ts b/src/model/Terminals/RightDrawer/RightDrawer.ts index d3494e0b..a358a4ea 100644 --- a/src/model/Terminals/RightDrawer/RightDrawer.ts +++ b/src/model/Terminals/RightDrawer/RightDrawer.ts @@ -13,6 +13,7 @@ export class RightDrawerConfig { showRightDrawer = true; + rightDrawerWidth_px = 400; /** * Expand the quick port settings and macro accordions by default. @@ -27,6 +28,8 @@ export default class RightDrawer { profileManager: ProfileManager; macroController: MacroController; + drawerWidth_px = 400; + quickPortSettingsIsExpanded = false; otherQuickSettingsIsExpanded = false; macrosIsExpanded = false; @@ -61,18 +64,17 @@ export default class RightDrawer { _saveConfig = () => { let config = this.profileManager.appData.currentAppConfig.terminal.rightDrawer; + config.rightDrawerWidth_px = this.drawerWidth_px; + config.quickPortSettingsIsExpanded = this.quickPortSettingsIsExpanded; config.otherQuickSettingsIsExpanded = this.otherQuickSettingsIsExpanded; config.macrosIsExpanded = this.macrosIsExpanded; - console.log('Saving config', config); - this.profileManager.saveAppData(); }; _loadConfig = () => { let configToLoad = this.profileManager.appData.currentAppConfig.terminal.rightDrawer; - console.log('configToLoad', configToLoad); //=============================================== // UPGRADE PATH //=============================================== @@ -85,8 +87,14 @@ export default class RightDrawer { configToLoad = this.profileManager.appData.currentAppConfig.terminal.rightDrawer; } + this.drawerWidth_px = configToLoad.rightDrawerWidth_px; this.quickPortSettingsIsExpanded = configToLoad.quickPortSettingsIsExpanded; this.otherQuickSettingsIsExpanded = configToLoad.otherQuickSettingsIsExpanded; this.macrosIsExpanded = configToLoad.macrosIsExpanded }; + + setDrawerWidth(width: number) { + this.drawerWidth_px = width; + this._saveConfig(); + } } diff --git a/src/view/Terminals/RightDrawer/RightDrawerView.tsx b/src/view/Terminals/RightDrawer/RightDrawerView.tsx index f7e08e84..9cc991fd 100644 --- a/src/view/Terminals/RightDrawer/RightDrawerView.tsx +++ b/src/view/Terminals/RightDrawer/RightDrawerView.tsx @@ -1,5 +1,5 @@ import { observer } from 'mobx-react-lite'; -import { ResizableBox } from 'react-resizable'; +import { ResizableBox, Resizable } from 'react-resizable'; import 'react-resizable/css/styles.css'; import { Accordion, @@ -19,6 +19,7 @@ import { Tooltip, styled, } from '@mui/material'; +import MuiAccordionSummary, { AccordionSummaryProps } from '@mui/material/AccordionSummary'; import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward'; import { OverridableStringUnion } from '@mui/types'; @@ -38,7 +39,6 @@ import { } from 'src/model/Settings/PortConfigurationSettings/PortConfigurationSettings'; import { portStateToButtonProps } from 'src/view/Components/PortStateToButtonProps'; -import MuiAccordionSummary, { AccordionSummaryProps } from '@mui/material/AccordionSummary'; import { SettingsCategories } from 'src/model/Settings/Settings'; // This code was modified from https://mui.com/material-ui/react-accordion/#customization @@ -70,12 +70,16 @@ export default observer((props: Props) => { }); return ( - { + console.log('onResize() called. size: ', size); + rightDrawer.setDrawerWidth(size.width); + }} resizeHandles={['w']} axis="x" - style={{ padding: '0px 0px 0px 10px', margin: '0px 0px 0px 0px', fontSize: '12px', display: 'flex', flexDirection: 'column', overflowY: 'auto' }} + // style={{ padding: '0px 0px 0px 10px', margin: '0px 0px 0px 0px', fontSize: '12px', display: 'flex', flexDirection: 'column', overflowY: 'auto' }} handle={
{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start', + overflowY: 'auto', + overflowX: 'hidden', + width: rightDrawer.drawerWidth_px + 'px', }} >
{/* Spacer to prevent select input title from being clipped */} @@ -458,6 +465,6 @@ export default observer((props: Props) => {
- + ); }); From 0c40229f40262e65904ae7ab284a6439de085bf3 Mon Sep 17 00:00:00 2001 From: Geoffrey Hunter Date: Sun, 16 Jun 2024 11:20:57 +1200 Subject: [PATCH 23/26] Active profile name is updated when the app state is saved to a profile. --- CHANGELOG.md | 1 + src/model/ProfileManager/ProfileManager.ts | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6facaa5..4237f305 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Improved the way app data is stored in local storage. - Snackbar message when profile is loaded now tells you if already connected port matches the one specified in the profile. +- Active profile name is updated when the app state is saved to a profile. ## [4.17.1] - 2024-06-11 diff --git a/src/model/ProfileManager/ProfileManager.ts b/src/model/ProfileManager/ProfileManager.ts index 52cbb5fb..d6832054 100644 --- a/src/model/ProfileManager/ProfileManager.ts +++ b/src/model/ProfileManager/ProfileManager.ts @@ -276,9 +276,7 @@ export class ProfileManager { // Now connect to the port if we need to if (weNeedToConnect) { - console.log('Setting selected port...', matchedAvailablePorts[0]); this.app.setSelectedPort(matchedAvailablePorts[0]); - console.log('Opening port...'); await this.app.openPort({ silenceSnackbar: true }); snackbarMessage += '\nConnected to port with info: "' + profileSerialPortInfoJson + '".'; } @@ -297,6 +295,10 @@ export class ProfileManager { profile.rootConfig = JSON.parse(JSON.stringify(this.appData.currentAppConfig)); this.saveAppData(); + // Although we are not loading a profile, saving the app state to a profile + // is essentially the same thing, so update the name (this is used in the app title) + this.lastAppliedProfileName = profile.name; + // Post message to snackbar if (!noSnackbar) { this.app.snackbar.sendToSnackbar('Profile "' + profile.name + '" saved.', 'success'); From 7c19ab1cf76ba1c3db90ae7cf82f16670a0467b1 Mon Sep 17 00:00:00 2001 From: Geoffrey Hunter Date: Sun, 16 Jun 2024 11:26:09 +1200 Subject: [PATCH 24/26] Fix broken macro tests. --- src/model/Terminals/RightDrawer/Macros/Macro.spec.ts | 5 +++++ tests/Macros.spec.ts | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/model/Terminals/RightDrawer/Macros/Macro.spec.ts b/src/model/Terminals/RightDrawer/Macros/Macro.spec.ts index 5d9957a0..96a6ea38 100644 --- a/src/model/Terminals/RightDrawer/Macros/Macro.spec.ts +++ b/src/model/Terminals/RightDrawer/Macros/Macro.spec.ts @@ -7,6 +7,8 @@ describe('macro tests', () => { test('basic ascii to bytes', () => { let macro = new Macro('M1', () => '\n'); macro.setData('a'); + macro.setSendOnEnterValueForEveryNewLineInTextBox(true); + const txSequence = macro.dataToTxSequence(); expect(txSequence.steps.length).toBe(1); let step = txSequence.steps[0] as TxStepData; @@ -17,6 +19,7 @@ describe('macro tests', () => { test('new line converted to \n', () => { let macro = new Macro('M1', () => '\n'); macro.setData('a\nb'); + macro.setSendOnEnterValueForEveryNewLineInTextBox(true); const txSequence = macro.dataToTxSequence(); expect(txSequence.steps.length).toBe(1); @@ -28,6 +31,7 @@ describe('macro tests', () => { test('new line converted to \r\n', () => { let macro = new Macro('M1', () => '\r\n'); macro.setData('a\nb'); + macro.setSendOnEnterValueForEveryNewLineInTextBox(true); const txSequence = macro.dataToTxSequence(); expect(txSequence.steps.length).toBe(1); @@ -39,6 +43,7 @@ describe('macro tests', () => { test('new line converted to \r', () => { let macro = new Macro('M1', () => '\r'); macro.setData('a\nb'); + macro.setSendOnEnterValueForEveryNewLineInTextBox(true); const txSequence = macro.dataToTxSequence(); expect(txSequence.steps.length).toBe(1); diff --git a/tests/Macros.spec.ts b/tests/Macros.spec.ts index aec188c2..309d0b21 100644 --- a/tests/Macros.spec.ts +++ b/tests/Macros.spec.ts @@ -59,7 +59,7 @@ test.describe('macros', () => { await page.getByTestId('macro-0-send-button').click(); const utf8EncodeText = new TextEncoder(); - const expectedText = utf8EncodeText.encode('abc123\n'); + const expectedText = utf8EncodeText.encode('abc123'); expect(appTestHarness.writtenData).toEqual(Array.from(expectedText)); }); @@ -78,7 +78,7 @@ test.describe('macros', () => { const utf8EncodeText = new TextEncoder(); // The \n should not be processed into LF, should still be separate \ and n chars - const expectedText = utf8EncodeText.encode('abc123\\n\n'); + const expectedText = utf8EncodeText.encode('abc123\\n'); expect(appTestHarness.writtenData).toEqual(Array.from(expectedText)); }); From 8f8e4effa324e9f6f28c05604ada1997006a1600 Mon Sep 17 00:00:00 2001 From: Geoffrey Hunter Date: Sun, 16 Jun 2024 11:31:53 +1200 Subject: [PATCH 25/26] Tidy up code and change font sizes. --- src/view/Terminals/RightDrawer/RightDrawerView.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/view/Terminals/RightDrawer/RightDrawerView.tsx b/src/view/Terminals/RightDrawer/RightDrawerView.tsx index 9cc991fd..ab29f1ad 100644 --- a/src/view/Terminals/RightDrawer/RightDrawerView.tsx +++ b/src/view/Terminals/RightDrawer/RightDrawerView.tsx @@ -1,5 +1,5 @@ import { observer } from 'mobx-react-lite'; -import { ResizableBox, Resizable } from 'react-resizable'; +import { Resizable } from 'react-resizable'; import 'react-resizable/css/styles.css'; import { Accordion, @@ -72,9 +72,8 @@ export default observer((props: Props) => { return ( { - console.log('onResize() called. size: ', size); rightDrawer.setDrawerWidth(size.width); }} resizeHandles={['w']} @@ -124,7 +123,7 @@ export default observer((props: Props) => { Quick Port Settings -
+
For more port settings, go to the{' '} { > }>Other Quick Settings -
+
For more options, go to the{' '} Date: Sun, 16 Jun 2024 11:33:55 +1200 Subject: [PATCH 26/26] Prep. for release of v4.18.0. --- CHANGELOG.md | 5 ++++- package.json | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4237f305..0ca605a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## Unreleased +## [4.18.0] - 2024-06-16 + ### Added - Clicking on the many of the indicators in the bottom status bar takes you to the settings view where you can change the related settings. @@ -737,7 +739,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.17.1...HEAD +[unreleased]: https://github.com/gbmhunter/NinjaTerm/compare/v4.18.0...HEAD +[4.18.0]: https://github.com/gbmhunter/NinjaTerm/compare/v4.17.1...v4.18.0 [4.17.1]: https://github.com/gbmhunter/NinjaTerm/compare/v4.17.0...v4.17.1 [4.17.0]: https://github.com/gbmhunter/NinjaTerm/compare/v4.16.0...v4.17.0 [4.16.0]: https://github.com/gbmhunter/NinjaTerm/compare/v4.15.0...v4.16.0 diff --git a/package.json b/package.json index 5c47c50e..0fb733a8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ninjaterm", - "version": "4.17.1", + "version": "4.18.0", "dependencies": { "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5",