feat: rough in a firmware update flow
This commit is contained in:
1 parent
18407b15af
commit
3b2b013cac
5 files changed
+217
-2
No files matched your search
@@ -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 && <ConnectedStep {...props} />}
|
||||
{props.phase === PHASES.error && <ErrorStep {...props} />}
|
||||
{props.phase === PHASES.unavailable && <UnavailableStep {...props} />}
|
||||
{props.phase === PHASES.updateFirmware && <UpdateFirmwareStep {...props} />}
|
||||
</Box>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -51,6 +51,25 @@ const ScanningStep = props => (
|
||||
description="Text shown when no devices could be found"
|
||||
id="gui.connection.scanning.noPeripheralsFound"
|
||||
/>
|
||||
{
|
||||
props.onSendFirmware && <p>
|
||||
<FormattedMessage
|
||||
defaultMessage="Need to update your device to work with Scratch?"
|
||||
description="Prompt for updating firmware"
|
||||
id="gui.connection.scanning.updateFirmwarePrompt"
|
||||
/>
|
||||
<button
|
||||
className={classNames(styles.bottomAreaItem, styles.connectionButton)}
|
||||
onClick={props.onUpdateFirmware}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Update my Device"
|
||||
description="Button in prompt for starting a search"
|
||||
id="gui.connection.scanning.updateFirmwareButton"
|
||||
/>
|
||||
</button>
|
||||
</p>
|
||||
}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
@@ -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,
|
||||
|
||||
@@ -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 (<Box className={styles.activityArea}>
|
||||
<Box className={styles.centeredRow}>
|
||||
<FormattedMessage
|
||||
defaultMessage="This will update your {extensionName} to work with Scratch."
|
||||
description="Introduction to the firmware update process for a peripheral"
|
||||
id="gui.connection.updateFirmware.introduction"
|
||||
values={{
|
||||
extensionName: this.props.name
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box className={styles.centeredRow}>
|
||||
{
|
||||
// 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?
|
||||
}
|
||||
<FormattedMessage
|
||||
defaultMessage="Please connect your {extensionName} to this device using a USB cable."
|
||||
description="Instructions to connect the micro:bit to the computer for the firmware update process"
|
||||
id="gui.connection.updateFirmware.microBitConnect"
|
||||
values={{
|
||||
extensionName: this.props.name
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box className={styles.centeredRow}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Please don't disconnect, reset, or turn off your {extensionName} during the update."
|
||||
description="Notice to not disrupt the firmware update process"
|
||||
id="gui.connection.updateFirmware.doNotDisconnect"
|
||||
values={{
|
||||
extensionName: this.props.name
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>);
|
||||
}
|
||||
|
||||
renderSendFirmware () {
|
||||
return (<Box className={styles.activityArea}>
|
||||
<p>{'Sending update...'}</p>
|
||||
<progress
|
||||
max="1"
|
||||
value={this.state.progress}
|
||||
/>
|
||||
</Box>);
|
||||
}
|
||||
|
||||
renderResults () {
|
||||
return (<Box className={styles.activityArea}>
|
||||
<p>{'Results'}</p>
|
||||
</Box>);
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<Box className={styles.body}>
|
||||
{(this.state.activity === UPDATE_ACTIVITY.getReady) && this.renderGetReady()}
|
||||
{(this.state.activity === UPDATE_ACTIVITY.sendFirmware) && this.renderSendFirmware()}
|
||||
{(this.state.activity === UPDATE_ACTIVITY.results) && this.renderResults()}
|
||||
<Box className={styles.bottomArea}>
|
||||
<Dots
|
||||
className={styles.bottomAreaItem}
|
||||
total={3}
|
||||
/>
|
||||
<Box className={classNames(styles.bottomAreaItem, styles.buttonRow)}>
|
||||
<button
|
||||
className={styles.connectionButton}
|
||||
onClick={this.props.onScanning}
|
||||
disabled={this.state.activity === UPDATE_ACTIVITY.sendFirmware}
|
||||
>
|
||||
<img
|
||||
className={classNames(styles.buttonIconLeft, styles.buttonIconBack)}
|
||||
src={backIcon}
|
||||
/>
|
||||
<FormattedMessage
|
||||
defaultMessage="Go back"
|
||||
description="Button to leave the firmware update process"
|
||||
id="gui.connection.updateFirmware.goBackButton"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
className={styles.connectionButton}
|
||||
onClick={this.handleUpdateFirmware}
|
||||
disabled={this.state.activity !== UPDATE_ACTIVITY.getReady}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Update now"
|
||||
description="Button to start the firmware update"
|
||||
id="gui.connection.updateFirmware.updateNowButton"
|
||||
/>
|
||||
</button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
UpdateFirmwareStep.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
onScanning: PropTypes.func.isRequired,
|
||||
onSendFirmware: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default UpdateFirmwareStep;
|
||||
@@ -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 (
|
||||
<ConnectionModalComponent
|
||||
connectingMessage={this.state.extension && this.state.extension.connectingMessage}
|
||||
@@ -123,6 +151,8 @@ class ConnectionModal extends React.Component {
|
||||
onDisconnect={this.handleDisconnect}
|
||||
onHelp={this.handleHelp}
|
||||
onScanning={this.handleScanning}
|
||||
onUpdateFirmware={this.handleUpdateFirmware}
|
||||
onSendFirmware={canSendFirmware ? this.handleSendFirmware : null}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
Reference in new issue
Block a user