Init
This commit is contained in:
811
src/blocks/functions.js
Normal file
811
src/blocks/functions.js
Normal file
@@ -0,0 +1,811 @@
|
||||
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`;
|
||||
};
|
||||
Reference in New Issue
Block a user