diff --git a/src/actions/editor.ts b/src/actions/editor.ts new file mode 100644 index 00000000..829382af --- /dev/null +++ b/src/actions/editor.ts @@ -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 { + 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 }; +} diff --git a/src/actions/hub.ts b/src/actions/hub.ts new file mode 100644 index 00000000..d3b765d2 --- /dev/null +++ b/src/actions/hub.ts @@ -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, {}, {}, 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 { + readonly newStatus: HubRuntimeStatusType; +} + +export interface HubChecksumAction extends Action { + 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 { + // 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)); + }; +} diff --git a/src/actions/mpy.ts b/src/actions/mpy.ts new file mode 100644 index 00000000..8a21aa71 --- /dev/null +++ b/src/actions/mpy.ts @@ -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 { + /** + * 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 }; +} diff --git a/src/components/ActionButton.tsx b/src/components/ActionButton.tsx index b3c60ece..381660fb 100644 --- a/src/components/ActionButton.tsx +++ b/src/components/ActionButton.tsx @@ -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 { /** 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 { +class ActionButton extends React.Component> { render(): JSX.Element { return ( { >