Skip to content
Snippets Groups Projects
Unverified Commit 976e3b9b authored by Eric Rosenbaum's avatar Eric Rosenbaum Committed by GitHub
Browse files

Merge pull request #2772 from ericrosenbaum/feature/wedo-steps

Add device connection steps for Lego WeDo 2.0
parents 05973cca b2a308e5
No related branches found
No related tags found
No related merge requests found
Showing
with 299 additions and 14 deletions
import {FormattedMessage} from 'react-intl';
import PropTypes from 'prop-types';
import React from 'react';
import keyMirror from 'keymirror';
import classNames from 'classnames';
import Box from '../box/box.jsx';
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 styles from './connection-modal.css';
const PHASES = keyMirror({
prescan: null,
pressbutton: null,
notfound: null
});
const AutoScanningStep = props => (
<Box className={styles.body}>
<Box className={styles.activityArea}>
<div className={styles.activityAreaInfo}>
<div className={styles.centeredRow}>
{props.phase === PHASES.prescan && (
<React.Fragment>
<img
className={styles.radarBig}
src={radarIcon}
/>
<img
className={styles.bluetoothCenteredIcon}
src={bluetoothIcon}
/>
</React.Fragment>
)}
{props.phase === PHASES.pressbutton && (
<React.Fragment>
<img
className={classNames(styles.radarBig, styles.radarSpin)}
src={radarIcon}
/>
<img
className={styles.deviceButtonImage}
src={props.deviceButtonImage}
/>
</React.Fragment>
)}
{props.phase === PHASES.notfound && (
<Box className={styles.instructions}>
<FormattedMessage
defaultMessage="No devices found"
description="Text shown when no devices could be found"
id="gui.connection.auto-scanning.noDevicesFound"
/>
</Box>
)}
</div>
</div>
</Box>
<Box className={styles.bottomArea}>
<Box className={styles.instructions}>
{props.phase === PHASES.prescan && (
<FormattedMessage
defaultMessage="Have your device nearby, then begin searching."
description="Prompt for beginning the search"
id="gui.connection.auto-scanning.prescan"
/>
)}
{props.phase === PHASES.pressbutton && (
<FormattedMessage
defaultMessage="Press the button on your device."
description="Prompt for pushing the button on the device"
id="gui.connection.auto-scanning.pressbutton"
/>
)}
</Box>
<Dots
counter={0}
total={3}
/>
{props.phase === PHASES.prescan && (
<button
className={styles.connectionButton}
onClick={props.onStartScan}
>
<FormattedMessage
defaultMessage="Start Searching"
description="Button in prompt for starting a search"
id="gui.connection.auto-scanning.start-search"
/>
</button>
)}
{props.phase === PHASES.pressbutton && (
<div className={styles.segmentedButton}>
<button
disabled
className={styles.connectionButton}
>
<FormattedMessage
defaultMessage="Searching..."
description="Label indicating that search is in progress"
id="gui.connection.connecting-searchbutton"
/>
</button>
<button
className={styles.connectionButton}
onClick={props.onRefresh}
>
<img
className={styles.abortConnectingIcon}
src={closeIcon}
/>
</button>
</div>
)}
{props.phase === PHASES.notfound && (
<button
className={styles.connectionButton}
onClick={props.onRefresh}
>
<img
className={styles.buttonIconLeft}
src={backIcon}
/>
<FormattedMessage
defaultMessage="Try again"
description="Button in prompt for trying a device search again"
id="gui.connection.auto-scanning.try-again"
/>
</button>
)}
</Box>
</Box>
);
AutoScanningStep.propTypes = {
deviceButtonImage: PropTypes.string,
onRefresh: PropTypes.func,
onStartScan: PropTypes.func,
phase: PropTypes.oneOf(Object.keys(PHASES))
};
AutoScanningStep.defaultProps = {
phase: PHASES.prescan
};
export {
AutoScanningStep as default,
PHASES
};
......@@ -118,9 +118,18 @@
background-color: $pen-primary;
}
.radar {
.radar-small {
width: 40px;
height: 40px;
margin-right: 0.5rem;
}
.radar-big {
width: 120px;
height: 120px;
}
.radar-spin {
animation: spin 4s linear infinite;
}
......@@ -147,6 +156,10 @@
height: 80px; */
}
.device-button-image {
position: absolute;
}
.bluetooth-connecting-icon {
position: absolute;
top: -5px;
......@@ -167,7 +180,6 @@
}
}
.bluetooth-connected-icon {
position: absolute;
top: -5px;
......@@ -178,8 +190,6 @@
box-shadow: 0px 0px 0px 4px $pen-transparent;
}
@keyframes wiggle {
0% {transform: rotate(3deg) scale(1.05);}
25% {transform: rotate(-3deg) scale(1.05);}
......@@ -188,6 +198,14 @@
100% {transform: rotate(0deg) scale(1.05);}
}
.bluetooth-centered-icon {
position: absolute;
padding: 5px 5px;
background-color: $motion-primary;
border-radius: 100%;
box-shadow: 0px 0px 0px 2px $motion-transparent;
}
.device-tile-widgets {
display: flex;
align-items: center;
......
......@@ -6,6 +6,7 @@ import Box from '../box/box.jsx';
import Modal from '../../containers/modal.jsx';
import ScanningStep from '../../containers/scanning-step.jsx';
import AutoScanningStep from '../../containers/auto-scanning-step.jsx';
import ConnectingStep from './connecting-step.jsx';
import ConnectedStep from './connected-step.jsx';
import ErrorStep from './error-step.jsx';
......@@ -31,7 +32,8 @@ const ConnectionModalComponent = props => (
onRequestClose={props.onCancel}
>
<Box className={styles.body}>
{props.phase === PHASES.scanning && <ScanningStep {...props} />}
{props.phase === PHASES.scanning && !props.useAutoScan && <ScanningStep {...props} />}
{props.phase === PHASES.scanning && props.useAutoScan && <AutoScanningStep {...props} />}
{props.phase === PHASES.connecting && <ConnectingStep {...props} />}
{props.phase === PHASES.connected && <ConnectedStep {...props} />}
{props.phase === PHASES.error && <ErrorStep {...props} />}
......@@ -42,12 +44,14 @@ const ConnectionModalComponent = props => (
ConnectionModalComponent.propTypes = {
connectingMessage: PropTypes.node,
deviceButtonImage: PropTypes.string,
name: PropTypes.node,
onCancel: PropTypes.func.isRequired,
onHelp: PropTypes.func.isRequired,
phase: PropTypes.oneOf(Object.keys(PHASES)).isRequired,
smallDeviceImage: PropTypes.string,
title: PropTypes.string.isRequired
title: PropTypes.string.isRequired,
useAutoScan: PropTypes.bool.isRequired
};
export {
......
src/components/connection-modal/icons/searching.png

41.4 KiB | W: | H:

src/components/connection-modal/icons/searching.png

41.6 KiB | W: | H:

src/components/connection-modal/icons/searching.png
src/components/connection-modal/icons/searching.png
src/components/connection-modal/icons/searching.png
src/components/connection-modal/icons/searching.png
  • 2-up
  • Swipe
  • Onion skin
import {FormattedMessage} from 'react-intl';
import PropTypes from 'prop-types';
import React from 'react';
import classNames from 'classnames';
import Box from '../box/box.jsx';
import DeviceTile from './device-tile.jsx';
......@@ -19,7 +20,7 @@ const ScanningStep = props => (
<div className={styles.activityAreaInfo}>
<div className={styles.centeredRow}>
<img
className={styles.radar}
className={classNames(styles.radarSmall, styles.radarSpin)}
src={radarIcon}
/>
<FormattedMessage
......
import PropTypes from 'prop-types';
import React from 'react';
import bindAll from 'lodash.bindall';
import ScanningStepComponent, {PHASES} from '../components/connection-modal/auto-scanning-step.jsx';
import VM from 'scratch-vm';
class AutoScanningStep extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handlePeripheralListUpdate',
'handlePeripheralScanTimeout',
'handleStartScan',
'handleRefresh'
]);
this.state = {
phase: PHASES.prescan
};
}
componentWillUnmount () {
// @todo: stop the device scan here
this.unbindPeripheralUpdates();
}
handlePeripheralScanTimeout () {
this.setState({
phase: PHASES.notfound
});
this.unbindPeripheralUpdates();
}
handlePeripheralListUpdate (newList) {
// TODO: sort peripherals by signal strength? so they don't jump around
const peripheralArray = Object.keys(newList).map(id =>
newList[id]
);
if (peripheralArray.length > 0) {
this.props.onConnecting(peripheralArray[0].peripheralId);
}
}
bindPeripheralUpdates () {
this.props.vm.on(
'PERIPHERAL_LIST_UPDATE', this.handlePeripheralListUpdate);
this.props.vm.on(
'PERIPHERAL_SCAN_TIMEOUT', this.handlePeripheralScanTimeout);
}
unbindPeripheralUpdates () {
this.props.vm.removeListener(
'PERIPHERAL_LIST_UPDATE', this.handlePeripheralListUpdate);
this.props.vm.removeListener(
'PERIPHERAL_SCAN_TIMEOUT', this.handlePeripheralScanTimeout);
}
handleRefresh () {
// @todo: stop the device scan here, it is more important for auto scan
// due to timeout and cancellation
this.setState({
phase: PHASES.prescan
});
this.unbindPeripheralUpdates();
}
handleStartScan () {
this.bindPeripheralUpdates();
this.props.vm.startDeviceScan(this.props.extensionId);
this.setState({
phase: PHASES.pressbutton
});
}
render () {
return (
<ScanningStepComponent
deviceButtonImage={this.props.deviceButtonImage}
phase={this.state.phase}
title={this.props.extensionId}
onRefresh={this.handleRefresh}
onStartScan={this.handleStartScan}
/>
);
}
}
AutoScanningStep.propTypes = {
deviceButtonImage: PropTypes.string,
extensionId: PropTypes.string.isRequired,
onConnecting: PropTypes.func.isRequired,
vm: PropTypes.instanceOf(VM).isRequired
};
export default AutoScanningStep;
......@@ -364,8 +364,10 @@ class Blocks extends React.Component {
if (extension) {
this.setState({connectionModal: {
extensionId: extensionId,
useAutoScan: extension.useAutoScan,
deviceImage: extension.deviceImage,
smallDeviceImage: extension.smallDeviceImage,
deviceButtonImage: extension.deviceButtonImage,
name: extension.name,
connectingMessage: extension.connectingMessage,
helpLink: extension.helpLink
......@@ -433,12 +435,7 @@ class Blocks extends React.Component {
) : null}
{this.state.connectionModal ? (
<ConnectionModal
connectingMessage={this.state.connectionModal.connectingMessage}
deviceImage={this.state.connectionModal.deviceImage}
extensionId={this.state.connectionModal.extensionId}
helpLink={this.state.connectionModal.helpLink}
name={this.state.connectionModal.name}
smallDeviceImage={this.state.connectionModal.smallDeviceImage}
{...this.state.connectionModal}
vm={vm}
onCancel={this.handleConnectionModalClose}
onStatusButtonUpdate={this.handleStatusButtonUpdate}
......
......@@ -99,12 +99,14 @@ class ConnectionModal extends React.Component {
return (
<ConnectionModalComponent
connectingMessage={this.props.connectingMessage}
deviceButtonImage={this.props.deviceButtonImage}
deviceImage={this.props.deviceImage}
extensionId={this.props.extensionId}
name={this.props.name}
phase={this.state.phase}
smallDeviceImage={this.props.smallDeviceImage}
title={this.props.extensionId}
useAutoScan={this.props.useAutoScan}
vm={this.props.vm}
onCancel={this.props.onCancel}
onConnected={this.handleConnected}
......@@ -119,6 +121,7 @@ class ConnectionModal extends React.Component {
ConnectionModal.propTypes = {
connectingMessage: PropTypes.node.isRequired,
deviceButtonImage: PropTypes.string,
deviceImage: PropTypes.string.isRequired,
extensionId: PropTypes.string.isRequired,
helpLink: PropTypes.string.isRequired,
......@@ -126,6 +129,7 @@ ConnectionModal.propTypes = {
onCancel: PropTypes.func.isRequired,
onStatusButtonUpdate: PropTypes.func.isRequired,
smallDeviceImage: PropTypes.string.isRequired,
useAutoScan: PropTypes.bool.isRequired,
vm: PropTypes.instanceOf(VM).isRequired
};
......
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
......@@ -13,6 +13,9 @@ import microbitDeviceImage from './device-connection/microbit/microbit-illustrat
import microbitMenuImage from './device-connection/microbit/microbit-small.svg';
import ev3DeviceImage from './device-connection/ev3/ev3-hub-illustration.svg';
import ev3MenuImage from './device-connection/ev3/ev3-small.svg';
import wedoDeviceImage from './device-connection/wedo/wedo-illustration.svg';
import wedoMenuImage from './device-connection/wedo/wedo-small.svg';
import wedoButtonImage from './device-connection/wedo/wedo-button-illustration.svg';
export default [
{
......@@ -105,6 +108,7 @@ export default [
featured: true,
disabled: false,
launchDeviceConnectionFlow: true,
useAutoScan: false,
deviceImage: microbitDeviceImage,
smallDeviceImage: microbitMenuImage,
connectingMessage: (
......@@ -130,6 +134,7 @@ export default [
featured: true,
disabled: false,
launchDeviceConnectionFlow: true,
useAutoScan: false,
deviceImage: ev3DeviceImage,
smallDeviceImage: ev3MenuImage,
connectingMessage: (
......@@ -153,6 +158,20 @@ export default [
/>
),
featured: true,
disabled: true
disabled: true,
launchDeviceConnectionFlow: true,
useAutoScan: true,
deviceImage: wedoDeviceImage,
smallDeviceImage: wedoMenuImage,
deviceButtonImage: wedoButtonImage,
connectingMessage: (
<FormattedMessage
defaultMessage="Connecting"
description="Message to help people connect to their WeDo."
id="gui.extension.wedo2.connectingMessage"
/>
),
helpLink: 'https://scratch.mit.edu/wedo'
}
];
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment