From 3b2b013cacc8cb979e353d07a3459695db6fb7d7 Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford <7019101+cwillisf@users.noreply.github.com> Date: Fri, 12 May 2023 15:19:22 -0700 Subject: [PATCH] feat: rough in a firmware update flow --- .../connection-modal/connection-modal.jsx | 5 +- .../connection-modal/scanning-step.jsx | 21 +++ .../connection-modal/update-firmware-step.jsx | 157 ++++++++++++++++++ src/containers/connection-modal.jsx | 32 +++- src/containers/scanning-step.jsx | 4 + 5 files changed, 217 insertions(+), 2 deletions(-) create mode 100644 src/components/connection-modal/update-firmware-step.jsx diff --git a/src/components/connection-modal/connection-modal.jsx b/src/components/connection-modal/connection-modal.jsx index a22cce5a1..97218ce93 100644 --- a/src/components/connection-modal/connection-modal.jsx +++ b/src/components/connection-modal/connection-modal.jsx @@ -11,6 +11,7 @@ import ConnectingStep from './connecting-step.jsx'; import ConnectedStep from './connected-step.jsx'; import ErrorStep from './error-step.jsx'; import UnavailableStep from './unavailable-step.jsx'; +import UpdateFirmwareStep from './update-firmware-step.jsx'; import styles from './connection-modal.css'; @@ -19,7 +20,8 @@ const PHASES = keyMirror({ connecting: null, connected: null, error: null, - unavailable: null + unavailable: null, + updateFirmware: null }); const ConnectionModalComponent = props => ( @@ -39,6 +41,7 @@ const ConnectionModalComponent = props => ( {props.phase === PHASES.connected && } {props.phase === PHASES.error && } {props.phase === PHASES.unavailable && } + {props.phase === PHASES.updateFirmware && } ); diff --git a/src/components/connection-modal/scanning-step.jsx b/src/components/connection-modal/scanning-step.jsx index cf637ec22..c3bf5f997 100644 --- a/src/components/connection-modal/scanning-step.jsx +++ b/src/components/connection-modal/scanning-step.jsx @@ -51,6 +51,25 @@ const ScanningStep = props => ( description="Text shown when no devices could be found" id="gui.connection.scanning.noPeripheralsFound" /> + { + props.onSendFirmware &&

+ + +

+ } )} @@ -89,6 +108,8 @@ ScanningStep.propTypes = { connectionSmallIconURL: PropTypes.string, onConnecting: PropTypes.func, onRefresh: PropTypes.func, + onSendFirmware: PropTypes.func, + onUpdateFirmware: PropTypes.func, peripheralList: PropTypes.arrayOf(PropTypes.shape({ name: PropTypes.string, rssi: PropTypes.number, diff --git a/src/components/connection-modal/update-firmware-step.jsx b/src/components/connection-modal/update-firmware-step.jsx new file mode 100644 index 000000000..e48b38fc1 --- /dev/null +++ b/src/components/connection-modal/update-firmware-step.jsx @@ -0,0 +1,157 @@ +import 'regenerator-runtime/runtime'; +import {FormattedMessage} from 'react-intl'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import React from 'react'; +import bindAll from 'lodash.bindall'; +import keyMirror from 'keymirror'; + +import Box from '../box/box.jsx'; +import Dots from './dots.jsx'; +import backIcon from './icons/back.svg'; + +import styles from './connection-modal.css'; + +/** @enum{string} UPDATE_ACTIVITY */ +const UPDATE_ACTIVITY = keyMirror({ + getReady: null, + sendFirmware: null, + results: null +}); + +class UpdateFirmwareStep extends React.Component { + constructor (props) { + super(props); + bindAll(this, [ + 'handleUpdateFirmware' + ]); + this.state = { + /** + * @type {UPDATE_ACTIVITY} + */ + activity: UPDATE_ACTIVITY.getReady, + progress: 0 + }; + } + + async handleUpdateFirmware () { + this.setState({ + activity: UPDATE_ACTIVITY.sendFirmware, + progress: 0 + }); + const results = await this.props.onSendFirmware(progress => { + this.setState({progress}); + }); + this.setState({ + activity: UPDATE_ACTIVITY.results, + results + }); + } + + renderGetReady () { + return ( + + + + + { + // The instructions for getting the peripheral ready for the update process will vary depending on + // the peripheral. Should we get this from the extension somehow? + } + + + + + + ); + } + + renderSendFirmware () { + return ( +

{'Sending update...'}

+ +
); + } + + renderResults () { + return ( +

{'Results'}

+
); + } + + render () { + return ( + + {(this.state.activity === UPDATE_ACTIVITY.getReady) && this.renderGetReady()} + {(this.state.activity === UPDATE_ACTIVITY.sendFirmware) && this.renderSendFirmware()} + {(this.state.activity === UPDATE_ACTIVITY.results) && this.renderResults()} + + + + + + + + + ); + } +} + +UpdateFirmwareStep.propTypes = { + name: PropTypes.string.isRequired, + onScanning: PropTypes.func.isRequired, + onSendFirmware: PropTypes.func.isRequired +}; + +export default UpdateFirmwareStep; diff --git a/src/containers/connection-modal.jsx b/src/containers/connection-modal.jsx index aefbb7bb5..823fc1506 100644 --- a/src/containers/connection-modal.jsx +++ b/src/containers/connection-modal.jsx @@ -18,7 +18,9 @@ class ConnectionModal extends React.Component { 'handleConnecting', 'handleDisconnect', 'handleError', - 'handleHelp' + 'handleHelp', + 'handleUpdateFirmware', + 'handleSendFirmware' ]); this.state = { extension: extensionData.find(ext => ext.extensionId === props.extensionId), @@ -104,7 +106,33 @@ class ConnectionModal extends React.Component { label: this.props.extensionId }); } + handleUpdateFirmware () { + this.setState({ + phase: PHASES.updateFirmware + }); + analytics.event({ + category: 'extensions', + action: 'update firmware', + label: this.props.extensionId + }); + } + handleSendFirmware (progressCallback) { + // TODO: get this functionality from the extension + // TODO: actually send firmware to the device + let progress = 0; + return new Promise(resolve => { + const interval = setInterval(() => { + progress += 0.01; + if (progress >= 1) { + clearInterval(interval); + resolve({success: true}); + } + progressCallback(progress); + }, 100); + }); + } render () { + const canSendFirmware = this.props.extensionId === 'microbit'; return ( ); } diff --git a/src/containers/scanning-step.jsx b/src/containers/scanning-step.jsx index 93fa6b7da..b638e3230 100644 --- a/src/containers/scanning-step.jsx +++ b/src/containers/scanning-step.jsx @@ -62,6 +62,8 @@ class ScanningStep extends React.Component { onConnected={this.props.onConnected} onConnecting={this.props.onConnecting} onRefresh={this.handleRefresh} + onSendFirmware={this.props.onSendFirmware} + onUpdateFirmware={this.props.onUpdateFirmware} /> ); } @@ -72,6 +74,8 @@ ScanningStep.propTypes = { extensionId: PropTypes.string.isRequired, onConnected: PropTypes.func.isRequired, onConnecting: PropTypes.func.isRequired, + onSendFirmware: PropTypes.func, + onUpdateFirmware: PropTypes.func, vm: PropTypes.instanceOf(VM).isRequired };