Spaces:
Runtime error
Runtime error
| import bindAll from 'lodash.bindall'; | |
| import defaultsDeep from 'lodash.defaultsdeep'; | |
| import PropTypes from 'prop-types'; | |
| import React from 'react'; | |
| import CustomProceduresComponent from '../components/custom-procedures/custom-procedures.jsx'; | |
| import LazyScratchBlocks from '../lib/tw-lazy-scratch-blocks'; | |
| import {connect} from 'react-redux'; | |
| import Color from './custom-procedures-util/color.js'; | |
| function createHeavyColorFromHex(hex, percentage) { | |
| const rgb = Color.hexToRgb(hex); | |
| const hsv = Color.rgbToHsv(rgb); | |
| if (hsv.v > 0.6) { | |
| // so that pure white can still get color change | |
| hsv.v -= percentage / 2; | |
| } | |
| // only white-black have this property | |
| // so we can avoid adding red to them | |
| if (!(hsv.h === 0 && hsv.s === 0)) { | |
| hsv.s += percentage * hsv.v; | |
| } | |
| // make sure values arent invalid | |
| if (hsv.v > 1) hsv.v = 1; | |
| if (hsv.v < 0) hsv.v = 0; | |
| if (hsv.s > 1) hsv.s = 1; | |
| if (hsv.s < 0) hsv.s = 0; | |
| const newRgb = Color.hsvToRgb(hsv); | |
| return Color.rgbToHex(newRgb); | |
| } | |
| class CustomProcedures extends React.Component { | |
| constructor (props) { | |
| super(props); | |
| bindAll(this, [ | |
| 'handleAddLabel', | |
| 'handleAddBoolean', | |
| 'handleAddCommand', | |
| 'handleAddTextNumber', | |
| 'handleToggleWarp', | |
| 'handleToggleReturns', | |
| 'handleCancel', | |
| 'handleOk', | |
| 'handleChangeType', | |
| 'handleBlockColorChange', | |
| 'setHexBlockColor', | |
| 'setBlocks', | |
| 'handleTestFunction' | |
| ]); | |
| this.state = { | |
| rtlOffset: 0, | |
| warp: false, | |
| returns: false, | |
| editing: false, | |
| blockColor: '#000000', | |
| type: 'statement' | |
| }; | |
| } | |
| componentWillUnmount () { | |
| if (this.workspace) { | |
| this.workspace.dispose(); | |
| } | |
| } | |
| setBlocks (blocksRef) { | |
| if (!blocksRef) return; | |
| this.blocks = blocksRef; | |
| const workspaceConfig = defaultsDeep({}, | |
| CustomProcedures.defaultOptions, | |
| this.props.options, | |
| {rtl: this.props.isRtl} | |
| ); | |
| // @todo This is a hack to make there be no toolbox. | |
| const ScratchBlocks = LazyScratchBlocks.get(); | |
| const oldDefaultToolbox = ScratchBlocks.Blocks.defaultToolbox; | |
| ScratchBlocks.Blocks.defaultToolbox = null; | |
| this.workspace = ScratchBlocks.inject(this.blocks, workspaceConfig); | |
| ScratchBlocks.Blocks.defaultToolbox = oldDefaultToolbox; | |
| // Create the procedure declaration block for editing the mutation. | |
| this.mutationRoot = this.workspace.newBlock('procedures_declaration'); | |
| // Make the declaration immovable, undeletable and have no context menu | |
| this.mutationRoot.setMovable(false); | |
| this.mutationRoot.setDeletable(false); | |
| this.mutationRoot.contextMenu = false; | |
| this.workspace.addChangeListener(() => { | |
| this.mutationRoot.onChangeFn(); | |
| // Keep the block centered on the workspace | |
| const metrics = this.workspace.getMetrics(); | |
| const {x, y} = this.mutationRoot.getRelativeToSurfaceXY(); | |
| const dy = (metrics.viewHeight / 2) - (this.mutationRoot.height / 2) - y; | |
| let dx; | |
| if (this.props.isRtl) { | |
| // // TODO: https://github.com/LLK/scratch-gui/issues/2838 | |
| // This is temporary until we can figure out what's going on width | |
| // block positioning on the workspace for RTL. | |
| // Workspace is always origin top-left, with x increasing to the right | |
| // Calculate initial starting offset and save it, every other move | |
| // has to take the original offset into account. | |
| // Calculate a new left postion based on new width | |
| // Convert current x position into LTR (mirror) x position (uses original offset) | |
| // Use the difference between ltrX and mirrorX as the amount to move | |
| const ltrX = ((metrics.viewWidth / 2) - (this.mutationRoot.width / 2) + 25); | |
| const mirrorX = x - ((x - this.state.rtlOffset) * 2); | |
| if (mirrorX === ltrX) { | |
| return; | |
| } | |
| dx = mirrorX - ltrX; | |
| const midPoint = metrics.viewWidth / 2; | |
| if (x === 0) { | |
| // if it's the first time positioning, it should always move right | |
| if (this.mutationRoot.width < midPoint) { | |
| dx = ltrX; | |
| } else if (this.mutationRoot.width < metrics.viewWidth) { | |
| dx = midPoint - ((metrics.viewWidth - this.mutationRoot.width) / 2); | |
| } else { | |
| dx = midPoint + (this.mutationRoot.width - metrics.viewWidth); | |
| } | |
| this.mutationRoot.moveBy(dx, dy); | |
| this.setState({rtlOffset: this.mutationRoot.getRelativeToSurfaceXY().x}); | |
| return; | |
| } | |
| if (this.mutationRoot.width > metrics.viewWidth) { | |
| dx = dx + this.mutationRoot.width - metrics.viewWidth; | |
| } | |
| } else { | |
| dx = (metrics.viewWidth / 2) - (this.mutationRoot.width / 2) - x; | |
| // If the procedure declaration is wider than the view width, | |
| // keep the right-hand side of the procedure in view. | |
| if (this.mutationRoot.width > metrics.viewWidth) { | |
| dx = metrics.viewWidth - this.mutationRoot.width - x; | |
| } | |
| } | |
| this.mutationRoot.moveBy(dx, dy); | |
| }); | |
| this.mutationRoot.domToMutation(this.props.mutator); | |
| this.mutationRoot.initSvg(); | |
| this.mutationRoot.render(); | |
| this.setState({ | |
| warp: this.mutationRoot.getWarp(), | |
| returns: this.mutationRoot.getReturns(), | |
| editing: this.mutationRoot.getEdited(), | |
| // sometimes color[0] exists but sometimes it doesnt | |
| // i can blame gsa for this or just do nothing about it :troll: | |
| blockColor: this.mutationRoot.color ? this.mutationRoot.color[0] : this.mutationRoot.colour_ | |
| }); | |
| // Allow the initial events to run to position this block, then focus. | |
| setTimeout(() => { | |
| this.mutationRoot.focusLastEditor_(); | |
| // if editing, apply block color | |
| if (this.state.editing && this.mutationRoot.color) { | |
| this.handleBlockColorChange({ | |
| target: { | |
| value: this.mutationRoot.color[0] | |
| } | |
| }); | |
| } | |
| }); | |
| } | |
| handleCancel () { | |
| this.props.onRequestClose(); | |
| } | |
| handleOk () { | |
| this.mutationRoot.setEdited(true) | |
| const newMutation = this.mutationRoot ? this.mutationRoot.mutationToDom(true) : null; | |
| this.props.onRequestClose(newMutation); | |
| } | |
| handleAddLabel () { | |
| if (this.mutationRoot) { | |
| this.mutationRoot.addLabelExternal(); | |
| } | |
| } | |
| handleAddBoolean () { | |
| if (this.mutationRoot) { | |
| this.mutationRoot.addBooleanExternal(); | |
| } | |
| } | |
| handleAddCommand () { | |
| if (this.mutationRoot) { | |
| this.mutationRoot.addCommandExternal(); | |
| } | |
| } | |
| handleAddTextNumber () { | |
| if (this.mutationRoot) { | |
| this.mutationRoot.addStringNumberExternal(); | |
| } | |
| } | |
| handleToggleWarp () { | |
| if (this.mutationRoot) { | |
| const newWarp = !this.mutationRoot.getWarp(); | |
| this.mutationRoot.setWarp(newWarp); | |
| this.setState({warp: newWarp}); | |
| } | |
| } | |
| handleToggleReturns () { | |
| if (this.mutationRoot) { | |
| const newReturns = !this.mutationRoot.getReturns(); | |
| this.mutationRoot.setReturns(newReturns); | |
| this.handleChangeType(newReturns ? 'string' : 'statement'); | |
| this.setState({returns: newReturns}); | |
| } | |
| } | |
| handleChangeType (value) { | |
| if (this.mutationRoot) { | |
| const newType = value; | |
| this.mutationRoot.setType(newType); | |
| this.setState({type: newType}); | |
| } | |
| } | |
| handleBlockColorChange (element) { | |
| if (this.mutationRoot) { | |
| const newColor = element.target.value; | |
| this.mutationRoot.setColor( | |
| newColor, | |
| createHeavyColorFromHex(newColor, 0.15), | |
| createHeavyColorFromHex(newColor, 0.25) | |
| ); | |
| this.setState({blockColor: newColor}); | |
| } | |
| } | |
| setHexBlockColor (hex) { | |
| this.handleBlockColorChange({ | |
| target: { | |
| value: hex | |
| } | |
| }); | |
| } | |
| handleTestFunction (type) { | |
| if (this.mutationRoot) { | |
| switch (type) { | |
| case 'icon': { | |
| const iconUri = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAMAAAC67D+PAAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAADwUExURf/////////////z8//v7//u7v/n5//j4//5+f/////////MzP+3t//////////9/f////+trf/////////Ozv/Pz//////////39//Jyf/8/P/////////////+/v/z8//////////S0v/Hx//////////e3v/e3v/////////////r6//e3v/j4//u7v////+Ghv+Fhf+Dg/95ef+bm/+Xl/+lpf+trf+rq/+fn/+kpP+Tk/9tbf+Cgv+Hh/9ZWf+cnP9ycv9bW/+Jif99ff+Li//ExP+zs/+EhP+Kiv+YmP9vb/+4uP9ubv+Bgf////kftB4AAAAwdFJOUwAQfdDW2u37siNq9f2ImKV0/JlH8PVlD6D6z2gMIc/DGDbt90Yr4uMvBHLr/fXjbhcejrAAAAABYktHRACIBR1IAAAAB3RJTUUH5wUYBDIzz3HsIQAAAHNJREFUCNdjYGRiZmFhZWPn4GTg4jYwMDA0MubhZeAzMTUzt7C0suZnEBC0trG1s7dzEGIQFnF0snV2cRYVYxCXkLSyc3WTkpZhYJCVc/fw9JJXYGBgUFTyNvfxVVYBMlXVjPz8DNQ1gExNLW0dXT19TQYA+wMO76YS2sEAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjMtMDUtMjRUMDQ6NTA6NTArMDA6MDDybVL4AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIzLTA1LTI0VDA0OjUwOjUwKzAwOjAwgzDqRAAAACh0RVh0ZGF0ZTp0aW1lc3RhbXAAMjAyMy0wNS0yNFQwNDo1MDo1MSswMDowMHJSwC8AAAAASUVORK5CYII='; | |
| this.mutationRoot.setImage(iconUri); | |
| break; | |
| } | |
| case 'removeicon': { | |
| this.mutationRoot.unsetImage(); | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| render () { | |
| return ( | |
| <CustomProceduresComponent | |
| componentRef={this.setBlocks} | |
| warp={this.state.warp} | |
| blockColor={this.state.blockColor} | |
| returns={this.state.returns} | |
| onAddBoolean={this.handleAddBoolean} | |
| onAddCommand={this.handleAddCommand} | |
| onAddLabel={this.handleAddLabel} | |
| onAddTextNumber={this.handleAddTextNumber} | |
| onCancel={this.handleCancel} | |
| onOk={this.handleOk} | |
| onToggleWarp={this.handleToggleWarp} | |
| onToggleReturns={this.handleToggleReturns} | |
| editing={this.state.editing} | |
| selectedType={this.state.type} | |
| onOutputTypeChanged={this.handleChangeType} | |
| onBlockColorChange={this.handleBlockColorChange} | |
| setHexBlockColor={this.setHexBlockColor} | |
| onTestStart={this.handleTestFunction} | |
| /> | |
| ); | |
| } | |
| } | |
| CustomProcedures.propTypes = { | |
| isRtl: PropTypes.bool, | |
| mutator: PropTypes.instanceOf(Element), | |
| onRequestClose: PropTypes.func.isRequired, | |
| options: PropTypes.shape({ | |
| media: PropTypes.string, | |
| zoom: PropTypes.shape({ | |
| controls: PropTypes.bool, | |
| wheel: PropTypes.bool, | |
| startScale: PropTypes.number | |
| }), | |
| comments: PropTypes.bool, | |
| collapse: PropTypes.bool | |
| }) | |
| }; | |
| CustomProcedures.defaultOptions = { | |
| zoom: { | |
| controls: false, | |
| wheel: false, | |
| startScale: 0.9 | |
| }, | |
| comments: false, | |
| collapse: false, | |
| scrollbars: true | |
| }; | |
| CustomProcedures.defaultProps = { | |
| options: CustomProcedures.defaultOptions | |
| }; | |
| const mapStateToProps = state => ({ | |
| isRtl: state.locales.isRtl, | |
| mutator: state.scratchGui.customProcedures.mutator | |
| }); | |
| export default connect( | |
| mapStateToProps | |
| )(CustomProcedures); | |