mirror of
https://github.com/pybricks/pybricks-code.git
synced 2026-09-12 01:23:52 +00:00
wire up run button
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
import { Ace } from 'ace-builds';
|
||||
import { Action } from 'redux';
|
||||
|
||||
export enum EditorActionType {
|
||||
/**
|
||||
* The current (active) editor changed.
|
||||
*/
|
||||
Current = 'editor.action.current',
|
||||
}
|
||||
|
||||
export interface CurrentEditorAction extends Action<EditorActionType.Current> {
|
||||
editSession: Ace.EditSession | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current (active) edit session.
|
||||
* @param editSession The new edit session.
|
||||
*/
|
||||
export function setEditSession(
|
||||
editSession: Ace.EditSession | undefined,
|
||||
): CurrentEditorAction {
|
||||
return { type: EditorActionType.Current, editSession };
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import { Action } from 'redux';
|
||||
import { ThunkAction } from 'redux-thunk';
|
||||
import { getChecksum } from '../epics/hub';
|
||||
import { write } from './ble';
|
||||
|
||||
export type HubThunkAction = ThunkAction<Promise<void>, {}, {}, HubRuntimeStatusAction>;
|
||||
|
||||
export enum HubRuntimeStatusType {
|
||||
Disconnected = 'disconnected',
|
||||
Idle = 'idle',
|
||||
Loading = 'loading',
|
||||
Loaded = 'loaded',
|
||||
Running = 'running',
|
||||
Error = 'error',
|
||||
}
|
||||
|
||||
export enum HubActionType {
|
||||
/**
|
||||
* MicroPython runtime status changed.
|
||||
*/
|
||||
RuntimeStatus = 'hub.runtime.status',
|
||||
/**
|
||||
* The hub has sent a checksum.
|
||||
*/
|
||||
Checksum = 'hub.runtime.checksum',
|
||||
}
|
||||
|
||||
export interface HubRuntimeStatusAction extends Action<HubActionType.RuntimeStatus> {
|
||||
readonly newStatus: HubRuntimeStatusType;
|
||||
}
|
||||
|
||||
export interface HubChecksumAction extends Action<HubActionType.Checksum> {
|
||||
readonly checksum: number;
|
||||
}
|
||||
|
||||
export function updateStatus(newStatus: HubRuntimeStatusType): HubRuntimeStatusAction {
|
||||
return {
|
||||
type: HubActionType.RuntimeStatus,
|
||||
newStatus,
|
||||
};
|
||||
}
|
||||
|
||||
export function checksum(checksum: number): HubChecksumAction {
|
||||
return {
|
||||
type: HubActionType.Checksum,
|
||||
checksum,
|
||||
};
|
||||
}
|
||||
|
||||
const downloadChunkSize = 100;
|
||||
|
||||
export function downloadAndRun(data: ArrayBuffer): HubThunkAction {
|
||||
return async function (dispatch): Promise<void> {
|
||||
// let everyone know the runtime is busy loading the program
|
||||
dispatch(updateStatus(HubRuntimeStatusType.Loading));
|
||||
|
||||
// TODO: might need to flush checksum queue here
|
||||
|
||||
// first send payload size as big-endian 32-bit integer
|
||||
const sizeBuf = new Uint8Array(4);
|
||||
const sizeView = new DataView(sizeBuf.buffer);
|
||||
sizeView.setUint32(0, data.byteLength);
|
||||
await dispatch(write(sizeBuf));
|
||||
|
||||
// Then send payload in 100 byte chunks waiting for checksum after
|
||||
// each chunk
|
||||
for (let i = 0; i < data.byteLength; i += downloadChunkSize) {
|
||||
// need to subscribe to checksum before writing to prevent race condition
|
||||
const checksum = getChecksum();
|
||||
await dispatch(write(data.slice(i, i + downloadChunkSize)));
|
||||
// TODO: verify checksum
|
||||
console.log(await checksum);
|
||||
// TODO: dispatch progress
|
||||
}
|
||||
|
||||
// let everyone know the runtime is done loading the program
|
||||
dispatch(updateStatus(HubRuntimeStatusType.Loaded));
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import MpyCross from '@pybricks/mpy-cross';
|
||||
import { Action } from 'redux';
|
||||
|
||||
// this starts the mpy-cross wasm runtime and leaves it running in the background
|
||||
const mpy = MpyCross({ arguments: ['-mno-unicode'] });
|
||||
|
||||
enum MpyActionType {
|
||||
Compiled = 'mpy.action.compile',
|
||||
}
|
||||
|
||||
interface MpyCompiledAction extends Action<MpyActionType.Compiled> {
|
||||
/**
|
||||
* The compiled .mpy data.
|
||||
*/
|
||||
data: Uint8Array;
|
||||
}
|
||||
|
||||
export function compile(script: string): MpyCompiledAction {
|
||||
// TODO: figure out how to capture stderr and emit error action on failure
|
||||
const data = mpy.compile(script);
|
||||
return { type: MpyActionType.Compiled, data };
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import Tooltip from 'react-bootstrap/Tooltip';
|
||||
import Image from 'react-bootstrap/Image';
|
||||
import React from 'react';
|
||||
|
||||
interface ActionButtonProps {
|
||||
export interface ActionButtonProps<T = undefined> {
|
||||
/** A unique id for each instance. */
|
||||
readonly id: string;
|
||||
/** Tooltip text that appears when hovering over the button. */
|
||||
@@ -14,12 +14,12 @@ interface ActionButtonProps {
|
||||
/** When true or undefined, the button is enabled. */
|
||||
readonly enabled?: boolean;
|
||||
/** Optional action hint passed to the onAction() callback. */
|
||||
readonly action?: string;
|
||||
readonly context?: T;
|
||||
/** Callback that is called when the button is activated (clicked). */
|
||||
readonly onAction: (action?: string) => void;
|
||||
readonly onAction: (context?: T) => void;
|
||||
}
|
||||
|
||||
class ActionButton extends React.Component<ActionButtonProps> {
|
||||
class ActionButton<T = undefined> extends React.Component<ActionButtonProps<T>> {
|
||||
render(): JSX.Element {
|
||||
return (
|
||||
<OverlayTrigger
|
||||
@@ -32,7 +32,7 @@ class ActionButton extends React.Component<ActionButtonProps> {
|
||||
>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={(): void => this.props.onAction(this.props.action)}
|
||||
onClick={(): void => this.props.onAction(this.props.context)}
|
||||
disabled={this.props.enabled === false}
|
||||
style={
|
||||
this.props.enabled === false
|
||||
|
||||
@@ -4,42 +4,35 @@ import { AnyAction } from 'redux';
|
||||
import { connect as bleConnect, disconnect as bleDisconnect } from '../actions/ble';
|
||||
import { RootState } from '../reducers';
|
||||
import { BLEConnectionState } from '../reducers/ble';
|
||||
import ActionButton from './ActionButton';
|
||||
import ActionButton, { ActionButtonProps } from './ActionButton';
|
||||
|
||||
type Dispatch = ThunkDispatch<{}, {}, AnyAction>;
|
||||
|
||||
interface StateProps {
|
||||
readonly tooltip: string;
|
||||
readonly icon: string;
|
||||
readonly action?: string;
|
||||
readonly enabled?: boolean;
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
readonly onAction: (action?: string) => void;
|
||||
}
|
||||
type ButtonProps = ActionButtonProps<string>;
|
||||
type StateProps = Pick<ButtonProps, 'tooltip' | 'icon' | 'context' | 'enabled'>;
|
||||
type DispatchProps = Pick<ButtonProps, 'onAction'>;
|
||||
|
||||
const mapStateToProps = (state: RootState): StateProps => {
|
||||
if (state.ble.connection === BLEConnectionState.Disconnected) {
|
||||
return {
|
||||
tooltip: 'Connect using Bluetooth',
|
||||
icon: 'btdisconnected.svg',
|
||||
action: 'connect',
|
||||
context: 'connect',
|
||||
enabled: true,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
tooltip: 'Disconnect Bluetooth',
|
||||
icon: 'btconnected.svg',
|
||||
action: 'disconnect',
|
||||
context: 'disconnect',
|
||||
enabled: state.ble.connection === BLEConnectionState.Connected,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({
|
||||
onAction: (a): void => {
|
||||
if (a === 'connect') {
|
||||
onAction: (c): void => {
|
||||
if (c === 'connect') {
|
||||
dispatch(bleConnect());
|
||||
} else {
|
||||
dispatch(bleDisconnect());
|
||||
|
||||
+29
-16
@@ -1,7 +1,9 @@
|
||||
import React from 'react';
|
||||
import React, { ReactElement } from 'react';
|
||||
import { ReactReduxContext } from 'react-redux';
|
||||
import AceEditor from 'react-ace';
|
||||
import Row from 'react-bootstrap/Row';
|
||||
import Col from 'react-bootstrap/Col';
|
||||
import { setEditSession } from '../actions/editor';
|
||||
|
||||
import 'ace-builds/src-noconflict/mode-python';
|
||||
import 'ace-builds/src-noconflict/theme-xcode';
|
||||
@@ -13,21 +15,32 @@ class Editor extends React.Component {
|
||||
return (
|
||||
<Row className="px-2 py-4 bg-primary">
|
||||
<Col>
|
||||
<AceEditor
|
||||
mode="python"
|
||||
theme="xcode"
|
||||
fontSize="16pt"
|
||||
width="100"
|
||||
focus={true}
|
||||
placeholder="Write your program here..."
|
||||
defaultValue={localStorage.getItem('program') || undefined}
|
||||
editorProps={{ $blockScrolling: true }}
|
||||
setOptions={{
|
||||
enableBasicAutocompletion: true,
|
||||
enableLiveAutocompletion: true,
|
||||
}}
|
||||
onChange={(v): void => localStorage.setItem('program', v)}
|
||||
/>
|
||||
<ReactReduxContext.Consumer>
|
||||
{({ store }): ReactElement => (
|
||||
<AceEditor
|
||||
mode="python"
|
||||
theme="xcode"
|
||||
fontSize="16pt"
|
||||
width="100"
|
||||
focus={true}
|
||||
placeholder="Write your program here..."
|
||||
defaultValue={
|
||||
localStorage.getItem('program') || undefined
|
||||
}
|
||||
editorProps={{ $blockScrolling: true }}
|
||||
setOptions={{
|
||||
enableBasicAutocompletion: true,
|
||||
enableLiveAutocompletion: true,
|
||||
}}
|
||||
onFocus={(_, e): void => {
|
||||
store.dispatch(setEditSession(e?.session));
|
||||
}}
|
||||
onChange={(v): void =>
|
||||
localStorage.setItem('program', v)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</ReactReduxContext.Consumer>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { connect, batch } from 'react-redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import { AnyAction } from 'redux';
|
||||
import { Ace } from 'ace-builds';
|
||||
import { RootState } from '../reducers';
|
||||
import { HubRuntimeState } from '../reducers/hub';
|
||||
import { compile } from '../actions/mpy';
|
||||
import { downloadAndRun } from '../actions/hub';
|
||||
import ActionButton, { ActionButtonProps } from './ActionButton';
|
||||
|
||||
type Dispatch = ThunkDispatch<{}, {}, AnyAction>;
|
||||
|
||||
type ButtonProps = ActionButtonProps<Ace.EditSession>;
|
||||
type StateProps = Pick<ButtonProps, 'enabled' | 'context'>;
|
||||
type DispatchProps = Pick<ButtonProps, 'onAction'>;
|
||||
type OwnProps = Pick<ButtonProps, 'id'>;
|
||||
|
||||
const mapStateToProps = (state: RootState): StateProps => ({
|
||||
enabled:
|
||||
state.editor.current !== null && state.hub.runtime === HubRuntimeState.Idle,
|
||||
context: state.editor.current || undefined,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({
|
||||
onAction: (c): void => {
|
||||
if (!c) {
|
||||
console.error('No current editor');
|
||||
return;
|
||||
}
|
||||
batch(() => {
|
||||
const script = c.getValue();
|
||||
const mpy = dispatch(compile(script));
|
||||
dispatch(downloadAndRun(mpy.data));
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const mergeProps = (
|
||||
stateProps: StateProps,
|
||||
dispatchProps: DispatchProps,
|
||||
ownProps: OwnProps,
|
||||
): ButtonProps => ({
|
||||
tooltip: 'Download and run this program',
|
||||
icon: 'run.svg',
|
||||
...ownProps,
|
||||
...stateProps,
|
||||
...dispatchProps,
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(ActionButton);
|
||||
@@ -6,6 +6,7 @@ import React from 'react';
|
||||
import { BLEConnectionState } from '../reducers/ble';
|
||||
import ActionButton from './ActionButton';
|
||||
import BluetoothButton from './BluetoothButton';
|
||||
import RunButton from './RunButton';
|
||||
|
||||
interface ToolbarState {
|
||||
bleState: BLEConnectionState;
|
||||
@@ -33,12 +34,7 @@ class Toolbar extends React.Component<{}, ToolbarState> {
|
||||
<ButtonToolbar className="m-2">
|
||||
<ButtonGroup className="mr-2" size="lg">
|
||||
<BluetoothButton id="bluetooth" />
|
||||
<ActionButton
|
||||
id="run"
|
||||
tooltip="Download and run this program"
|
||||
icon="run.svg"
|
||||
onAction={this.onAction}
|
||||
/>
|
||||
<RunButton id="run" />
|
||||
<ActionButton
|
||||
id="stop"
|
||||
tooltip="Stop everything"
|
||||
|
||||
+44
-9
@@ -1,17 +1,52 @@
|
||||
import { combineEpics, Epic, ofType } from 'redux-observable';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { BLEDataActionType, BLEDataAction } from '../actions/ble';
|
||||
import { TerminalDataAction, sendData } from '../actions/terminal';
|
||||
import { AnyAction } from 'redux';
|
||||
import { BLEDataActionType, BLEDataAction, BLEConnectActionType } from '../actions/ble';
|
||||
import { sendData } from '../actions/terminal';
|
||||
import { updateStatus, HubRuntimeStatusType, checksum } from '../actions/hub';
|
||||
import { RootState } from '../reducers';
|
||||
import { HubRuntimeState } from '../reducers/hub';
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
const rxUartData: Epic = (action$) =>
|
||||
const connect: Epic = (action$) =>
|
||||
action$.pipe(
|
||||
ofType(BLEDataActionType.ReceivedData),
|
||||
map(
|
||||
(a: BLEDataAction): TerminalDataAction =>
|
||||
sendData(decoder.decode(a.value.buffer)),
|
||||
),
|
||||
ofType(BLEConnectActionType.EndConnect),
|
||||
map(() => updateStatus(HubRuntimeStatusType.Idle)),
|
||||
);
|
||||
|
||||
export default combineEpics(rxUartData);
|
||||
const disconnect: Epic = (action$) =>
|
||||
action$.pipe(
|
||||
ofType(BLEConnectActionType.EndDisconnect),
|
||||
map(() => updateStatus(HubRuntimeStatusType.Disconnected)),
|
||||
);
|
||||
|
||||
const rxUartData: Epic<AnyAction, AnyAction, RootState> = (action$, state$) =>
|
||||
action$.pipe(
|
||||
ofType<AnyAction, BLEDataAction>(BLEDataActionType.ReceivedData),
|
||||
map((a) => {
|
||||
if (
|
||||
state$.value.hub.runtime === HubRuntimeState.Loading &&
|
||||
a.value.buffer.byteLength === 1
|
||||
) {
|
||||
const view = new DataView(a.value.buffer);
|
||||
return checksum(view.getUint8(0));
|
||||
} else {
|
||||
const value = decoder.decode(a.value.buffer);
|
||||
// FIXME: sometimes we get ERROR and IDLE in same message except
|
||||
// last E is cut off
|
||||
if (value.match(/>>>> IDLE/)) {
|
||||
return updateStatus(HubRuntimeStatusType.Idle);
|
||||
}
|
||||
if (value.match(/>>>> ERROR/)) {
|
||||
return updateStatus(HubRuntimeStatusType.Error);
|
||||
}
|
||||
if (value.match(/>>>> RUNNING/)) {
|
||||
return updateStatus(HubRuntimeStatusType.Running);
|
||||
}
|
||||
return sendData(value);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
export default combineEpics(connect, disconnect, rxUartData);
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { combineEpics, Epic, ofType } from 'redux-observable';
|
||||
import { AnyAction } from 'redux';
|
||||
import { take, tap, ignoreElements } from 'rxjs/operators';
|
||||
import { Subject } from 'rxjs';
|
||||
import { HubActionType, HubChecksumAction } from '../actions/hub';
|
||||
|
||||
const checksumSubject = new Subject<number>();
|
||||
|
||||
const checksum: Epic = (action$) =>
|
||||
action$.pipe(
|
||||
ofType<AnyAction, HubChecksumAction>(HubActionType.Checksum),
|
||||
tap((a) => checksumSubject.next(a.checksum)),
|
||||
ignoreElements(),
|
||||
);
|
||||
|
||||
export function getChecksum(): Promise<number> {
|
||||
return checksumSubject.pipe(take(1)).toPromise();
|
||||
}
|
||||
|
||||
export default combineEpics(checksum);
|
||||
+2
-1
@@ -1,10 +1,11 @@
|
||||
import { combineEpics, Epic } from 'redux-observable';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
import ble from './ble';
|
||||
import hub from './hub';
|
||||
import terminal from './terminal';
|
||||
|
||||
const rootEpic: Epic = (action$, store$, dependencies) =>
|
||||
combineEpics(ble, terminal)(action$, store$, dependencies).pipe(
|
||||
combineEpics(ble, hub, terminal)(action$, store$, dependencies).pipe(
|
||||
catchError((error, source) => {
|
||||
console.error(error);
|
||||
return source;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { combineEpics, Epic, ofType } from 'redux-observable';
|
||||
import { Subject } from 'rxjs';
|
||||
import { map, tap, ignoreElements } from 'rxjs/operators';
|
||||
import { write, BLEThunkAction } from '../actions/ble';
|
||||
import { AnyAction } from 'redux';
|
||||
import { write } from '../actions/ble';
|
||||
import { TerminalDataAction, TerminalDataActionType } from '../actions/terminal';
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
@@ -12,16 +13,16 @@ export const terminalOutput = terminalOutputSubject.asObservable();
|
||||
// When terminal has focus this receives the input. All input is sent over BLE connection.
|
||||
const receiveTerminalData: Epic = (action$) =>
|
||||
action$.pipe(
|
||||
ofType(TerminalDataActionType.ReceivedData),
|
||||
map((a: TerminalDataAction): BLEThunkAction => write(encoder.encode(a.value))),
|
||||
ofType<AnyAction, TerminalDataAction>(TerminalDataActionType.ReceivedData),
|
||||
map((a) => write(encoder.encode(a.value))),
|
||||
);
|
||||
|
||||
// Request to write to the terminal are handled by an observable rather than
|
||||
// using redux state.
|
||||
const sendTerminalData: Epic = (action$) =>
|
||||
action$.pipe(
|
||||
ofType(TerminalDataActionType.SendData),
|
||||
tap((a: TerminalDataAction): void => terminalOutputSubject.next(a.value)),
|
||||
ofType<AnyAction, TerminalDataAction>(TerminalDataActionType.SendData),
|
||||
tap((a) => terminalOutputSubject.next(a.value)),
|
||||
ignoreElements(),
|
||||
);
|
||||
|
||||
|
||||
+4
-7
@@ -23,13 +23,6 @@ export enum BLEConnectionState {
|
||||
Disconnecting = 'ble.connection.disconnecting',
|
||||
}
|
||||
|
||||
/**
|
||||
* BLE state for redux store.
|
||||
*/
|
||||
export interface BLEState {
|
||||
readonly connection: BLEConnectionState;
|
||||
}
|
||||
|
||||
const connection: Reducer<BLEConnectionState> = (
|
||||
state = BLEConnectionState.Disconnected,
|
||||
action,
|
||||
@@ -48,4 +41,8 @@ const connection: Reducer<BLEConnectionState> = (
|
||||
}
|
||||
};
|
||||
|
||||
export interface BLEState {
|
||||
readonly connection: BLEConnectionState;
|
||||
}
|
||||
|
||||
export default combineReducers({ connection });
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { combineReducers, Reducer } from 'redux';
|
||||
import { Ace } from 'ace-builds';
|
||||
import { CurrentEditorAction, EditorActionType } from '../actions/editor';
|
||||
|
||||
type CurrentEditSession = Ace.EditSession | null;
|
||||
|
||||
const current: Reducer<CurrentEditSession, CurrentEditorAction> = (
|
||||
state = null,
|
||||
action,
|
||||
) => {
|
||||
switch (action.type) {
|
||||
case EditorActionType.Current:
|
||||
return action.editSession || null;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export interface EditorState {
|
||||
current: CurrentEditSession;
|
||||
}
|
||||
export default combineReducers({ current });
|
||||
@@ -0,0 +1,70 @@
|
||||
import { Reducer, combineReducers } from 'redux';
|
||||
import {
|
||||
HubRuntimeStatusAction,
|
||||
HubActionType,
|
||||
HubRuntimeStatusType,
|
||||
} from '../actions/hub';
|
||||
|
||||
/**
|
||||
* Describes the state of the MicroPython runtime on the hub.
|
||||
*/
|
||||
export enum HubRuntimeState {
|
||||
/**
|
||||
* The hub is not connected.
|
||||
*/
|
||||
Disconnected = 'hub.runtime.disconnected',
|
||||
/**
|
||||
* The runtime is idle waiting for command after soft reboot.
|
||||
*/
|
||||
Idle = 'hub.runtime.idle',
|
||||
/**
|
||||
* A user program is being copied to the hub.
|
||||
*/
|
||||
Loading = 'hub.runtime.loading',
|
||||
/**
|
||||
* A user program has been copied to the hub.
|
||||
*/
|
||||
Loaded = 'hub.runtime.loaded',
|
||||
/**
|
||||
* A user program is running.
|
||||
*/
|
||||
Running = 'hub.runtime.running',
|
||||
/**
|
||||
* The runtime encountered an error.
|
||||
*/
|
||||
Error = 'hub.runtime.error',
|
||||
}
|
||||
|
||||
const runtime: Reducer<HubRuntimeState, HubRuntimeStatusAction> = (
|
||||
state = HubRuntimeState.Disconnected,
|
||||
action,
|
||||
) => {
|
||||
switch (action.type) {
|
||||
case HubActionType.RuntimeStatus:
|
||||
switch (action.newStatus) {
|
||||
case HubRuntimeStatusType.Disconnected:
|
||||
return HubRuntimeState.Disconnected;
|
||||
case HubRuntimeStatusType.Idle:
|
||||
return HubRuntimeState.Idle;
|
||||
case HubRuntimeStatusType.Loading:
|
||||
return HubRuntimeState.Loading;
|
||||
case HubRuntimeStatusType.Loaded:
|
||||
return HubRuntimeState.Loaded;
|
||||
case HubRuntimeStatusType.Running:
|
||||
return HubRuntimeState.Running;
|
||||
case HubRuntimeStatusType.Error:
|
||||
return HubRuntimeState.Error;
|
||||
default:
|
||||
console.error(`bad action/state: ${action.newStatus}`);
|
||||
return state;
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export interface HubState {
|
||||
readonly runtime: HubRuntimeState;
|
||||
}
|
||||
|
||||
export default combineReducers({ runtime });
|
||||
@@ -1,11 +1,15 @@
|
||||
import { combineReducers } from 'redux';
|
||||
import ble, { BLEState } from './ble';
|
||||
import editor, { EditorState } from './editor';
|
||||
import hub, { HubState } from './hub';
|
||||
|
||||
/**
|
||||
* Root state for redux store.
|
||||
*/
|
||||
export interface RootState {
|
||||
readonly ble: BLEState;
|
||||
readonly editor: EditorState;
|
||||
readonly hub: HubState;
|
||||
}
|
||||
|
||||
export default combineReducers({ ble });
|
||||
export default combineReducers({ ble, editor, hub });
|
||||
|
||||
Reference in New Issue
Block a user