2021-10-14 13:38:48 +02:00

652 lines
19 KiB
JavaScript

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;
},
};