move terminal dataSource to context

state should only hold serializeable data
This commit is contained in:
David Lechner
2021-02-01 10:26:05 -06:00
parent 0c20e52a45
commit a24c263789
8 changed files with 40 additions and 73 deletions
+2
View File
@@ -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,
},
});
-3
View File
@@ -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,
});
+6 -4
View File
@@ -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<string> | null;
darkMode: boolean;
}
@@ -54,6 +54,9 @@ class Terminal extends React.Component<TerminalProps> {
this.terminalRef = React.createRef();
}
static contextType = TerminalContext;
context!: React.ContextType<typeof TerminalContext>;
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<TerminalProps> {
}
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<TerminalProps> {
}
const mapStateToProps = (state: RootState): StateProps => ({
dataSource: state.terminal.dataSource,
darkMode: state.settings.darkMode,
});
+18
View File
@@ -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<string>(),
};
/** Terminal context data type. */
export type TerminalContextValue = typeof defaultTerminalContext;
/** Terminal React context. */
export const TerminalContext = createContext(defaultTerminalContext);
+1 -16
View File
@@ -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<TerminalActionType.SetDataSource> & {
dataSource: Observable<string>;
};
export function setDataSource(
dataSource: Observable<string>,
): TerminalSetDataSourceAction {
return { type: TerminalActionType.SetDataSource, dataSource };
}
export type TerminalDataSendDataAction = Action<TerminalActionType.SendData> & {
value: string;
};
@@ -45,6 +31,5 @@ export function receiveData(data: string): TerminalDataReceiveDataAction {
}
export type TerminalDataAction =
| TerminalSetDataSourceAction
| TerminalDataSendDataAction
| TerminalDataReceiveDataAction;
-24
View File
@@ -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<string> | null;
const dataSource: Reducer<DataSource, Action> = (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 });
+8 -10
View File
@@ -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<string>();
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<string>();
dataSource.subscribe({ next: (v) => data.push(v) });
dataSource.observable.subscribe({ next: (v) => data.push(v) });
saga.put(sendData('1'));
saga.put(sendData('2'));
+5 -16
View File
@@ -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<string>();
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<TerminalContextValue>('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);