From a24c263789f185ad07a03c8f416ba33458e87249 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Fri, 29 Jan 2021 15:39:57 -0600 Subject: [PATCH] move terminal dataSource to context state should only hold serializeable data --- src/index.tsx | 2 ++ src/reducers.ts | 3 --- src/terminal/Terminal.tsx | 10 ++++++---- src/terminal/TerminalContext.ts | 18 ++++++++++++++++++ src/terminal/actions.ts | 17 +---------------- src/terminal/reducers.ts | 24 ------------------------ src/terminal/sagas.test.ts | 18 ++++++++---------- src/terminal/sagas.ts | 21 +++++---------------- 8 files changed, 40 insertions(+), 73 deletions(-) create mode 100644 src/terminal/TerminalContext.ts delete mode 100644 src/terminal/reducers.ts diff --git a/src/index.tsx b/src/index.tsx index 8643e3f3..7288e19e 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -18,6 +18,7 @@ import reportWebVitals from './reportWebVitals'; import rootSaga from './sagas'; import { didSucceed, didUpdate } from './service-worker/actions'; import * as serviceWorkerRegistration from './serviceWorkerRegistration'; +import { defaultTerminalContext } from './terminal/TerminalContext'; import { createCountFunc } from './utils/iter'; const toaster = I18nToaster.create(i18nManager); @@ -26,6 +27,7 @@ const sagaMiddleware = createSagaMiddleware({ context: { nextMessageId: createCountFunc(), notification: { toaster }, + terminal: defaultTerminalContext, }, }); diff --git a/src/reducers.ts b/src/reducers.ts index 277a34b9..88da0fa5 100644 --- a/src/reducers.ts +++ b/src/reducers.ts @@ -10,7 +10,6 @@ import hub, { HubState } from './hub/reducers'; import licenses, { LicenseState } from './licenses/reducers'; import bootloader, { BootloaderState } from './lwp3-bootloader/reducers'; import settings, { SettingsState } from './settings/reducers'; -import terminal, { TerminalState } from './terminal/reducers'; /** * Root state for redux store. @@ -24,7 +23,6 @@ export interface RootState { readonly hub: HubState; readonly license: LicenseState; readonly settings: SettingsState; - readonly terminal: TerminalState; } export default combineReducers({ @@ -36,5 +34,4 @@ export default combineReducers({ hub, licenses, settings, - terminal, }); diff --git a/src/terminal/Terminal.tsx b/src/terminal/Terminal.tsx index e66387c1..1cd621eb 100644 --- a/src/terminal/Terminal.tsx +++ b/src/terminal/Terminal.tsx @@ -11,11 +11,12 @@ import { import { WithI18nProps, withI18n } from '@shopify/react-i18n'; import React from 'react'; import { connect } from 'react-redux'; -import { Observable, Unsubscribe } from 'redux'; +import { Unsubscribe } from 'redux'; import { Terminal as XTerm } from 'xterm'; import { FitAddon } from 'xterm-addon-fit'; import { RootState } from '../reducers'; import { isMacOS } from '../utils/os'; +import { TerminalContext } from './TerminalContext'; import { receiveData } from './actions'; import { TerminalStringId } from './i18n'; import en from './i18n.en.json'; @@ -23,7 +24,6 @@ import en from './i18n.en.json'; import 'xterm/css/xterm.css'; interface StateProps { - dataSource: Observable | null; darkMode: boolean; } @@ -54,6 +54,9 @@ class Terminal extends React.Component { this.terminalRef = React.createRef(); } + static contextType = TerminalContext; + context!: React.ContextType; + private handleKeyEvent = (e: KeyboardEvent): boolean => { if (e.key === 'v' && e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey) { // this allows CTRL+V to be handled by the browser instead of sending @@ -93,7 +96,7 @@ class Terminal extends React.Component { } this.xterm.open(this.terminalRef.current); this.fitAddon.fit(); - this.subscription = this.props.dataSource?.subscribe({ + this.subscription = this.context.dataSource.observable.subscribe({ next: (d) => this.xterm.write(d), }); window.addEventListener('keydown', this.handleKeyDownEvent); @@ -170,7 +173,6 @@ class Terminal extends React.Component { } const mapStateToProps = (state: RootState): StateProps => ({ - dataSource: state.terminal.dataSource, darkMode: state.settings.darkMode, }); diff --git a/src/terminal/TerminalContext.ts b/src/terminal/TerminalContext.ts new file mode 100644 index 00000000..81ced522 --- /dev/null +++ b/src/terminal/TerminalContext.ts @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2021 The Pybricks Authors +// +// Shared terminal context. + +import { createContext } from 'react'; +import PushStream from 'zen-push'; + +/** The default terminal context. */ +export const defaultTerminalContext = { + dataSource: new PushStream(), +}; + +/** Terminal context data type. */ +export type TerminalContextValue = typeof defaultTerminalContext; + +/** Terminal React context. */ +export const TerminalContext = createContext(defaultTerminalContext); diff --git a/src/terminal/actions.ts b/src/terminal/actions.ts index 5ad6c7e1..c3a779b5 100644 --- a/src/terminal/actions.ts +++ b/src/terminal/actions.ts @@ -1,13 +1,9 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2020 The Pybricks Authors -import { Action, Observable } from 'redux'; +import { Action } from 'redux'; export enum TerminalActionType { - /** - * Set the current data source. - */ - SetDataSource = 'terminal.action.setDataSource', /** * Send data. */ @@ -18,16 +14,6 @@ export enum TerminalActionType { ReceivedData = 'terminal.action.receiveData', } -export type TerminalSetDataSourceAction = Action & { - dataSource: Observable; -}; - -export function setDataSource( - dataSource: Observable, -): TerminalSetDataSourceAction { - return { type: TerminalActionType.SetDataSource, dataSource }; -} - export type TerminalDataSendDataAction = Action & { value: string; }; @@ -45,6 +31,5 @@ export function receiveData(data: string): TerminalDataReceiveDataAction { } export type TerminalDataAction = - | TerminalSetDataSourceAction | TerminalDataSendDataAction | TerminalDataReceiveDataAction; diff --git a/src/terminal/reducers.ts b/src/terminal/reducers.ts deleted file mode 100644 index 4c3b6cbf..00000000 --- a/src/terminal/reducers.ts +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2020 The Pybricks Authors - -import { Reducer } from 'react'; -import { Observable, combineReducers } from 'redux'; -import { Action } from '../actions'; -import { TerminalActionType } from './actions'; - -type DataSource = Observable | null; - -const dataSource: Reducer = (state = null, action) => { - switch (action.type) { - case TerminalActionType.SetDataSource: - return action.dataSource; - default: - return state; - } -}; - -export interface TerminalState { - readonly dataSource: DataSource; -} - -export default combineReducers({ dataSource }); diff --git a/src/terminal/sagas.test.ts b/src/terminal/sagas.test.ts index b2dfb418..06ed88ec 100644 --- a/src/terminal/sagas.test.ts +++ b/src/terminal/sagas.test.ts @@ -1,9 +1,8 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2020-2021 The Pybricks Authors +import PushStream from 'zen-push'; import { AsyncSaga, delay } from '../../test'; - -import { didStart } from '../app/actions'; import { BleUartActionType, BleUartWriteAction, @@ -22,7 +21,6 @@ import { createCountFunc } from '../utils/iter'; import { TerminalActionType, TerminalDataSendDataAction, - TerminalSetDataSourceAction, receiveData, sendData, } from './actions'; @@ -318,15 +316,15 @@ describe('Data receiver filters out hub status', () => { }); test('Terminal data source responds to send data actions', async () => { - const saga = new AsyncSaga(terminal, {}, { nextMessageId: createCountFunc() }); + const dataSource = new PushStream(); + const saga = new AsyncSaga( + terminal, + {}, + { nextMessageId: createCountFunc(), terminal: { dataSource } }, + ); - saga.put(didStart()); - const dataSourceAction = await saga.take(); - expect(dataSourceAction.type).toBe(TerminalActionType.SetDataSource); - - const dataSource = (dataSourceAction as TerminalSetDataSourceAction).dataSource; const data = new Array(); - dataSource.subscribe({ next: (v) => data.push(v) }); + dataSource.observable.subscribe({ next: (v) => data.push(v) }); saga.put(sendData('1')); saga.put(sendData('2')); diff --git a/src/terminal/sagas.ts b/src/terminal/sagas.ts index 6fbbe70c..af1a9663 100644 --- a/src/terminal/sagas.ts +++ b/src/terminal/sagas.ts @@ -12,29 +12,18 @@ import { take, takeEvery, } from 'typed-redux-saga/macro'; -import PushStream from 'zen-push'; import { Action } from '../actions'; -import { AppActionType, AppDidStartAction } from '../app/actions'; import { BleUartActionType, BleUartNotifyAction, write } from '../ble-uart/actions'; import { SafeTxCharLength } from '../ble-uart/protocol'; import { HubRuntimeStatusType, checksum, updateStatus } from '../hub/actions'; import { HubRuntimeState } from '../hub/reducers'; import { RootState } from '../reducers'; import { defined } from '../utils'; -import { - TerminalActionType, - TerminalDataReceiveDataAction, - sendData, - setDataSource, -} from './actions'; +import { TerminalContextValue } from './TerminalContext'; +import { TerminalActionType, TerminalDataReceiveDataAction, sendData } from './actions'; const encoder = new TextEncoder(); const decoder = new TextDecoder(); -const terminalDataSource = new PushStream(); - -function* startup(_action: AppDidStartAction): Generator { - yield* put(setDataSource(terminalDataSource.observable)); -} function* handleMatch( match: RegExpMatchArray | null, @@ -139,13 +128,13 @@ function* receiveTerminalData(): Generator { } } -function sendTerminalData(action: TerminalDataReceiveDataAction): void { +function* sendTerminalData(action: TerminalDataReceiveDataAction): Generator { + const { dataSource } = yield* getContext('terminal'); // This is used to provide a data source for the Terminal component - terminalDataSource.next(action.value); + dataSource.next(action.value); } export default function* (): Generator { - yield* takeEvery(AppActionType.DidStart, startup); yield* takeEvery(BleUartActionType.Notify, receiveUartData); yield* fork(receiveTerminalData); yield* takeEvery(TerminalActionType.SendData, sendTerminalData);