fix: implement the majority of the micro:bit update UX design
There's still quite a bit of work to do on alignment, margins, etc.
This commit is contained in:
1 parent
11ad8512ce
commit
b6c2c9ffdf
13 files changed
+337
-138
No files matched your search
@@ -9,9 +9,11 @@ import Dots from './dots.jsx';
|
||||
|
||||
import closeIcon from '../close-button/icon--close.svg';
|
||||
|
||||
import radarIcon from './icons/searching.png';
|
||||
import bluetoothIcon from './icons/bluetooth-white.svg';
|
||||
import backIcon from './icons/back.svg';
|
||||
import bluetoothIcon from './icons/bluetooth-white.svg';
|
||||
import enterUpdateIcon from './icons/enter-update.svg';
|
||||
import radarIcon from './icons/searching.png';
|
||||
import warningIcon from './icons/warning.svg';
|
||||
|
||||
import styles from './connection-modal.css';
|
||||
|
||||
@@ -21,8 +23,13 @@ const PHASES = keyMirror({
|
||||
notfound: null
|
||||
});
|
||||
|
||||
const AutoScanningStep = props => (
|
||||
<Box className={styles.body}>
|
||||
const AutoScanningStep = props => {
|
||||
// Offer to update both during scan and after a failed scan, as long there's an update function.
|
||||
// It's possible the scan will find "some" device but not the desired device,
|
||||
// so don't limit the update offer to just the PHASES.notfound case.
|
||||
const showUpdate = !!(props.onUpdatePeripheral &&
|
||||
(props.phase === PHASES.pressbutton || props.phase === PHASES.notfound));
|
||||
return (<Box className={styles.body}>
|
||||
<Box className={styles.activityArea}>
|
||||
<div className={styles.activityAreaInfo}>
|
||||
<div className={styles.centeredRow}>
|
||||
@@ -51,13 +58,18 @@ const AutoScanningStep = props => (
|
||||
</React.Fragment>
|
||||
)}
|
||||
{props.phase === PHASES.notfound && (
|
||||
<Box className={styles.instructions}>
|
||||
<React.Fragment>
|
||||
<img
|
||||
className={styles.helpStepImage}
|
||||
src={warningIcon}
|
||||
/>
|
||||
<FormattedMessage
|
||||
className={styles.helpStepText}
|
||||
defaultMessage="No devices found"
|
||||
description="Text shown when no devices could be found"
|
||||
id="gui.connection.auto-scanning.noPeripheralsFound"
|
||||
/>
|
||||
</Box>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -79,6 +91,15 @@ const AutoScanningStep = props => (
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
{showUpdate && (
|
||||
<Box className={classNames(styles.bottomAreaItem, styles.instructions)}>
|
||||
<FormattedMessage
|
||||
defaultMessage="If you don't see your device, you may need to update it to work with Scratch."
|
||||
description="Prompt for updating a peripheral device"
|
||||
id="gui.connection.auto-scanning.updatePeripheralPrompt"
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
<Dots
|
||||
className={styles.bottomAreaItem}
|
||||
counter={0}
|
||||
@@ -136,15 +157,32 @@ const AutoScanningStep = props => (
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
{showUpdate && (
|
||||
<button
|
||||
className={classNames(styles.bottomAreaItem, styles.connectionButton)}
|
||||
onClick={props.onUpdatePeripheral}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Update my Device"
|
||||
description="Button to enter the peripheral update mode"
|
||||
id="gui.connection.auto-scanning.updatePeripheralButton"
|
||||
/>
|
||||
<img
|
||||
className={styles.buttonIconRight}
|
||||
src={enterUpdateIcon}
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
</Box>);
|
||||
};
|
||||
|
||||
AutoScanningStep.propTypes = {
|
||||
connectionTipIconURL: PropTypes.string,
|
||||
onRefresh: PropTypes.func,
|
||||
onStartScan: PropTypes.func,
|
||||
onUpdatePeripheral: PropTypes.func,
|
||||
phase: PropTypes.oneOf(Object.keys(PHASES))
|
||||
};
|
||||
|
||||
|
||||
@@ -255,6 +255,10 @@
|
||||
max-width: 50px;
|
||||
}
|
||||
|
||||
.help-step-image {
|
||||
max-width: 40px;
|
||||
}
|
||||
|
||||
[dir="ltr"] .help-step-image {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
@@ -423,3 +427,21 @@
|
||||
.error-dot {
|
||||
background: $error-primary;
|
||||
}
|
||||
|
||||
.scratch-link-error {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
height: 100%;
|
||||
padding-top: .5rem;
|
||||
padding-bottom: .5rem;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.scratch-link-error-details {
|
||||
display: flex;
|
||||
resize: none;
|
||||
height: 60%;
|
||||
width: inherit;
|
||||
}
|
||||
@@ -21,7 +21,7 @@ const PHASES = keyMirror({
|
||||
connected: null,
|
||||
error: null,
|
||||
unavailable: null,
|
||||
updateFirmware: null
|
||||
updatePeripheral: null
|
||||
});
|
||||
|
||||
const ConnectionModalComponent = props => (
|
||||
@@ -41,7 +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 && <UpdatePeripheralStep {...props} />}
|
||||
{props.phase === PHASES.updatePeripheral && <UpdatePeripheralStep {...props} />}
|
||||
</Box>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M15.7836 5.58724V11.727C15.769 12.2168 15.4786 12.6343 15.0426 12.8063C14.6075 12.9794 14.1304 12.8777 13.7951 12.5424L12.6307 11.378L7.7648 15.0569C7.02568 15.6259 5.95905 15.5546 5.28934 14.8849C5.23166 14.8272 5.17691 14.7646 5.12607 14.7001C4.61671 14.0011 4.6216 13.0635 5.12803 12.4104L8.78646 7.53378L7.6729 6.42022C7.33365 6.08097 7.23099 5.57942 7.41088 5.14338C7.59273 4.70539 8.00531 4.43164 8.48827 4.43164H14.628C14.9321 4.43262 15.2215 4.55385 15.4414 4.77382C15.6614 4.9938 15.7827 5.28319 15.7836 5.58724Z"
|
||||
fill="white" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 711 B |
@@ -0,0 +1,5 @@
|
||||
<svg width="14" height="12" viewBox="0 0 14 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M10.4042 4.1111C11.1236 4.89094 11.496 6.03441 11.3014 7.17959C11.1075 8.48497 10.1426 9.73101 8.79602 10.2481H8.78756C8.37282 10.4091 8.1189 10.8499 8.22893 11.2992C8.33897 11.7823 8.82988 12.0875 9.31232 11.9773C11.3268 11.5111 13.0873 9.88359 13.7221 7.78142C14.0437 6.73796 14.086 5.60296 13.8499 4.50949C13.6467 3.5686 13.2151 2.67009 12.6141 1.89873L13.4605 1.05108C13.8406 0.661166 13.5706 0 13.0196 0L8.98308 0C8.64367 0 8.36436 0.279724 8.36436 0.618784V4.66207C8.36436 5.21304 9.02539 5.48429 9.41474 5.10285L10.4042 4.1111ZM3.59566 7.8889C2.87622 7.10906 2.50465 5.96474 2.69847 4.82041C2.89315 3.51503 3.85804 2.26814 5.20382 1.75192H5.21228C5.62702 1.59002 5.88094 1.15009 5.7709 0.700836C5.66087 0.216829 5.16996 -0.0874774 4.68751 0.022717C2.67308 0.488924 0.912573 2.11641 0.277773 4.21858C-0.0438585 5.26119 -0.0861788 6.39619 0.150813 7.48966C0.353949 8.4314 0.785612 9.32906 1.38656 10.1004L0.540157 10.9481C0.159277 11.338 0.430124 12 0.980284 12H5.01761C5.35617 12 5.63548 11.7203 5.63548 11.3804V7.33793C5.63548 6.78611 4.97529 6.51571 4.58595 6.89715L3.59566 7.8889Z"
|
||||
fill="white" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="50"
|
||||
height="50"
|
||||
viewBox="-2 -2 48 48"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<circle
|
||||
cx="22" cy="22"
|
||||
r="23.5"
|
||||
fill="#4c97ff"
|
||||
opacity="0.25"
|
||||
/>
|
||||
<circle
|
||||
cx="22" cy="22"
|
||||
r="18"
|
||||
fill="#4c97ff"
|
||||
/>
|
||||
<path
|
||||
d="m 25.4042,20.1111 c 0.7194,0.7798 1.0918,1.9233 0.8972,3.0685 -0.1939,1.3054 -1.1588,2.5514 -2.5054,3.0685 h -0.0084 c -0.4148,0.161 -0.6687,0.6018 -0.5587,1.0511 0.1101,0.4831 0.601,0.7883 1.0834,0.6781 2.0145,-0.4662 3.775,-2.0937 4.4098,-4.1959 0.3216,-1.0434 0.3639,-2.1784 0.1278,-3.2719 -0.2032,-0.9409 -0.6348,-1.8394 -1.2358,-2.6108 l 0.8464,-0.8476 C 28.8406,16.6612 28.5706,16 28.0196,16 h -4.0365 c -0.3394,0 -0.6187,0.2797 -0.6187,0.6188 v 4.0433 c 0,0.5509 0.661,0.8222 1.0503,0.4407 z m -6.8085,3.7778 c -0.7195,-0.7798 -1.091,-1.9242 -0.8972,-3.0685 0.1946,-1.3054 1.1595,-2.5523 2.5053,-3.0685 h 0.0085 c 0.4147,-0.1619 0.6686,-0.6018 0.5586,-1.0511 -0.11,-0.484 -0.6009,-0.7883 -1.0834,-0.6781 -2.0144,0.4662 -3.7749,2.0937 -4.4097,4.1959 -0.3217,1.0426 -0.364,2.1776 -0.127,3.2711 0.2031,0.9417 0.6348,1.8394 1.2358,2.6107 l -0.8464,0.8477 C 15.1593,27.338 15.4301,28 15.9803,28 h 4.0373 c 0.3386,0 0.6179,-0.2797 0.6179,-0.6196 v -4.0425 c 0,-0.5518 -0.6602,-0.8222 -1.0496,-0.4407 z"
|
||||
fill="#ffffff"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,26 @@
|
||||
<svg width="44" height="44" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_d_2828_26434)">
|
||||
<rect x="4" y="4" width="36" height="36" rx="18" fill="#FF8C1A" />
|
||||
<circle cx="22" cy="22" r="10" fill="#FF8C1A" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M22 23.4245C21.1974 23.4245 20.5477 24.0644 20.5477 24.8549C20.5477 25.6453 21.1974 26.2853 22 26.2853C22.8025 26.2853 23.4523 25.6453 23.4523 24.8549C23.4523 24.0644 22.8025 23.4245 22 23.4245ZM22.8337 22.1212C22.4643 22.936 21.5357 22.936 21.1663 22.1212L19.9869 19.5345C19.6176 18.7253 20.0804 17.7139 20.8206 17.7139H23.1793C23.9196 17.7139 24.3824 18.7253 24.013 19.5345L22.8337 22.1212Z"
|
||||
fill="white" />
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d_2828_26434" x="0" y="0" width="44" height="44"
|
||||
filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix in="SourceAlpha" type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" />
|
||||
<feMorphology radius="4" operator="dilate" in="SourceAlpha"
|
||||
result="effect1_dropShadow_2828_26434" />
|
||||
<feOffset />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix type="matrix"
|
||||
values="0 0 0 0 1 0 0 0 0 0.54902 0 0 0 0 0.101961 0 0 0 0.15 0" />
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_2828_26434" />
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_2828_26434"
|
||||
result="shape" />
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -7,13 +7,16 @@ import Box from '../box/box.jsx';
|
||||
import PeripheralTile from './peripheral-tile.jsx';
|
||||
import Dots from './dots.jsx';
|
||||
|
||||
import enterUpdateIcon from './icons/enter-update.svg';
|
||||
import radarIcon from './icons/searching.png';
|
||||
import refreshIcon from './icons/refresh.svg';
|
||||
import warningIcon from './icons/warning.svg';
|
||||
|
||||
import styles from './connection-modal.css';
|
||||
|
||||
const ScanningStep = props => (
|
||||
<Box className={styles.body}>
|
||||
const ScanningStep = props => {
|
||||
const showUpdate = !!(props.onUpdatePeripheral && !props.scanning);
|
||||
return (<Box className={styles.body}>
|
||||
<Box className={styles.activityArea}>
|
||||
{props.scanning ? (
|
||||
props.peripheralList.length === 0 ? (
|
||||
@@ -45,64 +48,81 @@ const ScanningStep = props => (
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<Box className={styles.instructions}>
|
||||
<Box className={styles.centeredRow}>
|
||||
<img
|
||||
className={styles.helpStepImage}
|
||||
src={warningIcon}
|
||||
/>
|
||||
<FormattedMessage
|
||||
className={styles.helpStepText}
|
||||
defaultMessage="No devices found"
|
||||
description="Text shown when no devices could be found"
|
||||
id="gui.connection.scanning.noPeripheralsFound"
|
||||
/>
|
||||
{
|
||||
props.onUpdatePeripheral && <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.onUpdatePeripheral}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Update my Device"
|
||||
description="Button in prompt for starting a search"
|
||||
id="gui.connection.scanning.updateFirmwareButton"
|
||||
/>
|
||||
</button>
|
||||
</p>
|
||||
}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
<Box className={styles.bottomArea}>
|
||||
<Box className={classNames(styles.bottomAreaItem, styles.instructions)}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Select your device in the list above."
|
||||
description="Prompt for choosing a device to connect to"
|
||||
id="gui.connection.scanning.instructions"
|
||||
/>
|
||||
{(props.scanning || props.peripheralList.length > 0) && (
|
||||
// Show this message if we're still scanning OR if we've found devices
|
||||
<FormattedMessage
|
||||
defaultMessage="Select your device in the list above."
|
||||
description="Prompt for choosing a device to connect to"
|
||||
id="gui.connection.scanning.instructions"
|
||||
/>
|
||||
)}
|
||||
{showUpdate && (
|
||||
// Show this message if we're done scanning AND we can update
|
||||
// Note that it's possible the list includes devices but does not include the desired device,
|
||||
// so don't limit this message to the (props.peripheralList.length === 0) case
|
||||
<FormattedMessage
|
||||
defaultMessage="If you don't see your device, you may need to update it to work with Scratch."
|
||||
description="Prompt for updating a peripheral device"
|
||||
id="gui.connection.scanning.updatePeripheralPrompt"
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
<Dots
|
||||
className={styles.bottomAreaItem}
|
||||
counter={0}
|
||||
total={3}
|
||||
/>
|
||||
<button
|
||||
className={classNames(styles.bottomAreaItem, styles.connectionButton)}
|
||||
onClick={props.onRefresh}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Refresh"
|
||||
description="Button in prompt for starting a search"
|
||||
id="gui.connection.search"
|
||||
/>
|
||||
<img
|
||||
className={styles.buttonIconRight}
|
||||
src={refreshIcon}
|
||||
/>
|
||||
</button>
|
||||
<Box className={classNames(styles.bottomAreaItem, styles.buttonRow)}>
|
||||
<button
|
||||
className={styles.connectionButton}
|
||||
onClick={props.onRefresh}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Refresh"
|
||||
description="Button in prompt for starting a search"
|
||||
id="gui.connection.search"
|
||||
/>
|
||||
<img
|
||||
className={styles.buttonIconRight}
|
||||
src={refreshIcon}
|
||||
/>
|
||||
</button>
|
||||
{showUpdate && (
|
||||
<button
|
||||
className={styles.connectionButton}
|
||||
onClick={props.onUpdatePeripheral}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Update my Device"
|
||||
description="Button to enter the peripheral update mode"
|
||||
id="gui.connection.scanning.updatePeripheralButton"
|
||||
/>
|
||||
<img
|
||||
className={styles.buttonIconRight}
|
||||
src={enterUpdateIcon}
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
</Box>);
|
||||
};
|
||||
|
||||
ScanningStep.propTypes = {
|
||||
connectionSmallIconURL: PropTypes.string,
|
||||
|
||||
@@ -7,10 +7,12 @@ 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 sendUpdateIcon from './icons/send-update.svg';
|
||||
import sendUpdateGlyph from './icons/send-update-white.svg';
|
||||
|
||||
import styles from './connection-modal.css';
|
||||
import ProgressRingComponent from '../progress-ring/progress-ring.jsx';
|
||||
|
||||
/** @enum{string} UPDATE_ACTIVITY */
|
||||
const UPDATE_ACTIVITY = keyMirror({
|
||||
@@ -32,7 +34,7 @@ class UpdatePeripheralStep extends React.Component {
|
||||
activity: UPDATE_ACTIVITY.getReady,
|
||||
|
||||
/** @type {number} */
|
||||
progress: 0,
|
||||
progressPercentage: 0,
|
||||
|
||||
/** @type {Error?} */
|
||||
err: null,
|
||||
@@ -51,7 +53,10 @@ class UpdatePeripheralStep extends React.Component {
|
||||
});
|
||||
try {
|
||||
const res = await this.props.onSendPeripheralUpdate(progress => {
|
||||
this.setState({progress});
|
||||
// On my computer, I get a progress update every 0.005% or so.
|
||||
// Rendering the progress ring is a little expensive, so filtering updates here reduces the CPU load.
|
||||
// Updating every 1% doesn't look very smooth, but 0.5% (1/200) looks good to me.
|
||||
this.setState({progressPercentage: Math.floor(progress * 200) / 2});
|
||||
});
|
||||
this.setState({
|
||||
activity: UPDATE_ACTIVITY.results,
|
||||
@@ -67,49 +72,61 @@ class UpdatePeripheralStep extends React.Component {
|
||||
|
||||
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 peripheral update process"
|
||||
id="gui.connection.updatePeripheral.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 update process"
|
||||
id="gui.connection.updatePeripheral.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 peripheral update process"
|
||||
id="gui.connection.updatePeripheral.doNotDisconnect"
|
||||
values={{
|
||||
extensionName: this.props.name
|
||||
}}
|
||||
/>
|
||||
<Box className={styles.scratchLinkHelp}>
|
||||
<Box className={styles.scratchLinkHelpStep}>
|
||||
<Box className={styles.helpStepNumber}>
|
||||
{'1'}
|
||||
</Box>
|
||||
<img
|
||||
className={styles.helpStepImage}
|
||||
src={this.props.connectionSmallIconURL}
|
||||
/>
|
||||
{
|
||||
// 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
|
||||
className={styles.helpStepText}
|
||||
defaultMessage="Connect your {extensionName} to this device using a USB cable."
|
||||
description="Instructions to connect the micro:bit to the computer for the update process"
|
||||
id="gui.connection.updatePeripheral.microBitConnect"
|
||||
values={{
|
||||
extensionName: this.props.name
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box className={styles.scratchLinkHelpStep}>
|
||||
<Box className={styles.helpStepNumber}>
|
||||
{'2'}
|
||||
</Box>
|
||||
<img
|
||||
className={styles.helpStepImage}
|
||||
src={sendUpdateIcon}
|
||||
/>
|
||||
<FormattedMessage
|
||||
defaultMessage="Press "Do Update" and allow the update to complete."
|
||||
description="Instructions to press the button to begin the update process"
|
||||
id="gui.connection.updatePeripheral.pressUpdate"
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>);
|
||||
}
|
||||
|
||||
renderSendUpdate () {
|
||||
return (<Box className={styles.activityArea}>
|
||||
<p>{'Sending update...'}</p>
|
||||
<progress
|
||||
max="1"
|
||||
value={this.state.progress}
|
||||
<ProgressRingComponent
|
||||
sizePx={36}
|
||||
value={this.state.progressPercentage}
|
||||
max={100}
|
||||
/>
|
||||
<FormattedMessage
|
||||
defaultMessage="Updating {progressPercentage}%"
|
||||
description="Progress message while updating the peripheral"
|
||||
id="gui.connection.updatePeripheral.progress"
|
||||
values={{
|
||||
progressPercentage: Math.floor(this.state.progressPercentage)
|
||||
}}
|
||||
/>
|
||||
</Box>);
|
||||
}
|
||||
@@ -139,14 +156,22 @@ class UpdatePeripheralStep extends React.Component {
|
||||
}}
|
||||
/>);
|
||||
} else {
|
||||
resultsContent = (<FormattedMessage
|
||||
defaultMessage="Update failed. Error: {errorMessage}"
|
||||
description="Message to indicate that the peripheral update failed"
|
||||
id="gui.connection.updatePeripheral.updateFailed"
|
||||
values={{
|
||||
errorMessage: this.state.err.message
|
||||
}}
|
||||
/>);
|
||||
resultsContent = (
|
||||
<Box className={styles.scratchLinkError}>
|
||||
<FormattedMessage
|
||||
className={styles.centeredRow}
|
||||
defaultMessage="Update failed."
|
||||
description="Message to indicate that the peripheral update failed"
|
||||
id="gui.connection.updatePeripheral.updateFailed"
|
||||
/>
|
||||
<textarea
|
||||
className={styles.scratchLinkErrorDetails}
|
||||
readOnly
|
||||
>
|
||||
{this.state.err.message}
|
||||
</textarea>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
return (<Box className={styles.activityArea}>
|
||||
{resultsContent}
|
||||
@@ -154,44 +179,71 @@ class UpdatePeripheralStep extends React.Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const showGetReady = this.state.activity === UPDATE_ACTIVITY.getReady;
|
||||
const showSendUpdate = this.state.activity === UPDATE_ACTIVITY.sendUpdate;
|
||||
const showResults = this.state.activity === UPDATE_ACTIVITY.results;
|
||||
const showBadResults = showResults && !!this.state.err;
|
||||
return (
|
||||
<Box className={styles.body}>
|
||||
{(this.state.activity === UPDATE_ACTIVITY.getReady) && this.renderGetReady()}
|
||||
{(this.state.activity === UPDATE_ACTIVITY.sendUpdate) && this.renderSendUpdate()}
|
||||
{(this.state.activity === UPDATE_ACTIVITY.results) && this.renderResults()}
|
||||
{showGetReady && this.renderGetReady()}
|
||||
{showSendUpdate && this.renderSendUpdate()}
|
||||
{showResults && 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.sendUpdate}
|
||||
>
|
||||
<img
|
||||
className={classNames(styles.buttonIconLeft, styles.buttonIconBack)}
|
||||
src={backIcon}
|
||||
/>
|
||||
<FormattedMessage
|
||||
defaultMessage="Go back"
|
||||
description="Button to leave the peripheral update process"
|
||||
id="gui.connection.updatePeripheral.goBackButton"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
className={styles.connectionButton}
|
||||
onClick={this.handleSendUpdate}
|
||||
disabled={this.state.activity !== UPDATE_ACTIVITY.getReady}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Update now"
|
||||
description="Button to start the peripheral update"
|
||||
id="gui.connection.updatePeripheral.updateNowButton"
|
||||
/>
|
||||
</button>
|
||||
</Box>
|
||||
{!showResults &&
|
||||
<FormattedMessage
|
||||
className={styles.bottomAreaItem}
|
||||
defaultMessage={'Do not leave or reload Scratch or disconnect your {extensionName} ' +
|
||||
'until the update is complete.'}
|
||||
description="Notice to not disrupt the peripheral update process"
|
||||
id="gui.connection.updatePeripheral.doNotDisconnect"
|
||||
values={{
|
||||
extensionName: this.props.name
|
||||
}}
|
||||
/>
|
||||
}
|
||||
{!showSendUpdate &&
|
||||
<Box className={classNames(styles.bottomAreaItem, styles.buttonRow)}>
|
||||
<button
|
||||
className={styles.connectionButton}
|
||||
onClick={this.props.onScanning}
|
||||
>
|
||||
<img
|
||||
className={classNames(styles.buttonIconLeft, styles.buttonIconBack)}
|
||||
src={backIcon}
|
||||
/>
|
||||
<FormattedMessage
|
||||
defaultMessage="Go Back"
|
||||
description="Button to leave the peripheral update process"
|
||||
id="gui.connection.updatePeripheral.goBackButton"
|
||||
/>
|
||||
</button>
|
||||
{(showGetReady || showBadResults) &&
|
||||
<button
|
||||
className={styles.connectionButton}
|
||||
onClick={this.handleSendUpdate}
|
||||
>
|
||||
{showGetReady &&
|
||||
<FormattedMessage
|
||||
defaultMessage="Do Update"
|
||||
description="Button to start the peripheral update"
|
||||
id="gui.connection.updatePeripheral.updateNowButton"
|
||||
/>
|
||||
}
|
||||
{showBadResults &&
|
||||
<FormattedMessage
|
||||
defaultMessage="Try Again"
|
||||
description="Button to try the peripheral update again"
|
||||
id="gui.connection.updatePeripheral.updateAgainButton"
|
||||
/>
|
||||
}
|
||||
<img
|
||||
className={styles.buttonIconRight}
|
||||
src={sendUpdateGlyph}
|
||||
/>
|
||||
</button>
|
||||
}
|
||||
</Box>
|
||||
}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
@@ -199,6 +251,7 @@ class UpdatePeripheralStep extends React.Component {
|
||||
}
|
||||
|
||||
UpdatePeripheralStep.propTypes = {
|
||||
connectionSmallIconURL: PropTypes.string,
|
||||
name: PropTypes.string.isRequired,
|
||||
onScanning: PropTypes.func.isRequired,
|
||||
onSendPeripheralUpdate: PropTypes.func.isRequired
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
@import "../../css/colors.css";
|
||||
|
||||
.progress-ring {
|
||||
margin: 0.5rem;
|
||||
}
|
||||
|
||||
.progress-ring-svg {
|
||||
fill: transparent;
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ const ProgressRingComponent = ({
|
||||
{...props}
|
||||
>
|
||||
<svg
|
||||
className={classNames(styles.progressRing, className)}
|
||||
className={classNames(styles.progressRingSvg, className)}
|
||||
viewBox={`0 0 ${sizePx} ${sizePx}`}
|
||||
>
|
||||
<circle
|
||||
|
||||
@@ -72,6 +72,7 @@ class AutoScanningStep extends React.Component {
|
||||
title={this.props.extensionId}
|
||||
onRefresh={this.handleRefresh}
|
||||
onStartScan={this.handleStartScan}
|
||||
onUpdatePeripheral={this.props.onUpdatePeripheral}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -81,6 +82,7 @@ AutoScanningStep.propTypes = {
|
||||
connectionTipIconURL: PropTypes.string,
|
||||
extensionId: PropTypes.string.isRequired,
|
||||
onConnecting: PropTypes.func.isRequired,
|
||||
onUpdatePeripheral: PropTypes.func,
|
||||
vm: PropTypes.instanceOf(VM).isRequired
|
||||
};
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ class ConnectionModal extends React.Component {
|
||||
}
|
||||
handleUpdatePeripheral () {
|
||||
this.setState({
|
||||
phase: PHASES.updateFirmware
|
||||
phase: PHASES.updatePeripheral
|
||||
});
|
||||
analytics.event({
|
||||
category: 'extensions',
|
||||
|
||||
Reference in new issue
Block a user