scratch-gui
Version:
GraphicaL User Interface for creating and running Scratch 3.0 projects
119 lines (112 loc) • 4.26 kB
JSX
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 {updateTargets} from '../reducers/targets';
import {updateBlockDrag} from '../reducers/block-drag';
import {updateMonitors} from '../reducers/monitors';
/*
* Higher Order Component to manage events emitted by the VM
* @param {React.Component} WrappedComponent component to manage VM events for
* @returns {React.Component} connected component with vm events bound to redux
*/
const vmListenerHOC = function (WrappedComponent) {
class VMListener extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handleKeyDown',
'handleKeyUp'
]);
// We have to start listening to the vm here rather than in
// componentDidMount because the HOC mounts the wrapped component,
// so the HOC componentDidMount triggers after the wrapped component
// mounts.
// If the wrapped component uses the vm in componentDidMount, then
// we need to start listening before mounting the wrapped component.
this.props.vm.on('targetsUpdate', this.props.onTargetsUpdate);
this.props.vm.on('MONITORS_UPDATE', this.props.onMonitorsUpdate);
this.props.vm.on('BLOCK_DRAG_UPDATE', this.props.onBlockDragUpdate);
}
componentDidMount () {
if (this.props.attachKeyboardEvents) {
document.addEventListener('keydown', this.handleKeyDown);
document.addEventListener('keyup', this.handleKeyUp);
}
}
componentWillUnmount () {
if (this.props.attachKeyboardEvents) {
document.removeEventListener('keydown', this.handleKeyDown);
document.removeEventListener('keyup', this.handleKeyUp);
}
}
handleKeyDown (e) {
// Don't capture keys intended for Blockly inputs.
if (e.target !== document && e.target !== document.body) return;
this.props.vm.postIOData('keyboard', {
keyCode: e.keyCode,
key: e.key,
isDown: true
});
}
handleKeyUp (e) {
// Always capture up events,
// even those that have switched to other targets.
this.props.vm.postIOData('keyboard', {
keyCode: e.keyCode,
key: e.key,
isDown: false
});
// E.g., prevent scroll.
if (e.target !== document && e.target !== document.body) {
e.preventDefault();
}
}
render () {
const {
/* eslint-disable no-unused-vars */
attachKeyboardEvents,
onBlockDragUpdate,
onKeyDown,
onKeyUp,
onMonitorsUpdate,
onTargetsUpdate,
/* eslint-enable no-unused-vars */
...props
} = this.props;
return <WrappedComponent {...props} />;
}
}
VMListener.propTypes = {
attachKeyboardEvents: PropTypes.bool,
onBlockDragUpdate: PropTypes.func.isRequired,
onKeyDown: PropTypes.func,
onKeyUp: PropTypes.func,
onMonitorsUpdate: PropTypes.func.isRequired,
onTargetsUpdate: PropTypes.func.isRequired,
vm: PropTypes.instanceOf(VM).isRequired
};
VMListener.defaultProps = {
attachKeyboardEvents: true
};
const mapStateToProps = state => ({
vm: state.vm
});
const mapDispatchToProps = dispatch => ({
onTargetsUpdate: data => {
dispatch(updateTargets(data.targetList, data.editingTarget));
},
onMonitorsUpdate: monitorList => {
dispatch(updateMonitors(monitorList));
},
onBlockDragUpdate: areBlocksOverGui => {
dispatch(updateBlockDrag(areBlocksOverGui));
}
});
return connect(
mapStateToProps,
mapDispatchToProps
)(VMListener);
};
export default vmListenerHOC;