Spaces:
Runtime error
Runtime error
| import bindAll from 'lodash.bindall'; | |
| import PropTypes from 'prop-types'; | |
| import React from 'react'; | |
| import VM from 'scratch-vm'; | |
| import {connect} from 'react-redux'; | |
| import {getEventXY} from '../lib/touch-utils'; | |
| import {getVariableValue, setVariableValue} from '../lib/variable-utils'; | |
| import ListMonitorComponent from '../components/monitor/list-monitor.jsx'; | |
| import {Map} from 'immutable'; | |
| class ListMonitor extends React.Component { | |
| constructor (props) { | |
| super(props); | |
| bindAll(this, [ | |
| 'handleActivate', | |
| 'handleDeactivate', | |
| 'handleInput', | |
| 'handleRemove', | |
| 'handleKeyPress', | |
| 'handleFocus', | |
| 'handleAdd', | |
| 'handleResizeMouseDown' | |
| ]); | |
| this.state = { | |
| activeIndex: null, | |
| activeValue: null, | |
| width: props.width || 100, | |
| height: props.height || 200 | |
| }; | |
| } | |
| handleActivate (index) { | |
| // Do nothing if activating the currently active item | |
| if (this.state.activeIndex === index) { | |
| return; | |
| } | |
| let activeValue = this.props.value[index]; | |
| if (activeValue.toListEditor) activeValue = activeValue.toListEditor(); | |
| this.setState({ | |
| activeIndex: index, | |
| activeValue, | |
| handlerClass: this.props.value[index] | |
| }); | |
| } | |
| handleDeactivate () { | |
| // Submit any in-progress value edits on blur | |
| if (this.state.activeIndex !== null) { | |
| const {vm, targetId, id: variableId} = this.props; | |
| const newListValue = getVariableValue(vm, targetId, variableId); | |
| const oldValue = this.props.value[this.state.activeIndex]; | |
| let newValue = this.state.activeValue; | |
| if (oldValue.fromListEditor) { | |
| newValue = oldValue.fromListEditor(newValue); | |
| } | |
| newListValue[this.state.activeIndex] = newValue; | |
| setVariableValue(vm, targetId, variableId, newListValue); | |
| this.setState({activeIndex: null, activeValue: null}); | |
| } | |
| } | |
| handleFocus (e) { | |
| // Select all the text in the input when it is focused. | |
| e.target.select(); | |
| } | |
| handleKeyPress (e) { | |
| // Special case for tab, arrow keys and enter. | |
| // Tab / shift+tab navigate down / up the list. | |
| // Arrow down / arrow up navigate down / up the list. | |
| // Enter / shift+enter insert new blank item below / above. | |
| const previouslyActiveIndex = this.state.activeIndex; | |
| const {vm, targetId, id: variableId} = this.props; | |
| let navigateDirection = 0; | |
| if (e.key === 'Tab') navigateDirection = e.shiftKey ? -1 : 1; | |
| else if (e.key === 'ArrowUp') navigateDirection = -1; | |
| else if (e.key === 'ArrowDown') navigateDirection = 1; | |
| if (navigateDirection) { | |
| this.handleDeactivate(); // Submit in-progress edits | |
| const newIndex = this.wrapListIndex(previouslyActiveIndex + navigateDirection, this.props.value.length); | |
| this.setState({ | |
| activeIndex: newIndex, | |
| activeValue: this.props.value[newIndex] | |
| }); | |
| e.preventDefault(); // Stop default tab behavior, handled by this state change | |
| } else if (e.key === 'Enter') { | |
| this.handleDeactivate(); // Submit in-progress edits | |
| const newListItemValue = ''; // Enter adds a blank item | |
| const newValueOffset = e.shiftKey ? 0 : 1; // Shift-enter inserts above | |
| const listValue = getVariableValue(vm, targetId, variableId); | |
| const newListValue = listValue.slice(0, previouslyActiveIndex + newValueOffset) | |
| .concat([newListItemValue]) | |
| .concat(listValue.slice(previouslyActiveIndex + newValueOffset)); | |
| setVariableValue(vm, targetId, variableId, newListValue); | |
| const newIndex = this.wrapListIndex(previouslyActiveIndex + newValueOffset, newListValue.length); | |
| this.setState({ | |
| activeIndex: newIndex, | |
| activeValue: newListItemValue | |
| }); | |
| } | |
| } | |
| handleInput (e) { | |
| this.setState({activeValue: e.target.value}); | |
| } | |
| handleRemove (e) { | |
| e.preventDefault(); // Default would blur input, prevent that. | |
| e.stopPropagation(); // Bubbling would activate, which will be handled here | |
| const {vm, targetId, id: variableId} = this.props; | |
| const listValue = getVariableValue(vm, targetId, variableId); | |
| const newListValue = listValue.slice(0, this.state.activeIndex) | |
| .concat(listValue.slice(this.state.activeIndex + 1)); | |
| setVariableValue(vm, targetId, variableId, newListValue); | |
| const newActiveIndex = Math.min(newListValue.length - 1, this.state.activeIndex); | |
| this.setState({ | |
| activeIndex: newActiveIndex, | |
| activeValue: newListValue[newActiveIndex] | |
| }); | |
| } | |
| handleAdd () { | |
| // Add button appends a blank value and switches to it | |
| const {vm, targetId, id: variableId} = this.props; | |
| const newListValue = getVariableValue(vm, targetId, variableId).concat(['']); | |
| setVariableValue(vm, targetId, variableId, newListValue); | |
| this.setState({activeIndex: newListValue.length - 1, activeValue: ''}); | |
| } | |
| handleResizeMouseDown (e) { | |
| this.initialPosition = getEventXY(e); | |
| this.initialWidth = this.state.width; | |
| this.initialHeight = this.state.height; | |
| const onMouseMove = ev => { | |
| const newPosition = getEventXY(ev); | |
| const dx = newPosition.x - this.initialPosition.x; | |
| const dy = newPosition.y - this.initialPosition.y; | |
| this.setState({ | |
| width: Math.max(Math.min(this.initialWidth + dx, this.props.customStageSize.width), 100), | |
| height: Math.max(Math.min(this.initialHeight + dy, this.props.customStageSize.height), 60) | |
| }); | |
| }; | |
| const onMouseUp = ev => { | |
| onMouseMove(ev); // Make sure width/height are up-to-date | |
| window.removeEventListener('mousemove', onMouseMove); | |
| window.removeEventListener('mouseup', onMouseUp); | |
| this.props.vm.runtime.requestUpdateMonitor(Map({ | |
| id: this.props.id, | |
| height: this.state.height, | |
| width: this.state.width | |
| })); | |
| }; | |
| window.addEventListener('mousemove', onMouseMove); | |
| window.addEventListener('mouseup', onMouseUp); | |
| } | |
| wrapListIndex (index, length) { | |
| return (index + length) % length; | |
| } | |
| render () { | |
| const { | |
| vm, // eslint-disable-line no-unused-vars | |
| ...props | |
| } = this.props; | |
| return ( | |
| <ListMonitorComponent | |
| {...props} | |
| activeIndex={this.state.activeIndex} | |
| activeValue={this.state.activeValue} | |
| height={this.state.height} | |
| width={this.state.width} | |
| onActivate={this.handleActivate} | |
| onAdd={this.handleAdd} | |
| onDeactivate={this.handleDeactivate} | |
| onFocus={this.handleFocus} | |
| onInput={this.handleInput} | |
| onKeyPress={this.handleKeyPress} | |
| onRemove={this.handleRemove} | |
| onResizeMouseDown={this.handleResizeMouseDown} | |
| /> | |
| ); | |
| } | |
| } | |
| ListMonitor.propTypes = { | |
| height: PropTypes.number, | |
| id: PropTypes.string, | |
| customStageSize: PropTypes.shape({ | |
| width: PropTypes.number, | |
| height: PropTypes.number | |
| }), | |
| targetId: PropTypes.string, | |
| value: PropTypes.oneOfType([ | |
| PropTypes.number, | |
| PropTypes.string | |
| ]), | |
| vm: PropTypes.instanceOf(VM), | |
| width: PropTypes.number, | |
| x: PropTypes.number, | |
| y: PropTypes.number | |
| }; | |
| const mapStateToProps = state => ({ | |
| customStageSize: state.scratchGui.customStageSize, | |
| vm: state.scratchGui.vm | |
| }); | |
| export default connect(mapStateToProps)(ListMonitor); | |