mirror of
https://github.com/pybricks/pybricks-code.git
synced 2026-09-12 01:23:52 +00:00
move terminal dataSource to context
state should only hold serializeable data
This commit is contained in:
@@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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 });
|
||||
@@ -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
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user