diff --git a/src/components/question/question.css b/src/components/question/question.css new file mode 100644 index 0000000000000000000000000000000000000000..7c6e672a97fd1be774a187edfab3b8131fb6bcc0 --- /dev/null +++ b/src/components/question/question.css @@ -0,0 +1,57 @@ +@import "../../css/units.css"; +@import "../../css/colors.css"; + +.question-wrapper { + position: absolute; + bottom: 0; + left: 0; + width: 100%; +} + +.question-container { + margin: $space; + border: 1px solid $form-border; + border-radius: $space; + border-width: 2px; + padding: 1rem; + background: white; +} + +.question-label { + font-size: 0.75rem; + font-weight: bold; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + color: $text-primary; + padding-bottom: $space; +} + +.question-input { + display: flex; /* Keeps the input from going outside this container */ + position: relative; +} + +.question-submit-button { + position: absolute; + top: calc($space / 2); + right: calc($space / 2); + + width: calc(2rem - $space); + height: calc(2rem - $space); + + border: none; + border-radius: 100%; + + color: white; + background: $motion-primary; +} + +/* Input overrides: width, font-weight, focus outline and padding */ +.question-input > input { + width: 100%; + padding: 0 2rem 0 0.75rem; /* To make room for the submit button */ + font-weight: normal; +} + +.question-input > input:focus { + box-shadow: 0px 0px 0px 3px $motion-transparent; +} diff --git a/src/components/question/question.jsx b/src/components/question/question.jsx new file mode 100644 index 0000000000000000000000000000000000000000..11927bf9652cadd55eeee2654a1429c5250f30dc --- /dev/null +++ b/src/components/question/question.jsx @@ -0,0 +1,47 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import styles from './question.css'; +import Input from '../forms/input.jsx'; + +const QuestionComponent = props => { + const { + answer, + question, + onChange, + onClick, + onKeyPress + } = props; + return ( + <div className={styles.questionWrapper}> + <div className={styles.questionContainer}> + {question ? ( + <div className={styles.questionLabel}>{question}</div> + ) : null} + <div className={styles.questionInput}> + <Input + autoFocus + value={answer} + onChange={onChange} + onKeyPress={onKeyPress} + /> + <button + className={styles.questionSubmitButton} + onClick={onClick} + > + ✔︎ + </button> + </div> + </div> + </div> + ); +}; + +QuestionComponent.propTypes = { + answer: PropTypes.string, + onChange: PropTypes.func.isRequired, + onClick: PropTypes.func.isRequired, + onKeyPress: PropTypes.func.isRequired, + question: PropTypes.string +}; + +export default QuestionComponent; diff --git a/src/components/stage/stage.jsx b/src/components/stage/stage.jsx index 7a23c099d8768c970f8750f4055faeab165962ce..ae5ff92a6d2d014429dc5f7fdca12102fd20abf1 100644 --- a/src/components/stage/stage.jsx +++ b/src/components/stage/stage.jsx @@ -5,6 +5,7 @@ import classNames from 'classnames'; import Box from '../box/box.jsx'; import Loupe from '../loupe/loupe.jsx'; import MonitorList from '../../containers/monitor-list.jsx'; +import Question from '../../containers/question.jsx'; import styles from './stage.css'; const StageComponent = props => { @@ -15,6 +16,8 @@ const StageComponent = props => { colorInfo, onDeactivateColorPicker, isColorPicking, + question, + onQuestionAnswered, ...boxProps } = props; return ( @@ -40,6 +43,12 @@ const StageComponent = props => { <Loupe colorInfo={colorInfo} /> </Box> ) : null} + {question === null ? null : ( + <Question + question={question} + onQuestionAnswered={onQuestionAnswered} + /> + )} </Box> {isColorPicking ? ( <Box @@ -56,6 +65,8 @@ StageComponent.propTypes = { height: PropTypes.number, isColorPicking: PropTypes.bool, onDeactivateColorPicker: PropTypes.func, + onQuestionAnswered: PropTypes.func, + question: PropTypes.string, width: PropTypes.number }; StageComponent.defaultProps = { diff --git a/src/containers/question.jsx b/src/containers/question.jsx new file mode 100644 index 0000000000000000000000000000000000000000..fdc67dac06d0a86541fdafb6b1e2bdedb21b412e --- /dev/null +++ b/src/containers/question.jsx @@ -0,0 +1,45 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import bindAll from 'lodash.bindall'; +import QuestionComponent from '../components/question/question.jsx'; + +class Question extends React.Component { + constructor (props) { + super(props); + bindAll(this, [ + 'handleChange', + 'handleKeyPress', + 'handleSubmit' + ]); + this.state = { + answer: '' + }; + } + handleChange (e) { + this.setState({answer: e.target.value}); + } + handleKeyPress (event) { + if (event.key === 'Enter') this.handleSubmit(); + } + handleSubmit () { + this.props.onQuestionAnswered(this.state.answer); + } + render () { + return ( + <QuestionComponent + answer={this.state.answer} + question={this.props.question} + onChange={this.handleChange} + onClick={this.handleSubmit} + onKeyPress={this.handleKeyPress} + /> + ); + } +} + +Question.propTypes = { + onQuestionAnswered: PropTypes.func.isRequired, + question: PropTypes.string +}; + +export default Question; diff --git a/src/containers/stage.jsx b/src/containers/stage.jsx index 1de93d23d36bbc49ad6a3930c3520a6041af96d3..fcff811bffef4092b0c912a240dd95466f11272b 100644 --- a/src/containers/stage.jsx +++ b/src/containers/stage.jsx @@ -24,12 +24,14 @@ class Stage extends React.Component { 'cancelMouseDownTimeout', 'detachMouseEvents', 'handleDoubleClick', + 'handleQuestionAnswered', 'onMouseUp', 'onMouseMove', 'onMouseDown', 'onStartDrag', 'onStopDrag', 'updateRect', + 'questionListener', 'setCanvas' ]); this.state = { @@ -38,7 +40,8 @@ class Stage extends React.Component { isDragging: false, dragOffset: null, dragId: null, - colorInfo: null + colorInfo: null, + question: null }; } componentDidMount () { @@ -47,12 +50,14 @@ class Stage extends React.Component { this.updateRect(); this.renderer = new Renderer(this.canvas); this.props.vm.attachRenderer(this.renderer); + this.props.vm.runtime.addListener('QUESTION', this.questionListener); } shouldComponentUpdate (nextProps, nextState) { return this.props.width !== nextProps.width || this.props.height !== nextProps.height || this.props.isColorPicking !== nextProps.isColorPicking || - this.state.colorInfo !== nextState.colorInfo; + this.state.colorInfo !== nextState.colorInfo || + this.state.question !== nextState.question; } componentDidUpdate (prevProps) { if (this.props.isColorPicking && !prevProps.isColorPicking) { @@ -66,6 +71,14 @@ class Stage extends React.Component { this.detachRectEvents(); this.stopColorPickingLoop(); } + questionListener (question) { + this.setState({question: question}); + } + handleQuestionAnswered (answer) { + this.setState({question: null}, () => { + this.props.vm.runtime.emit('ANSWER', answer); + }); + } startColorPickingLoop () { this.intervalId = setInterval(() => { this.setState({colorInfo: this.getColorInfo(this.pickX, this.pickY)}); @@ -251,7 +264,9 @@ class Stage extends React.Component { <StageComponent canvasRef={this.setCanvas} colorInfo={this.state.colorInfo} + question={this.state.question} onDoubleClick={this.handleDoubleClick} + onQuestionAnswered={this.handleQuestionAnswered} {...props} /> ); diff --git a/src/lib/make-toolbox-xml.js b/src/lib/make-toolbox-xml.js index 1c3843a09a1904bde09fc3c5dcce82bfde088284..315e1388c51656e87be18924c559fb506322dc6c 100644 --- a/src/lib/make-toolbox-xml.js +++ b/src/lib/make-toolbox-xml.js @@ -336,12 +336,12 @@ const control = ` </shadow> </value> </block> - <block type="control_forever"/> + <block id="forever" type="control_forever"/> ${blockSeparator} <block type="control_if"/> <block type="control_if_else"/> - <block type="control_wait_until"/> - <block type="control_repeat_until"/> + <block id="wait_until" type="control_wait_until"/> + <block id="repeat_until" type="control_repeat_until"/> ${blockSeparator} <block type="control_stop"/> ${blockSeparator} @@ -382,7 +382,7 @@ const sensing = ` </value> </block> ${blockSeparator} - <block type="sensing_askandwait"> + <block id="askandwait" type="sensing_askandwait"> <value name="QUESTION"> <shadow type="text"> <field name="TEXT">What's your name?</field>