import Blockly from "blockly/core"; import { getColour } from "../helpers/colour"; import * as Types from "../helpers/types"; import { getCompatibleTypes } from "../helpers/types"; Blockly.Blocks["controls_if"] = { /** * Block for if/elseif/else condition. * @this Blockly.Block */ init: function () { this.setHelpUrl(Blockly.Msg.CONTROLS_IF_HELPURL); this.setColour(getColour().logic); this.appendValueInput("IF0") .setCheck(Types.getCompatibleTypes("boolean")) .appendField(Blockly.Msg.CONTROLS_IF_MSG_IF); this.appendStatementInput("DO0").appendField( Blockly.Msg.CONTROLS_IF_MSG_THEN ); this.setPreviousStatement(true); this.setNextStatement(true); this.setMutator( new Blockly.Mutator(["controls_if_elseif", "controls_if_else"]) ); this.setTooltip(Blockly.Msg.CONTROLS_IF_TOOLTIP_1); this.elseifCount_ = 0; this.elseCount_ = 0; }, /** * Create XML to represent the number of else-if and else inputs. * @return {Element} XML storage element. * @this Blockly.Block */ mutationToDom: function () { if (!this.elseifCount_ && !this.elseCount_) { return null; } var container = document.createElement("mutation"); if (this.elseifCount_) { container.setAttribute("elseif", this.elseifCount_); } if (this.elseCount_) { container.setAttribute("else", 1); } return container; }, /** * Parse XML to restore the else-if and else inputs. * @param {!Element} xmlElement XML storage element. * @this Blockly.Block */ domToMutation: function (xmlElement) { this.elseifCount_ = parseInt(xmlElement.getAttribute("elseif"), 10) || 0; this.elseCount_ = parseInt(xmlElement.getAttribute("else"), 10) || 0; this.updateShape_(); }, /** * Populate the mutator's dialog with this block's components. * @param {!Blockly.Workspace} workspace Mutator's workspace. * @return {!Blockly.Block} Root block in mutator. * @this Blockly.Block */ decompose: function (workspace) { var containerBlock = workspace.newBlock("controls_if_if"); containerBlock.initSvg(); var connection = containerBlock.nextConnection; for (var i = 1; i <= this.elseifCount_; i++) { var elseifBlock = workspace.newBlock("controls_if_elseif"); elseifBlock.initSvg(); connection.connect(elseifBlock.previousConnection); connection = elseifBlock.nextConnection; } if (this.elseCount_) { var elseBlock = workspace.newBlock("controls_if_else"); elseBlock.initSvg(); connection.connect(elseBlock.previousConnection); } return containerBlock; }, /** * Reconfigure this block based on the mutator dialog's components. * @param {!Blockly.Block} containerBlock Root block in mutator. * @this Blockly.Block */ compose: function (containerBlock) { var clauseBlock = containerBlock.nextConnection.targetBlock(); // Count number of inputs. this.elseifCount_ = 0; this.elseCount_ = 0; var valueConnections = [null]; var statementConnections = [null]; var elseStatementConnection = null; while (clauseBlock) { switch (clauseBlock.type) { case "controls_if_elseif": this.elseifCount_++; valueConnections.push(clauseBlock.valueConnection_); statementConnections.push(clauseBlock.statementConnection_); break; case "controls_if_else": this.elseCount_++; elseStatementConnection = clauseBlock.statementConnection_; break; default: throw new Error("Unknown block type."); } clauseBlock = clauseBlock.nextConnection && clauseBlock.nextConnection.targetBlock(); } this.updateShape_(); // Reconnect any child blocks. for (var i = 1; i <= this.elseifCount_; i++) { Blockly.Mutator.reconnect(valueConnections[i], this, "IF" + i); Blockly.Mutator.reconnect(statementConnections[i], this, "DO" + i); } Blockly.Mutator.reconnect(elseStatementConnection, this, "ELSE"); }, /** * Store pointers to any connected child blocks. * @param {!Blockly.Block} containerBlock Root block in mutator. * @this Blockly.Block */ saveConnections: function (containerBlock) { var clauseBlock = containerBlock.nextConnection.targetBlock(); var i = 1; var inputDo; while (clauseBlock) { switch (clauseBlock.type) { case "controls_if_elseif": var inputIf = this.getInput("IF" + i); inputDo = this.getInput("DO" + i); clauseBlock.valueConnection_ = inputIf && inputIf.connection.targetConnection; clauseBlock.statementConnection_ = inputDo && inputDo.connection.targetConnection; i++; break; case "controls_if_else": inputDo = this.getInput("ELSE"); clauseBlock.statementConnection_ = inputDo && inputDo.connection.targetConnection; break; default: throw new Error("Unknown block type."); } clauseBlock = clauseBlock.nextConnection && clauseBlock.nextConnection.targetBlock(); } }, /** * Modify this block to have the correct number of inputs. * @private * @this Blockly.Block */ updateShape_: function () { // Delete everything. if (this.getInput("ELSE")) { this.removeInput("ELSE"); } var j = 1; while (this.getInput("IF" + j)) { this.removeInput("IF" + j); this.removeInput("DO" + j); j++; } // Rebuild block. for (var i = 1; i <= this.elseifCount_; i++) { this.appendValueInput("IF" + i) .setCheck(Types.getCompatibleTypes("boolean")) .appendField(Blockly.Msg.CONTROLS_IF_MSG_ELSEIF); this.appendStatementInput("DO" + i).appendField( Blockly.Msg.CONTROLS_IF_MSG_THEN ); } if (this.elseCount_) { this.appendStatementInput("ELSE").appendField( Blockly.Msg.CONTROLS_IF_MSG_ELSE ); } }, }; Blockly.Blocks["controls_if_if"] = { /** * Mutator block for if container. * @this Blockly.Block */ init: function () { this.setColour(getColour().logic); this.appendDummyInput().appendField(Blockly.Msg.CONTROLS_IF_IF_TITLE_IF); this.setNextStatement(true); this.setTooltip(Blockly.Msg.CONTROLS_IF_IF_TOOLTIP); this.contextMenu = false; }, }; Blockly.Blocks["controls_if_elseif"] = { /** * Mutator bolck for else-if condition. * @this Blockly.Block */ init: function () { this.setColour(getColour().logic); this.appendDummyInput().appendField( Blockly.Msg.CONTROLS_IF_ELSEIF_TITLE_ELSEIF ); this.setPreviousStatement(true); this.setNextStatement(true); this.setTooltip(Blockly.Msg.CONTROLS_IF_ELSEIF_TOOLTIP); this.contextMenu = false; }, }; Blockly.Blocks["controls_if_else"] = { /** * Mutator block for else condition. * @this Blockly.Block */ init: function () { this.setColour(getColour().logic); this.appendDummyInput().appendField( Blockly.Msg.CONTROLS_IF_ELSE_TITLE_ELSE ); this.setPreviousStatement(true); this.setTooltip(Blockly.Msg.CONTROLS_IF_ELSE_TOOLTIP); this.contextMenu = false; }, }; Blockly.defineBlocksWithJsonArray([ // BEGIN JSON EXTRACT // Block for boolean data type: true and false. { type: "logic_boolean", message0: "%1", args0: [ { type: "field_dropdown", name: "BOOL", options: [ ["%{BKY_LOGIC_BOOLEAN_TRUE}", "TRUE"], ["%{BKY_LOGIC_BOOLEAN_FALSE}", "FALSE"], ], }, ], output: Types.BOOLEAN.typeName, style: "logic_blocks", tooltip: "%{BKY_LOGIC_BOOLEAN_TOOLTIP}", helpUrl: "%{BKY_LOGIC_BOOLEAN_HELPURL}", }, { type: "controls_ifelse", message0: "%{BKY_CONTROLS_IF_MSG_IF} %1", args0: [ { type: "input_value", name: "IF0", check: Types.getCompatibleTypes("boolean"), }, ], message1: "%{BKY_CONTROLS_IF_MSG_THEN} %1", args1: [ { type: "input_statement", name: "DO0", }, ], message2: "%{BKY_CONTROLS_IF_MSG_ELSE} %1", args2: [ { type: "input_statement", name: "ELSE", }, ], previousStatement: null, nextStatement: null, style: "logic_blocks", tooltip: "%{BKYCONTROLS_IF_TOOLTIP_2}", helpUrl: "%{BKY_CONTROLS_IF_HELPURL}", extensions: ["controls_if_tooltip"], }, // Block for comparison operator. { type: "logic_compare", message0: "%1 %2 %3", args0: [ { type: "input_value", name: "A", }, { type: "field_dropdown", name: "OP", options: [ ["=", "EQ"], ["\u2260", "NEQ"], ["\u200F<", "LT"], ["\u200F\u2264", "LTE"], ["\u200F>", "GT"], ["\u200F\u2265", "GTE"], ], }, { type: "input_value", name: "B", }, ], inputsInline: true, output: Types.BOOLEAN.typeName, style: "logic_blocks", helpUrl: "%{BKY_LOGIC_COMPARE_HELPURL}", extensions: ["logic_compare", "logic_op_tooltip"], }, // Block for logical operations: 'and', 'or'. { type: "logic_operation", message0: "%1 %2 %3", args0: [ { type: "input_value", name: "A", check: Types.getCompatibleTypes("boolean"), }, { type: "field_dropdown", name: "OP", options: [ ["%{BKY_LOGIC_OPERATION_AND}", "AND"], ["%{BKY_LOGIC_OPERATION_OR}", "OR"], ], }, { type: "input_value", name: "B", check: Types.getCompatibleTypes("boolean"), }, ], inputsInline: true, output: Types.BOOLEAN.typeName, style: "logic_blocks", helpUrl: "%{BKY_LOGIC_OPERATION_HELPURL}", extensions: ["logic_op_tooltip"], }, // Block for negation. { type: "logic_negate", message0: "%{BKY_LOGIC_NEGATE_TITLE}", args0: [ { type: "input_value", name: "BOOL", check: Types.getCompatibleTypes("boolean"), }, ], output: Types.BOOLEAN.typeName, style: "logic_blocks", tooltip: "%{BKY_LOGIC_NEGATE_TOOLTIP}", helpUrl: "%{BKY_LOGIC_NEGATE_HELPURL}", }, // Block for null data type. { type: "logic_null", message0: "%{BKY_LOGIC_NULL}", output: null, style: "logic_blocks", tooltip: "%{BKY_LOGIC_NULL_TOOLTIP}", helpUrl: "%{BKY_LOGIC_NULL_HELPURL}", }, // Block for ternary operator. { type: "logic_ternary", message0: "%{BKY_LOGIC_TERNARY_CONDITION} %1", args0: [ { type: "input_value", name: "IF", check: Types.getCompatibleTypes("boolean"), }, ], message1: "%{BKY_LOGIC_TERNARY_IF_TRUE} %1", args1: [ { type: "input_value", name: "THEN", check: Types.getCompatibleTypes("boolean"), }, ], message2: "%{BKY_LOGIC_TERNARY_IF_FALSE} %1", args2: [ { type: "input_value", name: "ELSE", check: Types.getCompatibleTypes("boolean"), }, ], output: null, style: "logic_blocks", tooltip: "%{BKY_LOGIC_TERNARY_TOOLTIP}", helpUrl: "%{BKY_LOGIC_TERNARY_HELPURL}", extensions: ["logic_ternary"], }, ]); // END JSON EXTRACT (Do not delete this comment.) Blockly.Blocks["logic_compare"] = { /** * Block for comparison operator. * @this Blockly.Block */ init: function () { var OPERATORS = this.RTL ? [ ["=", "EQ"], ["\u2260", "NEQ"], [">", "LT"], ["\u2265", "LTE"], ["<", "GT"], ["\u2264", "GTE"], ] : [ ["=", "EQ"], ["\u2260", "NEQ"], ["<", "LT"], ["\u2264", "LTE"], [">", "GT"], ["\u2265", "GTE"], ]; this.setHelpUrl(Blockly.Msg.LOGIC_COMPARE_HELPURL); this.setColour(getColour().logic); this.setOutput(true, Types.BOOLEAN.typeName); this.appendValueInput("A"); this.appendValueInput("B").appendField( new Blockly.FieldDropdown(OPERATORS), "OP" ); this.setInputsInline(true); // Assign 'this' to a variable for use in the tooltip closure below. var thisBlock = this; this.setTooltip(function () { var op = thisBlock.getFieldValue("OP"); var TOOLTIPS = { EQ: Blockly.Msg.LOGIC_COMPARE_TOOLTIP_EQ, NEQ: Blockly.Msg.LOGIC_COMPARE_TOOLTIP_NEQ, LT: Blockly.Msg.LOGIC_COMPARE_TOOLTIP_LT, LTE: Blockly.Msg.LOGIC_COMPARE_TOOLTIP_LTE, GT: Blockly.Msg.LOGIC_COMPARE_TOOLTIP_GT, GTE: Blockly.Msg.LOGIC_COMPARE_TOOLTIP_GTE, }; return TOOLTIPS[op]; }); }, /** * Called whenever anything on the workspace changes. * Prevent mismatched types from being compared. * @param {!Blockly.Events.Abstract} e Change event. * @this Blockly.Block */ onchange: function (e) { var blockA = this.getInputTargetBlock("A"); var blockB = this.getInputTargetBlock("B"); if (blockA === null && blockB === null) { this.getInput("A").setCheck(null); this.getInput("B").setCheck(null); } if (blockA !== null && blockB === null) { this.getInput("A").setCheck( getCompatibleTypes(blockA.outputConnection.check_[0]) ); this.getInput("B").setCheck( getCompatibleTypes(blockA.outputConnection.check_[0]) ); } if (blockB !== null && blockA === null) { this.getInput("B").setCheck( getCompatibleTypes(blockB.outputConnection.check_[0]) ); this.getInput("A").setCheck( getCompatibleTypes(blockB.outputConnection.check_[0]) ); } }, }; Blockly.Blocks["switch_case"] = { init: function () { this.setColour(getColour().logic); this.setPreviousStatement(true); this.setTooltip(Blockly.Msg.cases_tooltip); this.setNextStatement(true); this.appendValueInput("CONDITION").appendField(Blockly.Msg.cases_switch); this.appendValueInput("CASECONDITION0").appendField( Blockly.Msg.cases_condition ); this.appendStatementInput("CASE0").appendField(Blockly.Msg.cases_do); this.setMutator(new Blockly.Mutator(["case_incaseof", "case_default"])); this.caseCount_ = 0; this.defaultCount_ = 0; }, mutationToDom: function () { if (!this.caseCount_ && !this.defaultCount_) { return null; } var container = document.createElement("mutation"); if (this.caseCount_) { container.setAttribute("case", this.caseCount_); } if (this.defaultCount_) { container.setAttribute("default", 1); } return container; }, domToMutation: function (xmlElement) { this.caseCount_ = parseInt(xmlElement.getAttribute("case"), 10); this.defaultCount_ = parseInt(xmlElement.getAttribute("default"), 10); for (var x = 0; x <= this.caseCount_; x++) { this.appendValueInput("CASECONDITION" + x).appendField( Blockly.Msg.cases_condition ); this.appendStatementInput("CASE" + x).appendField(Blockly.Msg.cases_do); } if (this.defaultCount_) { this.appendStatementInput("ONDEFAULT").appendField("default"); } }, decompose: function (workspace) { var containerBlock = workspace.newBlock("control_case"); containerBlock.initSvg(); var connection = containerBlock.getInput("STACK").connection; for (var x = 1; x <= this.caseCount_; x++) { var caseBlock = workspace.newBlock("case_incaseof"); caseBlock.initSvg(); connection.connect(caseBlock.previousConnection); connection = caseBlock.nextConnection; } if (this.defaultCount_) { var defaultBlock = Blockly.Block.obtain(workspace, "case_default"); defaultBlock.initSvg(); connection.connect(defaultBlock.previousConnection); } return containerBlock; }, compose: function (containerBlock) { //Disconnect all input blocks and remove all inputs. if (this.defaultCount_) { this.removeInput("ONDEFAULT"); } this.defaultCount_ = 0; for (var x = this.caseCount_; x > 0; x--) { this.removeInput("CASECONDITION" + x); this.removeInput("CASE" + x); } this.caseCount_ = 0; var caseBlock = containerBlock.getInputTargetBlock("STACK"); while (caseBlock) { switch (caseBlock.type) { case "case_incaseof": this.caseCount_++; var caseconditionInput = this.appendValueInput( "CASECONDITION" + this.caseCount_ ).appendField(Blockly.Msg.cases_condition); var caseInput = this.appendStatementInput( "CASE" + this.caseCount_ ).appendField(Blockly.Msg.cases_do); if (caseBlock.valueConnection_) { caseconditionInput.connection.connect(caseBlock.valueConnection_); } if (caseBlock.statementConnection_) { caseInput.connection.connect(caseBlock.statementConnection_); } break; case "case_default": this.defaultCount_++; var defaultInput = this.appendStatementInput("ONDEFAULT").appendField("default"); if (caseBlock.statementConnection_) { defaultInput.connection.connect(caseBlock.statementConnection_); } break; default: throw new Error("Unknown block type."); } caseBlock = caseBlock.nextConnection && caseBlock.nextConnection.targetBlock(); } }, saveConnections: function (containerBlock) { var caseBlock = containerBlock.getInputTargetBlock("STACK"); var x = 1; while (caseBlock) { switch (caseBlock.type) { case "case_incaseof": var caseconditionInput = this.getInput("CASECONDITION" + x); var caseInput = this.getInput("CASE" + x); caseBlock.valueConnection_ = caseconditionInput && caseconditionInput.connection.targetConnection; caseBlock.statementConnection_ = caseInput && caseInput.connection.targetConnection; x++; break; case "case_default": var defaultInput = this.getInput("ONDEFAULT"); caseBlock.satementConnection_ = defaultInput && defaultInput.connection.targetConnection; break; default: throw new Error("Unknown block type"); } caseBlock = caseBlock.nextConnection && caseBlock.nextConnection.targetBlock(); } }, }; Blockly.Blocks["control_case"] = { init: function () { this.setColour(getColour().logic); this.appendDummyInput().appendField(Blockly.Msg.cases_switch); this.appendStatementInput("STACK"); this.setTooltip("--Placeholder--"); this.contextMenu = false; }, }; Blockly.Blocks["case_incaseof"] = { init: function () { this.setColour(getColour().logic); this.appendDummyInput().appendField(Blockly.Msg.cases_add); this.setPreviousStatement(true); this.setNextStatement(true); this.setTooltip("--Placeholder--"); this.contextMenu = false; }, }; Blockly.Blocks["case_default"] = { init: function () { this.setColour(getColour().logic); this.appendValueInput("default").appendField("default"); this.setPreviousStatement(true); this.setNextStatement(false); this.setTooltip( "This function will run if there aren't any matching cases." ); this.contextMenu = false; }, };