Merge pull request #633 from paulkaplan/turbo-mode
Implement turbo mode basic indicator
This commit is contained in:
commit
e78ef1cae3
17 files changed
+264
-75
No files matched your search
@@ -0,0 +1,3 @@
|
||||
.controls-container {
|
||||
display: flex;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
import GreenFlag from '../green-flag/green-flag.jsx';
|
||||
import StopAll from '../stop-all/stop-all.jsx';
|
||||
import TurboMode from '../turbo-mode/turbo-mode.jsx';
|
||||
|
||||
import styles from './controls.css';
|
||||
|
||||
const Controls = function (props) {
|
||||
const {
|
||||
active,
|
||||
className,
|
||||
onGreenFlagClick,
|
||||
onStopAllClick,
|
||||
turbo,
|
||||
...componentProps
|
||||
} = props;
|
||||
return (
|
||||
<div
|
||||
className={classNames(styles.controlsContainer, className)}
|
||||
{...componentProps}
|
||||
>
|
||||
<GreenFlag
|
||||
active={active}
|
||||
onClick={onGreenFlagClick}
|
||||
/>
|
||||
<StopAll
|
||||
active={active}
|
||||
onClick={onStopAllClick}
|
||||
/>
|
||||
{turbo ? (
|
||||
<TurboMode />
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Controls.propTypes = {
|
||||
active: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
onGreenFlagClick: PropTypes.func.isRequired,
|
||||
onStopAllClick: PropTypes.func.isRequired,
|
||||
turbo: PropTypes.bool
|
||||
};
|
||||
|
||||
Controls.defaultProps = {
|
||||
active: false,
|
||||
turbo: false
|
||||
};
|
||||
|
||||
export default Controls;
|
||||
@@ -1,13 +1,21 @@
|
||||
.green-flag {
|
||||
width: 1.1rem;
|
||||
height: 1.1rem;
|
||||
opacity: 0.5;
|
||||
box-sizing: content-box;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
padding: 0.375rem;
|
||||
border-radius: 0.25rem;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.2s ease-out; /* @todo: standardize with var */
|
||||
transition: 0.2s ease-out;
|
||||
}
|
||||
|
||||
.green-flag.is-active,
|
||||
.green-flag:hover {
|
||||
opacity: 1;
|
||||
/* Scale flag image by 1.2, but keep background static */
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
padding: 0.25rem;
|
||||
}
|
||||
|
||||
.green-flag.is-active {
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 13.8 14.5" style="enable-background:new 0 0 13.8 14.5;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#55BA58;stroke:#469945;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st1{fill:#55BA58;stroke:#469945;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
</style>
|
||||
<g>
|
||||
<path class="st0" d="M0.8,1.8c1.7-1.4,4.5-1.4,6.2,0l0,0c1.7,1.4,4.5,1.4,6.2,0v8.4c-1.7,1.4-4.5,1.4-6.2,0l0,0
|
||||
c-1.7-1.4-4.5-1.4-6.2,0"/>
|
||||
<line class="st1" x1="0.8" y1="13.8" x2="0.8" y2="0.8"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 816 B |
@@ -101,10 +101,6 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.green-flag {
|
||||
margin: 0.25rem 0.6rem;
|
||||
}
|
||||
|
||||
.stage-and-target-wrapper {
|
||||
/*
|
||||
Makes rows for children:
|
||||
@@ -130,7 +126,7 @@
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-basis: 0;
|
||||
|
||||
|
||||
padding-top: $space;
|
||||
padding-left: $space;
|
||||
padding-right: $space;
|
||||
|
||||
@@ -8,11 +8,10 @@ import VM from 'scratch-vm';
|
||||
|
||||
import Blocks from '../../containers/blocks.jsx';
|
||||
import CostumeTab from '../../containers/costume-tab.jsx';
|
||||
import GreenFlag from '../../containers/green-flag.jsx';
|
||||
import Controls from '../../containers/controls.jsx';
|
||||
import TargetPane from '../../containers/target-pane.jsx';
|
||||
import SoundTab from '../../containers/sound-tab.jsx';
|
||||
import Stage from '../../containers/stage.jsx';
|
||||
import StopAll from '../../containers/stop-all.jsx';
|
||||
|
||||
import Box from '../box/box.jsx';
|
||||
import MenuBar from '../menu-bar/menu-bar.jsx';
|
||||
@@ -90,11 +89,7 @@ const GUIComponent = props => {
|
||||
|
||||
<Box className={styles.stageAndTargetWrapper}>
|
||||
<Box className={styles.stageMenuWrapper}>
|
||||
<GreenFlag
|
||||
className={styles.greenFlag}
|
||||
vm={vm}
|
||||
/>
|
||||
<StopAll vm={vm} />
|
||||
<Controls vm={vm} />
|
||||
</Box>
|
||||
<Box className={styles.stageWrapper}>
|
||||
<MediaQuery minWidth={layout.fullSizeMinWidth}>{isFullSize => (
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
.stop-all {
|
||||
width: 1.1rem;
|
||||
height: 1.1rem;
|
||||
opacity: 0.5;
|
||||
box-sizing: content-box;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
padding: 0.375rem;
|
||||
border-radius: 0.25rem;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.2s ease-out; /* @todo: standardize with var */
|
||||
transition: 0.2s ease-out;
|
||||
}
|
||||
|
||||
.stop-all {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.stop-all.is-active,
|
||||
.stop-all:hover {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.stop-all.is-active {
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>turbo-bolt-icon</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="turbo-bolt-icon" stroke="#FF8C1A" fill="#FF8C1A">
|
||||
<path d="M6.1919265,9.97041267 C6.39817578,10.4827993 6.8451296,10.5515418 7.19456044,10.1185825 L7.4614448,9.78790167 C7.80893496,9.35734694 8.43422564,9.28545516 8.87346661,9.63974521 L11.68656,11.9087754 C12.118908,12.2575055 12.3030858,12.1265903 12.0962705,11.6122341 L9.74921575,5.77503453 C9.54314462,5.26252912 9.09584472,5.19401738 8.7458012,5.62738515 L8.4784489,5.9583779 C8.13034947,6.38933881 7.50456486,6.46155987 7.06532389,6.10726982 L4.25223048,3.83823967 C3.81988252,3.48950949 3.63584851,3.62032891 3.84284261,4.13456591 L6.1919265,9.97041267 Z" id="turbo-bolt" transform="translate(7.969395, 7.873508) rotate(69.000000) translate(-7.969395, -7.873508) "></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,20 @@
|
||||
@import "../../css/colors.css";
|
||||
|
||||
.turbo-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.25rem;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.turbo-icon {
|
||||
margin: 0.25rem;
|
||||
}
|
||||
|
||||
.turbo-label {
|
||||
font-size: 0.625rem;
|
||||
font-weight: bold;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
color: $control-primary;
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
import turboIcon from './icon--turbo.svg';
|
||||
|
||||
import styles from './turbo-mode.css';
|
||||
|
||||
const TurboMode = () => (
|
||||
<div className={styles.turboContainer}>
|
||||
<img
|
||||
className={styles.turboIcon}
|
||||
src={turboIcon}
|
||||
/>
|
||||
<div className={styles.turboLabel}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Turbo Mode"
|
||||
description="Label indicating turbo mode is active"
|
||||
id="controls.turboMode"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default TurboMode;
|
||||
@@ -0,0 +1,70 @@
|
||||
import bindAll from 'lodash.bindall';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import VM from 'scratch-vm';
|
||||
|
||||
import ControlsComponent from '../components/controls/controls.jsx';
|
||||
|
||||
class Controls extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'handleGreenFlagClick',
|
||||
'handleStopAllClick',
|
||||
'onProjectRunStart',
|
||||
'onProjectRunStop'
|
||||
]);
|
||||
this.state = {
|
||||
projectRunning: false,
|
||||
turbo: false
|
||||
};
|
||||
}
|
||||
componentDidMount () {
|
||||
this.props.vm.addListener('PROJECT_RUN_START', this.onProjectRunStart);
|
||||
this.props.vm.addListener('PROJECT_RUN_STOP', this.onProjectRunStop);
|
||||
}
|
||||
componentWillUnmount () {
|
||||
this.props.vm.removeListener('PROJECT_RUN_START', this.onProjectRunStart);
|
||||
this.props.vm.removeListener('PROJECT_RUN_STOP', this.onProjectRunStop);
|
||||
}
|
||||
onProjectRunStart () {
|
||||
this.setState({projectRunning: true});
|
||||
}
|
||||
onProjectRunStop () {
|
||||
this.setState({projectRunning: false});
|
||||
}
|
||||
handleGreenFlagClick (e) {
|
||||
e.preventDefault();
|
||||
if (e.shiftKey) {
|
||||
this.setState({turbo: !this.state.turbo});
|
||||
this.props.vm.setTurboMode(!this.state.turbo);
|
||||
} else {
|
||||
this.props.vm.greenFlag();
|
||||
}
|
||||
}
|
||||
handleStopAllClick (e) {
|
||||
e.preventDefault();
|
||||
this.props.vm.stopAll();
|
||||
}
|
||||
render () {
|
||||
const {
|
||||
vm, // eslint-disable-line no-unused-vars
|
||||
...props
|
||||
} = this.props;
|
||||
return (
|
||||
<ControlsComponent
|
||||
{...props}
|
||||
active={this.state.projectRunning}
|
||||
turbo={this.state.turbo}
|
||||
onGreenFlagClick={this.handleGreenFlagClick}
|
||||
onStopAllClick={this.handleStopAllClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Controls.propTypes = {
|
||||
vm: PropTypes.instanceOf(VM)
|
||||
};
|
||||
|
||||
export default Controls;
|
||||
@@ -11,24 +11,18 @@ class GreenFlag extends React.Component {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'handleClick',
|
||||
'handleKeyDown',
|
||||
'handleKeyUp',
|
||||
'onProjectRunStart',
|
||||
'onProjectRunStop'
|
||||
]);
|
||||
this.state = {projectRunning: false, shiftKeyDown: false};
|
||||
this.state = {projectRunning: false};
|
||||
}
|
||||
componentDidMount () {
|
||||
this.props.vm.addListener('PROJECT_RUN_START', this.onProjectRunStart);
|
||||
this.props.vm.addListener('PROJECT_RUN_STOP', this.onProjectRunStop);
|
||||
document.addEventListener('keydown', this.handleKeyDown);
|
||||
document.addEventListener('keyup', this.handleKeyUp);
|
||||
}
|
||||
componentWillUnmount () {
|
||||
this.props.vm.removeListener('PROJECT_RUN_START', this.onProjectRunStart);
|
||||
this.props.vm.removeListener('PROJECT_RUN_STOP', this.onProjectRunStop);
|
||||
document.removeEventListener('keydown', this.handleKeyDown);
|
||||
document.removeEventListener('keyup', this.handleKeyUp);
|
||||
}
|
||||
onProjectRunStart () {
|
||||
this.setState({projectRunning: true});
|
||||
@@ -36,15 +30,9 @@ class GreenFlag extends React.Component {
|
||||
onProjectRunStop () {
|
||||
this.setState({projectRunning: false});
|
||||
}
|
||||
handleKeyDown (e) {
|
||||
this.setState({shiftKeyDown: e.shiftKey});
|
||||
}
|
||||
handleKeyUp (e) {
|
||||
this.setState({shiftKeyDown: e.shiftKey});
|
||||
}
|
||||
handleClick (e) {
|
||||
e.preventDefault();
|
||||
if (this.state.shiftKeyDown) {
|
||||
if (e.shiftKey) {
|
||||
this.props.vm.setTurboMode(!this.props.vm.runtime.turboMode);
|
||||
} else {
|
||||
this.props.vm.greenFlag();
|
||||
|
||||
@@ -14,4 +14,6 @@ $red-tertiary: #E64D00;
|
||||
$sound-primary: #CF63CF;
|
||||
$sound-tertiary: #A63FA6;
|
||||
|
||||
$control-primary: #FFAB19;
|
||||
|
||||
$form-border: #E9EEF2;
|
||||
@@ -1,11 +1,4 @@
|
||||
.green-flag {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
top: 10px;
|
||||
right: 50px;
|
||||
}
|
||||
|
||||
.stop-all {
|
||||
.controls {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
top: 10px;
|
||||
|
||||
@@ -3,8 +3,7 @@ import ReactDOM from 'react-dom';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import AppStateHOC from '../lib/app-state-hoc.jsx';
|
||||
import GreenFlag from '../containers/green-flag.jsx';
|
||||
import StopAll from '../containers/stop-all.jsx';
|
||||
import Controls from '../containers/controls.jsx';
|
||||
import Blocks from '../containers/blocks.jsx';
|
||||
import GUI from '../containers/gui.jsx';
|
||||
import ProjectLoaderHOC from '../lib/project-loader-hoc.jsx';
|
||||
@@ -14,8 +13,7 @@ import styles from './blocks-only.css';
|
||||
const mapStateToProps = state => ({vm: state.vm});
|
||||
|
||||
const VMBlocks = connect(mapStateToProps)(Blocks);
|
||||
const VMGreenFlag = connect(mapStateToProps)(GreenFlag);
|
||||
const VMStopAll = connect(mapStateToProps)(StopAll);
|
||||
const VMControls = connect(mapStateToProps)(Controls);
|
||||
|
||||
const BlocksOnly = props => (
|
||||
<GUI {...props}>
|
||||
@@ -25,8 +23,7 @@ const BlocksOnly = props => (
|
||||
media: `static/blocks-media/`
|
||||
}}
|
||||
/>
|
||||
<VMGreenFlag className={styles.greenFlag} />
|
||||
<VMStopAll className={styles.stopAll} />
|
||||
<VMControls className={styles.controls} />
|
||||
</GUI>
|
||||
);
|
||||
|
||||
|
||||
+3
-10
@@ -3,8 +3,7 @@ import ReactDOM from 'react-dom';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import AppStateHOC from '../lib/app-state-hoc.jsx';
|
||||
import GreenFlag from '../containers/green-flag.jsx';
|
||||
import StopAll from '../containers/stop-all.jsx';
|
||||
import Controls from '../containers/controls.jsx';
|
||||
import Stage from '../containers/stage.jsx';
|
||||
import Box from '../components/box/box.jsx';
|
||||
import GUI from '../containers/gui.jsx';
|
||||
@@ -15,8 +14,7 @@ import './player.css';
|
||||
const mapStateToProps = state => ({vm: state.vm});
|
||||
|
||||
const VMStage = connect(mapStateToProps)(Stage);
|
||||
const VMGreenFlag = connect(mapStateToProps)(GreenFlag);
|
||||
const VMStopAll = connect(mapStateToProps)(StopAll);
|
||||
const VMControls = connect(mapStateToProps)(Controls);
|
||||
|
||||
class Player extends React.Component {
|
||||
constructor (props) {
|
||||
@@ -55,17 +53,12 @@ class Player extends React.Component {
|
||||
width={width}
|
||||
>
|
||||
<Box height={40}>
|
||||
<VMGreenFlag
|
||||
<VMControls
|
||||
style={{
|
||||
marginRight: 10,
|
||||
height: 40
|
||||
}}
|
||||
/>
|
||||
<VMStopAll
|
||||
style={{
|
||||
height: 40
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<VMStage
|
||||
height={height}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import React from 'react';
|
||||
import {shallowWithIntl} from '../../helpers/intl-helpers.jsx';
|
||||
import Controls from '../../../src/components/controls/controls';
|
||||
import TurboMode from '../../../src/components/turbo-mode/turbo-mode';
|
||||
|
||||
describe('Controls component', () => {
|
||||
const defaultProps = () => ({
|
||||
active: false,
|
||||
greenFlagTitle: 'Go',
|
||||
onGreenFlagClick: jest.fn(),
|
||||
onStopAllClick: jest.fn(),
|
||||
stopAllTitle: 'Stop',
|
||||
turbo: false
|
||||
});
|
||||
|
||||
test('shows turbo mode when in turbo mode', () => {
|
||||
const component = shallowWithIntl(
|
||||
<Controls
|
||||
{...defaultProps()}
|
||||
/>
|
||||
);
|
||||
expect(component.find(TurboMode).exists()).toEqual(false);
|
||||
component.setProps({turbo: true});
|
||||
expect(component.find(TurboMode).exists()).toEqual(true);
|
||||
});
|
||||
|
||||
test('triggers the right callbacks when clicked', () => {
|
||||
const props = defaultProps();
|
||||
const component = shallowWithIntl(
|
||||
<Controls
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
component.find('[title="Go"]').simulate('click');
|
||||
expect(props.onGreenFlagClick).toHaveBeenCalled();
|
||||
|
||||
component.find('[title="Stop"]').simulate('click');
|
||||
expect(props.onStopAllClick).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user