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
};