812 lines
22 KiB
JavaScript
812 lines
22 KiB
JavaScript
import * as Blockly from "blockly";
|
|
import * as BlocklyJS from "blockly/javascript";
|
|
|
|
const ARG_BLOCK_TYPE = "FunctionsArgumentBlock";
|
|
|
|
class CustomChecker extends Blockly.ConnectionChecker {
|
|
canConnect(a, b, isDragging, opt_distance) {
|
|
if (!isDragging) {
|
|
return super.canConnect(a, b, isDragging, opt_distance);
|
|
}
|
|
|
|
const existing = b.targetConnection && b.targetConnection.getSourceBlock();
|
|
|
|
if (
|
|
existing &&
|
|
existing.type === "functions_argument_block" &&
|
|
existing.isShadow()
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
return super.canConnect(a, b, isDragging, opt_distance);
|
|
}
|
|
}
|
|
|
|
Blockly.registry.register(
|
|
Blockly.registry.Type.CONNECTION_CHECKER,
|
|
"CustomChecker",
|
|
CustomChecker,
|
|
true
|
|
);
|
|
|
|
class DuplicateOnDrag {
|
|
constructor(block) {
|
|
this.block = block;
|
|
}
|
|
|
|
isMovable() {
|
|
return true;
|
|
}
|
|
|
|
startDrag(e) {
|
|
const ws = this.block.workspace;
|
|
|
|
let typeToCreate = this.block.type;
|
|
if (this.block.argType_ === "statement") {
|
|
typeToCreate = "functions_statement_argument_block";
|
|
}
|
|
|
|
let data = this.block.toCopyData();
|
|
if (data?.blockState) {
|
|
data.blockState.type = typeToCreate;
|
|
} else {
|
|
data.blockState = { type: typeToCreate };
|
|
}
|
|
|
|
if (this.block.mutationToDom) {
|
|
const mutation = this.block.mutationToDom();
|
|
if (mutation) {
|
|
data.blockState.extraState = mutation.outerHTML;
|
|
}
|
|
}
|
|
|
|
this.copy = Blockly.clipboard.paste(data, ws);
|
|
this.baseStrat = new Blockly.dragging.BlockDragStrategy(this.copy);
|
|
this.copy.setDragStrategy(this.baseStrat);
|
|
this.baseStrat.startDrag(e);
|
|
}
|
|
|
|
drag(e) {
|
|
this.block.workspace
|
|
.getGesture(e)
|
|
.getCurrentDragger()
|
|
.setDraggable(this.copy);
|
|
this.baseStrat.drag(e);
|
|
}
|
|
|
|
endDrag(e) {
|
|
this.baseStrat?.endDrag(e);
|
|
}
|
|
|
|
revertDrag(e) {
|
|
this.copy?.dispose();
|
|
}
|
|
}
|
|
|
|
function typeToBlocklyCheck(type) {
|
|
return (
|
|
{
|
|
string: "String",
|
|
number: "Number",
|
|
boolean: "Boolean",
|
|
array: "Array",
|
|
object: "Object",
|
|
}[type] || null
|
|
);
|
|
}
|
|
|
|
function findDuplicateArgNames(types, names) {
|
|
const used = {};
|
|
const duplicates = [];
|
|
|
|
for (let i = 0; i < types.length; i++) {
|
|
const key = types[i] + ":" + names[i];
|
|
if (!names[i]) continue;
|
|
|
|
if (used[key]) duplicates.push(i);
|
|
else used[key] = true;
|
|
}
|
|
return duplicates;
|
|
}
|
|
|
|
function isValidIdentifier(name) {
|
|
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name);
|
|
}
|
|
|
|
Blockly.Blocks["functions_argument_block"] = {
|
|
init() {
|
|
if (!this.argType_) this.argType_ = "string";
|
|
if (!this.argName_) this.argName_ = "arg";
|
|
|
|
this.setStyle("procedure_blocks");
|
|
this.appendDummyInput().appendField(
|
|
new Blockly.FieldLabel(this.argName_),
|
|
"ARG_NAME"
|
|
);
|
|
|
|
this.setOutput(true, null);
|
|
this.setMovable(true);
|
|
this.setDeletable(true);
|
|
|
|
setTimeout(() => {
|
|
if (this.setDragStrategy && this.isShadow()) {
|
|
this.setDragStrategy(new DuplicateOnDrag(this));
|
|
}
|
|
});
|
|
},
|
|
|
|
mutationToDom: function () {
|
|
const container = Blockly.utils.xml.createElement("mutation");
|
|
container.setAttribute("type", this.argType_ || "string");
|
|
container.setAttribute("name", this.argName_ || "arg");
|
|
return container;
|
|
},
|
|
|
|
domToMutation: function (xmlElement) {
|
|
const type = xmlElement.getAttribute("type") || "string";
|
|
const name = xmlElement.getAttribute("name") || "arg";
|
|
this.updateType_(type);
|
|
this.updateName_(name);
|
|
},
|
|
|
|
updateType_: function (type) {
|
|
this.argType_ = type;
|
|
if (type === "statement") {
|
|
this.setOutputShape(3);
|
|
this.setOutput(true, ARG_BLOCK_TYPE);
|
|
} else {
|
|
const outputType = typeToBlocklyCheck(type) || "String";
|
|
this.setOutput(true, [outputType, ARG_BLOCK_TYPE]);
|
|
}
|
|
},
|
|
|
|
updateName_: function (name) {
|
|
this.argName_ = name;
|
|
if (this.getField("ARG_NAME")) {
|
|
this.setFieldValue(name, "ARG_NAME");
|
|
} else {
|
|
this.appendDummyInput().appendField(
|
|
new Blockly.FieldLabel(name),
|
|
"ARG_NAME"
|
|
);
|
|
}
|
|
},
|
|
};
|
|
|
|
Blockly.Blocks["functions_statement_argument_block"] = {
|
|
init() {
|
|
if (!this.argName_) this.argName_ = "arg";
|
|
|
|
this.setStyle("procedure_blocks");
|
|
this.appendDummyInput().appendField(
|
|
new Blockly.FieldLabel(this.argName_),
|
|
"ARG_NAME"
|
|
);
|
|
|
|
this.setNextStatement(true, "default");
|
|
this.setPreviousStatement(true, "default");
|
|
},
|
|
|
|
mutationToDom: function () {
|
|
const container = Blockly.utils.xml.createElement("mutation");
|
|
container.setAttribute("name", this.argName_ || "arg");
|
|
return container;
|
|
},
|
|
|
|
domToMutation: function (xmlElement) {
|
|
const name = xmlElement.getAttribute("name") || "arg";
|
|
this.updateName_(name);
|
|
},
|
|
|
|
updateName_: function (name) {
|
|
this.argName_ = name;
|
|
if (this.getField("ARG_NAME")) {
|
|
this.setFieldValue(name, "ARG_NAME");
|
|
} else {
|
|
this.appendDummyInput().appendField(
|
|
new Blockly.FieldLabel(name),
|
|
"ARG_NAME"
|
|
);
|
|
}
|
|
},
|
|
};
|
|
|
|
Blockly.Blocks["functions_definition"] = {
|
|
init: function () {
|
|
this.setStyle("procedure_blocks");
|
|
this.setTooltip("Function definition with a variable number of inputs.");
|
|
this.setInputsInline(true);
|
|
|
|
this.functionId_ = Blockly.utils.idGenerator.genUid();
|
|
this.itemCount_ = 0;
|
|
this.argTypes_ = [];
|
|
this.argNames_ = [];
|
|
this.blockShape_ = "statement";
|
|
this.returnTypes_ = [];
|
|
|
|
this.updateShape_();
|
|
this.setMutator(
|
|
new Blockly.icons.MutatorIcon(["functions_args_generic"], this)
|
|
);
|
|
},
|
|
|
|
mutationToDom: function () {
|
|
const container = Blockly.utils.xml.createElement("mutation");
|
|
container.setAttribute("functionid", this.functionId_);
|
|
container.setAttribute("items", String(this.itemCount_));
|
|
container.setAttribute("shape", this.blockShape_ || "statement");
|
|
|
|
for (let i = 0; i < this.itemCount_; i++) {
|
|
const item = Blockly.utils.xml.createElement("item");
|
|
item.setAttribute("type", this.argTypes_[i]);
|
|
item.setAttribute("name", this.argNames_[i]);
|
|
container.appendChild(item);
|
|
}
|
|
|
|
return container;
|
|
},
|
|
|
|
domToMutation: function (xmlElement) {
|
|
const items = xmlElement.getAttribute("items");
|
|
this.itemCount_ = items ? parseInt(items, 10) : 0;
|
|
this.argTypes_ = [];
|
|
this.argNames_ = [];
|
|
|
|
const children = [...xmlElement.children].filter(
|
|
n => n.tagName.toLowerCase() === "item"
|
|
);
|
|
for (let i = 0; i < children.length; i++) {
|
|
this.argTypes_[i] = children[i].getAttribute("type");
|
|
this.argNames_[i] = children[i].getAttribute("name");
|
|
}
|
|
|
|
while (this.argTypes_.length < this.itemCount_)
|
|
this.argTypes_.push("label");
|
|
while (this.argNames_.length < this.itemCount_) this.argNames_.push("text");
|
|
|
|
this.functionId_ =
|
|
xmlElement.getAttribute("functionid") ||
|
|
Blockly.utils.idGenerator.genUid();
|
|
this.blockShape_ = xmlElement.getAttribute("shape") || "statement";
|
|
this.updateShape_();
|
|
},
|
|
|
|
saveExtraState: function () {
|
|
return {
|
|
functionId: this.functionId_,
|
|
itemCount: this.itemCount_,
|
|
argTypes: this.argTypes_,
|
|
argNames: this.argNames_,
|
|
shape: this.blockShape_,
|
|
returnTypes: this.returnTypes_,
|
|
};
|
|
},
|
|
|
|
loadExtraState: function (state) {
|
|
this.functionId_ = state.functionId || Blockly.utils.idGenerator.genUid();
|
|
this.itemCount_ = state.itemCount || 0;
|
|
this.argTypes_ = state.argTypes || [];
|
|
this.argNames_ = state.argNames || [];
|
|
this.blockShape_ = state.shape || "statement";
|
|
this.returnTypes_ = state.returnTypes || [];
|
|
this.updateShape_();
|
|
},
|
|
|
|
createDefaultArgBlock_: function (type, name = "arg") {
|
|
Blockly.Events.disable();
|
|
|
|
let block;
|
|
try {
|
|
const ws = this.workspace;
|
|
block = ws.newBlock("functions_argument_block");
|
|
block.setShadow(true);
|
|
block.setEditable(false);
|
|
block.updateType_(type);
|
|
block.updateName_(name);
|
|
|
|
if (ws?.rendered) {
|
|
block.initSvg();
|
|
block.render();
|
|
}
|
|
} catch (_) {}
|
|
|
|
Blockly.Events.enable();
|
|
return block;
|
|
},
|
|
|
|
updateShape_: function () {
|
|
let savedBody = null;
|
|
|
|
const bodyInput = this.getInput("BODY");
|
|
if (bodyInput && bodyInput.connection?.targetConnection) {
|
|
savedBody = bodyInput.connection.targetConnection;
|
|
}
|
|
|
|
if (bodyInput) this.removeInput("BODY");
|
|
if (this.getInput("EMPTY")) this.removeInput("EMPTY");
|
|
if (this.getInput("SHAPE")) this.removeInput("SHAPE");
|
|
|
|
[...this.inputList].forEach(input => {
|
|
const connection = input.connection?.targetConnection;
|
|
if (connection) connection.getSourceBlock()?.dispose(false);
|
|
this.removeInput(input.name);
|
|
});
|
|
|
|
let firstArgAdded = this.argTypes_[0] === "label";
|
|
|
|
for (let i = 0; i < this.itemCount_; i++) {
|
|
const type = this.argTypes_[i];
|
|
const name = this.argNames_[i];
|
|
|
|
if (type === "label") {
|
|
this.appendDummyInput().appendField(new Blockly.FieldLabel(name));
|
|
} else {
|
|
const input = this.appendValueInput(name).setCheck(
|
|
typeToBlocklyCheck(type)
|
|
);
|
|
|
|
if (!firstArgAdded) {
|
|
input.appendField("my block with");
|
|
firstArgAdded = true;
|
|
}
|
|
|
|
const reporter = this.createDefaultArgBlock_(type, name);
|
|
reporter.setFieldValue(name, "ARG_NAME");
|
|
|
|
try {
|
|
reporter.outputConnection.connect(input.connection);
|
|
} catch (e) {}
|
|
}
|
|
}
|
|
|
|
if (this.itemCount_ === 0) {
|
|
this.appendDummyInput("EMPTY").appendField("my block");
|
|
}
|
|
|
|
const newBody = this.appendStatementInput("BODY").setCheck("default");
|
|
if (savedBody) {
|
|
try {
|
|
newBody.connection.connect(savedBody);
|
|
} catch (e) {}
|
|
}
|
|
},
|
|
|
|
decompose: function (workspace) {
|
|
const containerBlock = workspace.newBlock("functions_args_container");
|
|
if (workspace.rendered) containerBlock.initSvg();
|
|
let connection = containerBlock.getInput("STACK").connection;
|
|
|
|
for (let i = 0; i < this.itemCount_; i++) {
|
|
const type = this.argTypes_[i] || "label";
|
|
const name = this.argNames_[i] || "text";
|
|
const itemBlock = workspace.newBlock("functions_args_generic");
|
|
itemBlock.setFieldValue(type, "ARG_TYPE");
|
|
itemBlock.setFieldValue(name, "ARG_NAME");
|
|
if (workspace.rendered) itemBlock.initSvg();
|
|
itemBlock.valueConnection_ = null;
|
|
|
|
connection.connect(itemBlock.previousConnection);
|
|
connection = itemBlock.nextConnection;
|
|
}
|
|
|
|
containerBlock.setFieldValue(this.blockShape_, "SHAPEMENU");
|
|
|
|
return containerBlock;
|
|
},
|
|
|
|
compose: function (containerBlock) {
|
|
const newTypes = [];
|
|
const newNames = [];
|
|
|
|
let itemBlock = containerBlock.getInputTargetBlock("STACK");
|
|
while (itemBlock) {
|
|
if (!(itemBlock.isInsertionMarker && itemBlock.isInsertionMarker())) {
|
|
const type = itemBlock.getFieldValue("ARG_TYPE");
|
|
const name = itemBlock.getFieldValue("ARG_NAME");
|
|
newTypes.push(type);
|
|
newNames.push(name);
|
|
}
|
|
itemBlock = itemBlock.getNextBlock();
|
|
}
|
|
|
|
const dups = findDuplicateArgNames(newTypes, newNames);
|
|
|
|
const invalid = [];
|
|
for (let i = 0; i < newTypes.length; i++) {
|
|
const type = newTypes[i];
|
|
const name = newNames[i];
|
|
if (type !== "label") {
|
|
if (!isValidIdentifier(name)) {
|
|
invalid.push(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
itemBlock = containerBlock.getInputTargetBlock("STACK");
|
|
let index = 0;
|
|
while (itemBlock) {
|
|
if (!(itemBlock.isInsertionMarker && itemBlock.isInsertionMarker())) {
|
|
if (dups.includes(index)) {
|
|
itemBlock.setWarningText(
|
|
"This argument name is already used for this type."
|
|
);
|
|
} else if (invalid.includes(index)) {
|
|
itemBlock.setWarningText("This argument name is not a valid.");
|
|
} else {
|
|
itemBlock.setWarningText(null);
|
|
}
|
|
|
|
index++;
|
|
}
|
|
itemBlock = itemBlock.getNextBlock();
|
|
}
|
|
|
|
const newBlockShape =
|
|
containerBlock.getFieldValue("SHAPEMENU") || "statement";
|
|
|
|
if (dups.length > 0 || invalid.length > 0) return;
|
|
|
|
this.itemCount_ = newTypes.length;
|
|
this.argTypes_ = newTypes;
|
|
this.argNames_ = newNames;
|
|
this.blockShape_ = newBlockShape;
|
|
|
|
this.updateShape_();
|
|
},
|
|
|
|
saveConnections: function (containerBlock) {
|
|
let itemBlock = containerBlock.getInputTargetBlock("STACK");
|
|
let i = 0;
|
|
while (itemBlock) {
|
|
if (!(itemBlock.isInsertionMarker && itemBlock.isInsertionMarker())) {
|
|
const key = this.argTypes_[i] + "_" + this.argNames_[i];
|
|
const input = this.getInput(key);
|
|
itemBlock.valueConnection_ =
|
|
input && input.connection && input.connection.targetConnection;
|
|
i++;
|
|
}
|
|
itemBlock = itemBlock.getNextBlock();
|
|
}
|
|
},
|
|
|
|
updateReturnState_: function () {
|
|
const body = this.getInputTargetBlock("BODY");
|
|
const types = new Set();
|
|
|
|
function walk(block) {
|
|
if (!block) return;
|
|
|
|
if (block?.childBlocks_?.length > 0) block?.childBlocks_.forEach(walk);
|
|
|
|
if (block.type === "functions_return") {
|
|
const val = block.getInputTargetBlock("VALUE");
|
|
const checks = val?.outputConnection?.check;
|
|
if (checks !== undefined) {
|
|
(Array.isArray(checks) ? checks : [checks]).forEach(t =>
|
|
types.add(t)
|
|
);
|
|
}
|
|
}
|
|
|
|
walk(block.getNextBlock());
|
|
}
|
|
walk(body);
|
|
|
|
if (types.size === 0) this.returnTypes_ = [];
|
|
else this.returnTypes_ = [...types];
|
|
},
|
|
};
|
|
|
|
Blockly.Blocks["functions_args_container"] = {
|
|
init: function () {
|
|
this.setStyle("procedure_blocks");
|
|
this.appendDummyInput().appendField("arguments");
|
|
this.appendStatementInput("STACK");
|
|
this.appendDummyInput()
|
|
.appendField("shape")
|
|
.appendField(
|
|
new Blockly.FieldDropdown([
|
|
[
|
|
{
|
|
src: "icons/statement.svg",
|
|
width: 98 * 0.6,
|
|
height: 57 * 0.6,
|
|
alt: "A block with top and bottom connections",
|
|
},
|
|
"statement",
|
|
],
|
|
[
|
|
{
|
|
src: "icons/terminal.svg",
|
|
width: 98 * 0.6,
|
|
height: 48 * 0.6,
|
|
alt: "A block with only a top connection",
|
|
},
|
|
"terminal",
|
|
],
|
|
]),
|
|
"SHAPEMENU"
|
|
);
|
|
this.contextMenu = false;
|
|
},
|
|
};
|
|
|
|
Blockly.Blocks["functions_args_generic"] = {
|
|
init() {
|
|
this.setStyle("procedure_blocks");
|
|
|
|
this.appendDummyInput()
|
|
.appendField("argument")
|
|
.appendField(
|
|
new Blockly.FieldDropdown([
|
|
["label", "label"],
|
|
["string", "string"],
|
|
["number", "number"],
|
|
["boolean", "boolean"],
|
|
["array", "array"],
|
|
["object", "object"],
|
|
["statement", "statement"],
|
|
]),
|
|
"ARG_TYPE"
|
|
)
|
|
.appendField(new Blockly.FieldTextInput("arg"), "ARG_NAME");
|
|
|
|
this.setPreviousStatement(true);
|
|
this.setNextStatement(true);
|
|
this.contextMenu = false;
|
|
this.valueConnection_ = null;
|
|
},
|
|
};
|
|
|
|
Blockly.Blocks["functions_call"] = {
|
|
init: function () {
|
|
this.setStyle("procedure_blocks");
|
|
this.setInputsInline(true);
|
|
|
|
this.functionId_ = null;
|
|
this.blockShape_ = null;
|
|
this.argTypes_ = [];
|
|
this.argNames_ = [];
|
|
this.previousArgTypes_ = [];
|
|
this.previousArgNames_ = [];
|
|
this.returnTypes_ = [];
|
|
|
|
this.updateShape_();
|
|
},
|
|
|
|
mutationToDom: function () {
|
|
const container = Blockly.utils.xml.createElement("mutation");
|
|
container.setAttribute("functionid", this.functionId_);
|
|
container.setAttribute("items", this.argTypes_.length);
|
|
container.setAttribute("shape", this.blockShape_ || "statement");
|
|
container.setAttribute(
|
|
"returntypes",
|
|
JSON.stringify(this.returnTypes_ || [])
|
|
);
|
|
|
|
for (let i = 0; i < this.argTypes_.length; i++) {
|
|
const item = Blockly.utils.xml.createElement("item");
|
|
item.setAttribute("type", this.argTypes_[i]);
|
|
item.setAttribute("name", this.argNames_[i]);
|
|
container.appendChild(item);
|
|
}
|
|
|
|
return container;
|
|
},
|
|
|
|
domToMutation: function (xmlElement) {
|
|
this.functionId_ = xmlElement.getAttribute("functionid");
|
|
this.blockShape_ = xmlElement.getAttribute("shape") || "statement";
|
|
this.previousArgTypes_ = [...this.argTypes_];
|
|
this.previousArgNames_ = [...this.argNames_];
|
|
this.argTypes_ = [];
|
|
this.argNames_ = [];
|
|
|
|
this.returnTypes_;
|
|
try {
|
|
this.returnTypes_ = JSON.parse(
|
|
xmlElement.getAttribute("returntypes") || "[]"
|
|
);
|
|
} catch {
|
|
this.returnTypes_ = [];
|
|
}
|
|
|
|
const items = parseInt(xmlElement.getAttribute("items") || "0", 10);
|
|
for (let i = 0; i < items; i++) {
|
|
const item = xmlElement.children[i];
|
|
this.argTypes_[i] = item.getAttribute("type");
|
|
this.argNames_[i] = item.getAttribute("name");
|
|
}
|
|
|
|
this.updateShape_();
|
|
},
|
|
|
|
matchDefinition: function (defBlock) {
|
|
this.functionId_ = defBlock.functionId_;
|
|
this.previousArgTypes_ = [...this.argTypes_];
|
|
this.previousArgNames_ = [...this.argNames_];
|
|
this.argTypes_ = [...defBlock.argTypes_];
|
|
this.argNames_ = [...defBlock.argNames_];
|
|
this.blockShape_ = defBlock.blockShape_;
|
|
this.returnTypes_ = [...defBlock.returnTypes_];
|
|
|
|
this.updateShape_();
|
|
if (defBlock.workspace.rendered) this.render();
|
|
},
|
|
|
|
updateShape_: function () {
|
|
const oldConnections = {};
|
|
|
|
[...this.inputList].forEach(input => {
|
|
if (input.connection && input.connection.targetBlock()) {
|
|
oldConnections[input.name] = input.connection.targetConnection;
|
|
}
|
|
this.removeInput(input.name);
|
|
});
|
|
|
|
const shape = this.blockShape_ || "statement";
|
|
const nextConn = this.nextConnection;
|
|
const prevConn = this.previousConnection;
|
|
const outputConn = this.outputConnection;
|
|
const returnTypes = this.returnTypes_ || [];
|
|
|
|
if (returnTypes?.length > 0) {
|
|
if (prevConn && prevConn.isConnected()) {
|
|
const blockAbove = prevConn.targetBlock();
|
|
blockAbove.unplug(true);
|
|
}
|
|
if (nextConn && nextConn.isConnected()) {
|
|
const blockBelow = nextConn.targetBlock();
|
|
blockBelow.unplug(true);
|
|
}
|
|
|
|
this.setPreviousStatement(false);
|
|
this.setNextStatement(false);
|
|
this.setOutput(true, returnTypes);
|
|
} else {
|
|
if (outputConn && outputConn.isConnected()) {
|
|
outputConn.disconnect();
|
|
}
|
|
|
|
if (shape === "statement") {
|
|
this.setPreviousStatement(true, "default");
|
|
this.setNextStatement(true, "default");
|
|
this.setOutput(false);
|
|
} else if (shape === "terminal") {
|
|
if (nextConn && nextConn.isConnected()) {
|
|
nextConn.targetBlock().unplug(true);
|
|
}
|
|
|
|
this.setNextStatement(false);
|
|
this.setPreviousStatement(true, "default");
|
|
this.setOutput(false);
|
|
}
|
|
}
|
|
|
|
if (!this.argTypes_ || this.argTypes_.length === 0) {
|
|
this.appendDummyInput("EMPTY").appendField("my block");
|
|
return;
|
|
}
|
|
|
|
let firstLabel = this.argTypes_[0] === "label";
|
|
|
|
for (let i = 0; i < this.argTypes_.length; i++) {
|
|
const type = this.argTypes_[i];
|
|
const name = this.argNames_[i];
|
|
|
|
if (!type || !name) continue;
|
|
|
|
if (type === "label") {
|
|
this.appendDummyInput().appendField(name);
|
|
continue;
|
|
}
|
|
|
|
if (!firstLabel) {
|
|
this.appendDummyInput().appendField("my block with");
|
|
firstLabel = true;
|
|
}
|
|
|
|
let input;
|
|
const key = type + "_" + name;
|
|
if (type === "statement") {
|
|
input = this.appendStatementInput(key).setCheck("default");
|
|
} else {
|
|
input = this.appendValueInput(key).setCheck(typeToBlocklyCheck(type));
|
|
}
|
|
|
|
if (oldConnections[key]) {
|
|
try {
|
|
input.connection.connect(
|
|
oldConnections[key].targetBlock()?.outputConnection ||
|
|
oldConnections[key]
|
|
);
|
|
} catch (e) {}
|
|
}
|
|
}
|
|
},
|
|
};
|
|
|
|
Blockly.Blocks["functions_return"] = {
|
|
init() {
|
|
this.setStyle("procedure_blocks");
|
|
this.appendValueInput("VALUE").appendField("return");
|
|
this.setPreviousStatement(true, "default");
|
|
this.setNextStatement(false);
|
|
this.setInputsInline(true);
|
|
},
|
|
|
|
update_() {
|
|
const def = this.getSurroundParent();
|
|
if (!def || def.type !== "functions_definition") return;
|
|
|
|
def.updateReturnState_();
|
|
def.workspace.updateAllFunctionCalls();
|
|
},
|
|
|
|
onchange(e) {
|
|
if (e.isUiEvent || e.isBlank) return;
|
|
|
|
this.update_();
|
|
},
|
|
};
|
|
|
|
BlocklyJS.javascriptGenerator.forBlock["functions_argument_block"] = block => [
|
|
block.argType_ + "_" + block.argName_,
|
|
BlocklyJS.Order.NONE,
|
|
];
|
|
|
|
BlocklyJS.javascriptGenerator.forBlock["functions_statement_argument_block"] =
|
|
block => "statement_" + block.argName_ + "();\n";
|
|
|
|
BlocklyJS.javascriptGenerator.forBlock["functions_definition"] = function (
|
|
block,
|
|
generator
|
|
) {
|
|
const params = block.argTypes_
|
|
.map((type, i) => {
|
|
if (type === "label") return null;
|
|
return type + "_" + block.argNames_[i];
|
|
})
|
|
.filter(Boolean);
|
|
|
|
const body = BlocklyJS.javascriptGenerator.statementToCode(block, "BODY");
|
|
return `MyFunctions[${generator.quote_(
|
|
block.functionId_
|
|
)}] = async (${params.join(", ")}) => {\n${body}};\n`;
|
|
};
|
|
|
|
BlocklyJS.javascriptGenerator.forBlock["functions_call"] = function (
|
|
block,
|
|
generator
|
|
) {
|
|
const args = [];
|
|
|
|
for (let i = 0; i < block.argTypes_.length; i++) {
|
|
const type = block.argTypes_[i];
|
|
const name = block.argNames_[i];
|
|
const key = `${type}_${name}`;
|
|
|
|
if (type === "label") continue;
|
|
|
|
if (type === "statement")
|
|
args.push(`async () => {${generator.statementToCode(block, key)}}`);
|
|
else
|
|
args.push(
|
|
generator.valueToCode(block, key, BlocklyJS.Order.NONE) || "null"
|
|
);
|
|
}
|
|
|
|
return `await MyFunctions[${generator.quote_(block.functionId_)}](${args.join(
|
|
", "
|
|
)});\n`;
|
|
};
|
|
|
|
BlocklyJS.javascriptGenerator.forBlock["functions_return"] = function (
|
|
block,
|
|
generator
|
|
) {
|
|
const value = generator.valueToCode(block, "VALUE", BlocklyJS.Order.NONE);
|
|
return `return ${value || "null"};\n`;
|
|
};
|