penguinmod-editor-2 / src /containers /custom-procedures.jsx
soiz1's picture
Upload 2891 files
6bcb42f verified
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);