Init
This commit is contained in:
187
src/blocks/control.js
Normal file
187
src/blocks/control.js
Normal file
@@ -0,0 +1,187 @@
|
||||
import * as Blockly from "blockly";
|
||||
import * as BlocklyJS from "blockly/javascript";
|
||||
|
||||
Blockly.Blocks["wait_one_frame"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput().appendField("wait one frame");
|
||||
this.setPreviousStatement(true, "default");
|
||||
this.setNextStatement(true, "default");
|
||||
this.setStyle("control_blocks");
|
||||
},
|
||||
};
|
||||
|
||||
Blockly.Blocks["wait_block"] = {
|
||||
init: function () {
|
||||
this.appendValueInput("AMOUNT").setCheck("Number").appendField("wait");
|
||||
this.appendDummyInput().appendField(
|
||||
new Blockly.FieldDropdown([
|
||||
["seconds", "1000"],
|
||||
["milliseconds", "1"],
|
||||
]),
|
||||
"MENU"
|
||||
);
|
||||
this.setPreviousStatement(true, "default");
|
||||
this.setNextStatement(true, "default");
|
||||
this.setStyle("control_blocks");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["wait_one_frame"] = function (block) {
|
||||
return `await waitOneFrame();\n`;
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["wait_block"] = function (
|
||||
block,
|
||||
generator
|
||||
) {
|
||||
const duration =
|
||||
generator.valueToCode(block, "AMOUNT", BlocklyJS.Order.ATOMIC) || 0;
|
||||
const menu = block.getFieldValue("MENU") || 0;
|
||||
return `await wait(${duration} * ${+menu});\n`;
|
||||
};
|
||||
|
||||
Blockly.Blocks["controls_thread_create"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput().appendField("create thread");
|
||||
this.appendStatementInput("code").setCheck("default");
|
||||
this.setTooltip("Create and run the code specified in a new thread");
|
||||
this.setPreviousStatement(true, "default");
|
||||
this.setNextStatement(true, "default");
|
||||
this.setStyle("control_blocks");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["controls_thread_create"] = function (
|
||||
block,
|
||||
generator
|
||||
) {
|
||||
const code = generator.statementToCode(block, "code");
|
||||
return `Thread.getCurrentContext().spawn(async () => {\n${code}});`;
|
||||
};
|
||||
|
||||
Blockly.Blocks["controls_thread_current"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput().appendField("current thread");
|
||||
this.setOutput(true, "ThreadID");
|
||||
this.setStyle("control_blocks");
|
||||
this.setTooltip("Return the ID of the currently running thread");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["controls_thread_current"] = () => [
|
||||
`Thread.getCurrentContext().id`,
|
||||
BlocklyJS.Order.MEMBER,
|
||||
];
|
||||
|
||||
Blockly.Blocks["controls_thread_set_var"] = {
|
||||
init: function () {
|
||||
this.appendValueInput("NAME")
|
||||
.setCheck("String")
|
||||
.appendField("set variable");
|
||||
this.appendValueInput("VALUE").appendField("to");
|
||||
this.appendValueInput("THREAD")
|
||||
.setCheck("ThreadID")
|
||||
.appendField("in thread");
|
||||
this.setPreviousStatement(true, "default");
|
||||
this.setNextStatement(true, "default");
|
||||
this.setInputsInline(true);
|
||||
this.setStyle("control_blocks");
|
||||
this.setTooltip("Set a variable inside the given thread");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["controls_thread_set_var"] = function (
|
||||
block,
|
||||
generator
|
||||
) {
|
||||
const threadId =
|
||||
generator.valueToCode(block, "THREAD", BlocklyJS.Order.NONE) || "null";
|
||||
const name =
|
||||
generator.valueToCode(block, "NAME", BlocklyJS.Order.NONE) || '""';
|
||||
const value =
|
||||
generator.valueToCode(block, "VALUE", BlocklyJS.Order.NONE) || "undefined";
|
||||
return `Thread.set(${threadId}, ${name}, ${value});\n`;
|
||||
};
|
||||
|
||||
Blockly.Blocks["controls_thread_get_var"] = {
|
||||
init: function () {
|
||||
this.appendValueInput("NAME")
|
||||
.setCheck("String")
|
||||
.appendField("get variable");
|
||||
this.appendValueInput("THREAD")
|
||||
.setCheck("ThreadID")
|
||||
.appendField("from thread");
|
||||
this.setInputsInline(true);
|
||||
this.setOutput(true, null);
|
||||
this.setStyle("control_blocks");
|
||||
this.setTooltip("Get a variable from the given thread");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["controls_thread_get_var"] = function (
|
||||
block,
|
||||
generator
|
||||
) {
|
||||
const threadId =
|
||||
generator.valueToCode(block, "THREAD", BlocklyJS.Order.NONE) || "null";
|
||||
const name =
|
||||
generator.valueToCode(block, "NAME", BlocklyJS.Order.NONE) || '""';
|
||||
const code = `Thread.get(${threadId}, ${name})`;
|
||||
return [code, BlocklyJS.Order.FUNCTION_CALL];
|
||||
};
|
||||
|
||||
Blockly.Blocks["controls_thread_has_var"] = {
|
||||
init: function () {
|
||||
this.appendValueInput("NAME").setCheck("String").appendField("variable");
|
||||
this.appendValueInput("THREAD")
|
||||
.setCheck("ThreadID")
|
||||
.appendField("exists in thread");
|
||||
this.setInputsInline(true);
|
||||
this.setOutput(true, null);
|
||||
this.setStyle("control_blocks");
|
||||
this.setTooltip("Checks if a variable exists in the given thread");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["controls_thread_has_var"] = function (
|
||||
block,
|
||||
generator
|
||||
) {
|
||||
const threadId =
|
||||
generator.valueToCode(block, "THREAD", BlocklyJS.Order.NONE) || "null";
|
||||
const name =
|
||||
generator.valueToCode(block, "NAME", BlocklyJS.Order.NONE) || '""';
|
||||
const code = `Thread.has(${threadId}, ${name})`;
|
||||
return [code, BlocklyJS.Order.FUNCTION_CALL];
|
||||
};
|
||||
|
||||
Blockly.Blocks["controls_run_instantly"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput().appendField("run instantly");
|
||||
this.appendStatementInput("do");
|
||||
this.setPreviousStatement(true);
|
||||
this.setNextStatement(true);
|
||||
this.setStyle("control_blocks");
|
||||
this.setTooltip("Run inside code without frame delay");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["controls_run_instantly"] = function (
|
||||
block
|
||||
) {
|
||||
const branch = BlocklyJS.javascriptGenerator.statementToCode(block, "do");
|
||||
return `let _prevFast = fastExecution;
|
||||
fastExecution = true;
|
||||
${branch}fastExecution = _prevFast;\n`;
|
||||
};
|
||||
|
||||
Blockly.Blocks["controls_stopscript"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput().appendField("stop this script");
|
||||
this.setPreviousStatement(true, "default");
|
||||
this.setStyle("control_blocks");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["controls_stopscript"] = () =>
|
||||
'throw new Error("shouldStop");\n';
|
||||
172
src/blocks/event.js
Normal file
172
src/blocks/event.js
Normal file
@@ -0,0 +1,172 @@
|
||||
import * as Blockly from "blockly";
|
||||
import * as BlocklyJS from "blockly/javascript";
|
||||
|
||||
Blockly.Blocks["when_flag_clicked"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput()
|
||||
.appendField("when")
|
||||
.appendField(
|
||||
new Blockly.FieldImage("icons/flag.svg", 25, 25, {
|
||||
alt: "Green flag",
|
||||
flipRtl: "FALSE",
|
||||
})
|
||||
)
|
||||
.appendField("clicked");
|
||||
this.appendStatementInput("DO").setCheck("default");
|
||||
this.setStyle("events_blocks");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["when_flag_clicked"] = function (
|
||||
block,
|
||||
generator
|
||||
) {
|
||||
const branch = generator.statementToCode(block, "DO");
|
||||
return `registerEvent("flag", null, async () => {\n${branch}});\n`;
|
||||
};
|
||||
|
||||
const normalKeys = [
|
||||
..."abcdefghijklmnopqrstuvwxyz",
|
||||
..."abcdefghijklmnopqrstuvwxyz0123456789".toUpperCase(),
|
||||
];
|
||||
|
||||
Blockly.Blocks["when_key_clicked"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput()
|
||||
.appendField("when")
|
||||
.appendField(
|
||||
new Blockly.FieldDropdown([
|
||||
["any", "any"],
|
||||
["space", " "],
|
||||
["enter", "Enter"],
|
||||
["escape", "Escape"],
|
||||
["up arrow", "ArrowUp"],
|
||||
["down arrow", "ArrowDown"],
|
||||
["left arrow", "ArrowLeft"],
|
||||
["right arrow", "ArrowRight"],
|
||||
...normalKeys.map((i) => [i, i]),
|
||||
]),
|
||||
"KEY"
|
||||
)
|
||||
.appendField("key pressed");
|
||||
this.appendStatementInput("DO").setCheck("default");
|
||||
this.setStyle("events_blocks");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["when_key_clicked"] = function (
|
||||
block,
|
||||
generator
|
||||
) {
|
||||
const key = block.getFieldValue("KEY");
|
||||
const safeKey = generator.quote_(key);
|
||||
const branch = generator.statementToCode(block, "DO");
|
||||
return `registerEvent("key", ${safeKey}, async () => {\n${branch}});\n`;
|
||||
};
|
||||
|
||||
Blockly.Blocks["when_stage_clicked"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput()
|
||||
.appendField("when stage clicked");
|
||||
this.appendStatementInput("DO").setCheck("default");
|
||||
this.setStyle("events_blocks");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["when_stage_clicked"] = function (
|
||||
block,
|
||||
generator
|
||||
) {
|
||||
const branch = generator.statementToCode(block, "DO");
|
||||
return `registerEvent("stageClick", null, async () => {\n${branch}});\n`;
|
||||
};
|
||||
|
||||
Blockly.Blocks["project_timer"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput().appendField("project timer");
|
||||
this.setOutput(true, "Number");
|
||||
this.setStyle("events_blocks");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["project_timer"] = function (block) {
|
||||
return ["projectTime()", BlocklyJS.Order.NONE];
|
||||
};
|
||||
|
||||
Blockly.Blocks["when_timer_reaches"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput()
|
||||
.appendField("when timer reaches")
|
||||
.appendField(new Blockly.FieldNumber(2, 0), "VALUE")
|
||||
.appendField("seconds");
|
||||
this.appendStatementInput("DO").setCheck("default");
|
||||
this.setStyle("events_blocks");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["when_timer_reaches"] = function (
|
||||
block,
|
||||
generator
|
||||
) {
|
||||
const value = block.getFieldValue("VALUE");
|
||||
const branch = generator.statementToCode(block, "DO");
|
||||
return `registerEvent("timer", ${value}, async () => {\n${branch}});\n`;
|
||||
};
|
||||
|
||||
Blockly.Blocks["every_seconds"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput()
|
||||
.appendField("every")
|
||||
.appendField(new Blockly.FieldNumber(2, 0.1), "SECONDS")
|
||||
.appendField("seconds");
|
||||
this.appendStatementInput("DO").setCheck("default");
|
||||
this.setStyle("events_blocks");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["every_seconds"] = function (
|
||||
block,
|
||||
generator
|
||||
) {
|
||||
const seconds = block.getFieldValue("SECONDS");
|
||||
const branch = generator.statementToCode(block, "DO");
|
||||
return `registerEvent("interval", ${seconds}, async () => {\n${branch}});\n`;
|
||||
};
|
||||
|
||||
Blockly.Blocks["when_custom_event_triggered"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput()
|
||||
.appendField("when")
|
||||
.appendField(new Blockly.FieldTextInput("event_name"), "EVENT")
|
||||
.appendField("triggered");
|
||||
this.appendStatementInput("DO").setCheck("default");
|
||||
this.setStyle("events_blocks");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["when_custom_event_triggered"] = function (
|
||||
block,
|
||||
generator
|
||||
) {
|
||||
const event = generator.quote_(block.getFieldValue("EVENT"));
|
||||
const branch = generator.statementToCode(block, "DO");
|
||||
return `registerEvent("custom", ${event}, async () => {\n${branch}});\n`;
|
||||
};
|
||||
|
||||
Blockly.Blocks["trigger_custom_event"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput()
|
||||
.appendField("trigger")
|
||||
.appendField(new Blockly.FieldTextInput("event_name"), "EVENT");
|
||||
this.setPreviousStatement(true, "default");
|
||||
this.setNextStatement(true, "default");
|
||||
this.setStyle("events_blocks");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["trigger_custom_event"] = function (
|
||||
block
|
||||
) {
|
||||
const event = BlocklyJS.javascriptGenerator.quote_(block.getFieldValue("EVENT"));
|
||||
return `triggerCustomEvent(${event});\n`;
|
||||
};
|
||||
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`;
|
||||
};
|
||||
512
src/blocks/json.js
Normal file
512
src/blocks/json.js
Normal file
@@ -0,0 +1,512 @@
|
||||
import * as Blockly from "blockly";
|
||||
import * as BlocklyJS from "blockly/javascript";
|
||||
const xmlUtils = Blockly.utils.xml;
|
||||
|
||||
Blockly.Blocks["json_get"] = {
|
||||
init: function () {
|
||||
this.appendValueInput("KEY").setCheck("String").appendField("value of");
|
||||
this.appendValueInput("OBJECT").setCheck("Object").appendField("in object");
|
||||
this.setOutput(true);
|
||||
this.setInputsInline(true);
|
||||
this.setStyle("json_category");
|
||||
this.setTooltip("Returns the value of a key from a JSON object.");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["json_get"] = function (block) {
|
||||
const obj =
|
||||
BlocklyJS.javascriptGenerator.valueToCode(
|
||||
block,
|
||||
"OBJECT",
|
||||
BlocklyJS.Order.MEMBER
|
||||
) || "{}";
|
||||
const key =
|
||||
BlocklyJS.javascriptGenerator.valueToCode(
|
||||
block,
|
||||
"KEY",
|
||||
BlocklyJS.Order.NONE
|
||||
) || '""';
|
||||
return [`${obj}[${key}]`, BlocklyJS.Order.MEMBER];
|
||||
};
|
||||
|
||||
Blockly.Blocks["json_set"] = {
|
||||
init: function () {
|
||||
this.appendValueInput("OBJECT").setCheck("Object").appendField("in object");
|
||||
this.appendValueInput("KEY").setCheck("String").appendField("set");
|
||||
this.appendValueInput("VALUE").appendField("to");
|
||||
this.setPreviousStatement(true, "default");
|
||||
this.setNextStatement(true, "default");
|
||||
this.setInputsInline(true);
|
||||
this.setStyle("json_category");
|
||||
this.setTooltip("Sets a value to a key in a JSON object.");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["json_set"] = function (block) {
|
||||
const obj =
|
||||
BlocklyJS.javascriptGenerator.valueToCode(
|
||||
block,
|
||||
"OBJECT",
|
||||
BlocklyJS.Order.MEMBER
|
||||
) || "{}";
|
||||
const key =
|
||||
BlocklyJS.javascriptGenerator.valueToCode(
|
||||
block,
|
||||
"KEY",
|
||||
BlocklyJS.Order.NONE
|
||||
) || '""';
|
||||
const value =
|
||||
BlocklyJS.javascriptGenerator.valueToCode(
|
||||
block,
|
||||
"VALUE",
|
||||
BlocklyJS.Order.ASSIGNMENT
|
||||
) || "null";
|
||||
return `${obj}[${key}] = ${value};\n`;
|
||||
};
|
||||
|
||||
Blockly.Blocks["json_delete"] = {
|
||||
init: function () {
|
||||
this.appendValueInput("OBJECT").setCheck("Object").appendField("in object");
|
||||
this.appendValueInput("KEY").setCheck("String").appendField("remove");
|
||||
this.setPreviousStatement(true);
|
||||
this.setNextStatement(true);
|
||||
this.setInputsInline(true);
|
||||
this.setStyle("json_category");
|
||||
this.setTooltip("Deletes a key from a JSON object.");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["json_delete"] = function (block) {
|
||||
const obj =
|
||||
BlocklyJS.javascriptGenerator.valueToCode(
|
||||
block,
|
||||
"OBJECT",
|
||||
BlocklyJS.Order.MEMBER
|
||||
) || "{}";
|
||||
const key =
|
||||
BlocklyJS.javascriptGenerator.valueToCode(
|
||||
block,
|
||||
"KEY",
|
||||
BlocklyJS.Order.NONE
|
||||
) || '""';
|
||||
return `delete ${obj}[${key}];\n`;
|
||||
};
|
||||
|
||||
Blockly.Blocks["json_create_item"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput().appendField("key and value");
|
||||
this.setPreviousStatement(true);
|
||||
this.setNextStatement(true);
|
||||
this.setStyle("json_category");
|
||||
this.setTooltip("Add a key with a value to the object.");
|
||||
this.contextMenu = false;
|
||||
},
|
||||
};
|
||||
|
||||
/* --- start deprecated --- */
|
||||
Blockly.Blocks["json_key_value"] = {
|
||||
init: function () {
|
||||
this.appendValueInput("KEY").setCheck("String").appendField("key");
|
||||
this.appendValueInput("VALUE").setCheck(null).appendField("value");
|
||||
this.setInputsInline(true);
|
||||
this.setStyle("json_category");
|
||||
this.setTooltip("A single key with a value.");
|
||||
this.setOutput(true, "ObjectItem");
|
||||
this.setInputsInline(true);
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["json_key_value"] = function (block) {
|
||||
const keyCode =
|
||||
BlocklyJS.javascriptGenerator.valueToCode(
|
||||
block,
|
||||
"KEY",
|
||||
BlocklyJS.Order.NONE
|
||||
) || '""';
|
||||
const valCode =
|
||||
BlocklyJS.javascriptGenerator.valueToCode(
|
||||
block,
|
||||
"VALUE",
|
||||
BlocklyJS.Order.ATOMIC
|
||||
) || "null";
|
||||
const code = `${keyCode}: ${valCode}`;
|
||||
return [code, BlocklyJS.Order.ATOMIC];
|
||||
};
|
||||
|
||||
Blockly.Blocks["json_create"] = {
|
||||
init: function () {
|
||||
this.setOutput(true, "Object");
|
||||
this.setStyle("json_category");
|
||||
this.itemCount_ = 0;
|
||||
this.updateShape_();
|
||||
this.setMutator(new Blockly.icons.MutatorIcon(["json_create_item"], this));
|
||||
this.setTooltip("Create a JSON object with any number of keys.");
|
||||
this.setInputsInline(false);
|
||||
},
|
||||
mutationToDom: function () {
|
||||
const container = xmlUtils.createElement("mutation");
|
||||
container.setAttribute("items", String(this.itemCount_));
|
||||
return container;
|
||||
},
|
||||
domToMutation: function (xmlElement) {
|
||||
const items = xmlElement.getAttribute("items");
|
||||
if (!items) throw new TypeError("element did not have items");
|
||||
this.itemCount_ = parseInt(items, 10);
|
||||
this.updateShape_();
|
||||
},
|
||||
saveExtraState: function () {
|
||||
return {
|
||||
itemCount: this.itemCount_,
|
||||
};
|
||||
},
|
||||
loadExtraState: function (state) {
|
||||
this.itemCount_ = state["itemCount"];
|
||||
this.updateShape_();
|
||||
},
|
||||
saveConnections: function (containerBlock) {
|
||||
let itemBlock = containerBlock.getInputTargetBlock("STACK");
|
||||
let i = 0;
|
||||
while (itemBlock) {
|
||||
const input = this.getInput("ITEM" + i);
|
||||
itemBlock.valueConnection_ = input && input.connection.targetConnection;
|
||||
itemBlock = itemBlock.nextConnection?.targetBlock();
|
||||
i++;
|
||||
}
|
||||
},
|
||||
compose: function (containerBlock) {
|
||||
let itemBlock = containerBlock.getInputTargetBlock("STACK");
|
||||
const connections = [];
|
||||
while (itemBlock) {
|
||||
if (itemBlock.isInsertionMarker()) {
|
||||
itemBlock = itemBlock.getNextBlock();
|
||||
continue;
|
||||
}
|
||||
connections.push(itemBlock.valueConnection_);
|
||||
itemBlock = itemBlock.getNextBlock();
|
||||
}
|
||||
for (let i = 0; i < this.itemCount_; i++) {
|
||||
const connection = this.getInput("ADD" + i)?.connection?.targetConnection;
|
||||
if (connection && !connections.includes(connection)) {
|
||||
connection.disconnect();
|
||||
}
|
||||
}
|
||||
this.itemCount_ = connections.length;
|
||||
this.updateShape_();
|
||||
for (let i = 0; i < this.itemCount_; i++) {
|
||||
connections[i]?.reconnect(this, "ADD" + i);
|
||||
}
|
||||
},
|
||||
decompose: function (workspace) {
|
||||
const containerBlock = workspace.newBlock("json_create_container");
|
||||
containerBlock.initSvg();
|
||||
let connection = containerBlock.getInput("STACK").connection;
|
||||
for (let i = 0; i < this.itemCount_; i++) {
|
||||
const itemBlock = workspace.newBlock("json_create_item");
|
||||
itemBlock.initSvg();
|
||||
if (!itemBlock.previousConnection) {
|
||||
throw new Error("itemBlock has no previousConnection");
|
||||
}
|
||||
connection.connect(itemBlock.previousConnection);
|
||||
connection = itemBlock.nextConnection;
|
||||
}
|
||||
return containerBlock;
|
||||
},
|
||||
saveConnections: function (containerBlock) {
|
||||
let itemBlock = containerBlock.getInputTargetBlock("STACK");
|
||||
let i = 0;
|
||||
while (itemBlock) {
|
||||
if (itemBlock.isInsertionMarker()) {
|
||||
itemBlock = itemBlock.getNextBlock();
|
||||
continue;
|
||||
}
|
||||
const input = this.getInput("ADD" + i);
|
||||
itemBlock.valueConnection_ = input?.connection?.targetConnection;
|
||||
itemBlock = itemBlock?.getNextBlock();
|
||||
i++;
|
||||
}
|
||||
},
|
||||
updateShape_: function () {
|
||||
if (this.itemCount_ && this.getInput("EMPTY")) {
|
||||
this.removeInput("EMPTY");
|
||||
} else if (!this.itemCount_ && !this.getInput("EMPTY")) {
|
||||
this.appendDummyInput("EMPTY").appendField("create empty object");
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.itemCount_; i++) {
|
||||
if (!this.getInput("ADD" + i)) {
|
||||
const input = this.appendValueInput("ADD" + i).setAlign(
|
||||
Blockly.inputs.Align.RIGHT
|
||||
);
|
||||
input.setCheck("ObjectItem");
|
||||
if (i === 0) input.appendField("create object with");
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = this.itemCount_; this.getInput("ADD" + i); i++) {
|
||||
this.removeInput("ADD" + i);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Blockly.Blocks["json_create_container"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput().appendField("object");
|
||||
this.appendStatementInput("STACK");
|
||||
this.setStyle("json_category");
|
||||
this.setTooltip(
|
||||
"Add, remove, or reorder sections to configure this object block."
|
||||
);
|
||||
this.contextMenu = false;
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["json_create"] = function (block) {
|
||||
const entries = [];
|
||||
for (let i = 0; i < block.itemCount_; i++) {
|
||||
const pairCode = BlocklyJS.javascriptGenerator.valueToCode(
|
||||
block,
|
||||
"ADD" + i,
|
||||
BlocklyJS.Order.NONE
|
||||
);
|
||||
if (pairCode) {
|
||||
entries.push(pairCode || "");
|
||||
}
|
||||
}
|
||||
const code = `{ ${entries.join(", ")} }`;
|
||||
return [code, BlocklyJS.Order.ATOMIC];
|
||||
};
|
||||
/* --- end deprecated --- */
|
||||
|
||||
Blockly.Blocks["json_create_statement"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput().appendField("create object");
|
||||
this.appendStatementInput("STACK").setCheck("json_key_value");
|
||||
this.setOutput(true, "Object");
|
||||
this.setStyle("json_category");
|
||||
this.setTooltip("Create a JSON object using stacked key/value pairs.");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["json_create_statement"] = function (
|
||||
block
|
||||
) {
|
||||
const statements = BlocklyJS.javascriptGenerator.statementToCode(
|
||||
block,
|
||||
"STACK"
|
||||
);
|
||||
return [`{\n${statements}}`, BlocklyJS.Order.ATOMIC];
|
||||
};
|
||||
|
||||
Blockly.Blocks["json_key_value_statement"] = {
|
||||
init: function () {
|
||||
this.appendValueInput("KEY").setCheck("String").appendField("key");
|
||||
this.appendValueInput("VALUE").appendField("value");
|
||||
this.setPreviousStatement(true, "json_key_value");
|
||||
this.setNextStatement(true, "json_key_value");
|
||||
this.setInputsInline(true);
|
||||
this.setStyle("json_category");
|
||||
this.setTooltip("A single key/value pair for a JSON object.");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["json_key_value_statement"] = function (
|
||||
block,
|
||||
generator
|
||||
) {
|
||||
const key =
|
||||
BlocklyJS.javascriptGenerator.valueToCode(
|
||||
block,
|
||||
"KEY",
|
||||
BlocklyJS.Order.ATOMIC
|
||||
) || "";
|
||||
const value =
|
||||
BlocklyJS.javascriptGenerator.valueToCode(
|
||||
block,
|
||||
"VALUE",
|
||||
BlocklyJS.Order.ATOMIC
|
||||
) || "null";
|
||||
|
||||
if (key) return `${key}: ${value},\n`;
|
||||
else return "";
|
||||
};
|
||||
|
||||
Blockly.Blocks["json_has_key"] = {
|
||||
init: function () {
|
||||
this.appendValueInput("OBJECT")
|
||||
.setCheck("Object")
|
||||
.appendField("does object");
|
||||
this.appendValueInput("KEY").setCheck("String").appendField("have");
|
||||
this.setOutput(true, "Boolean");
|
||||
this.setInputsInline(true);
|
||||
this.setStyle("json_category");
|
||||
this.setTooltip("Returns true if the key exists in the object.");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["json_has_key"] = function (block) {
|
||||
const obj =
|
||||
BlocklyJS.javascriptGenerator.valueToCode(
|
||||
block,
|
||||
"OBJECT",
|
||||
BlocklyJS.Order.MEMBER
|
||||
) || "{}";
|
||||
const key =
|
||||
BlocklyJS.javascriptGenerator.valueToCode(
|
||||
block,
|
||||
"KEY",
|
||||
BlocklyJS.Order.NONE
|
||||
) || '""';
|
||||
return [`(${key} in ${obj})`, BlocklyJS.Order.RELATIONAL];
|
||||
};
|
||||
|
||||
Blockly.Blocks["json_property_list"] = {
|
||||
init: function () {
|
||||
this.appendValueInput("OBJECT")
|
||||
.setCheck("Object")
|
||||
.appendField(
|
||||
new Blockly.FieldDropdown([
|
||||
["keys", "KEYS"],
|
||||
["values", "VALUES"],
|
||||
["entries", "ENTRIES"],
|
||||
]),
|
||||
"MODE"
|
||||
)
|
||||
.appendField("of");
|
||||
this.setOutput(true, "Array");
|
||||
this.setStyle("json_category");
|
||||
this.setTooltip("Gets keys, values, or entries of the JSON object.");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["json_property_list"] = function (
|
||||
block
|
||||
) {
|
||||
const obj =
|
||||
BlocklyJS.javascriptGenerator.valueToCode(
|
||||
block,
|
||||
"OBJECT",
|
||||
BlocklyJS.Order.FUNCTION_CALL
|
||||
) || "{}";
|
||||
const mode = block.getFieldValue("MODE");
|
||||
|
||||
let code;
|
||||
switch (mode) {
|
||||
case "KEYS":
|
||||
code = `Object.keys(${obj})`;
|
||||
break;
|
||||
case "VALUES":
|
||||
code = `Object.values(${obj})`;
|
||||
break;
|
||||
case "ENTRIES":
|
||||
code = `Object.entries(${obj})`;
|
||||
break;
|
||||
default:
|
||||
code = "[]";
|
||||
}
|
||||
|
||||
return [code, BlocklyJS.Order.FUNCTION_CALL];
|
||||
};
|
||||
|
||||
Blockly.Blocks["json_parse"] = {
|
||||
init: function () {
|
||||
const dropdown = new Blockly.FieldDropdown([
|
||||
["object from text", "PARSE"],
|
||||
["text from object", "STRINGIFY"],
|
||||
]);
|
||||
dropdown.setValidator((newMode) => {
|
||||
this.updateType_(newMode);
|
||||
});
|
||||
|
||||
this.setStyle("json_category");
|
||||
this.appendValueInput("INPUT")
|
||||
.setCheck("String")
|
||||
.appendField("make")
|
||||
.appendField(dropdown, "MODE");
|
||||
this.setInputsInline(true);
|
||||
this.setOutput(true, "Object");
|
||||
this.setTooltip(() => {
|
||||
const mode = this.getFieldValue("MODE");
|
||||
if (mode === "PARSE") {
|
||||
return "Convert a stringified object into an object.";
|
||||
} else if (mode === "STRINGIFY") {
|
||||
return "Convert an object into text representing an object.";
|
||||
}
|
||||
throw Error("Unknown mode: " + mode);
|
||||
});
|
||||
},
|
||||
updateType_: function (newMode) {
|
||||
const mode = this.getFieldValue("MODE");
|
||||
if (mode !== newMode) {
|
||||
const inputConnection = this.getInput("INPUT")?.connection;
|
||||
inputConnection?.setShadowDom(null);
|
||||
const inputBlock = inputConnection?.targetBlock();
|
||||
|
||||
if (inputBlock) {
|
||||
inputConnection.disconnect();
|
||||
if (inputBlock.isShadow()) {
|
||||
inputBlock.dispose(false);
|
||||
} else {
|
||||
this.bumpNeighbours();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (newMode === "PARSE") {
|
||||
this.outputConnection.setCheck("Object");
|
||||
this.getInput("INPUT").setCheck("String");
|
||||
} else {
|
||||
this.outputConnection.setCheck("String");
|
||||
this.getInput("INPUT").setCheck("Object");
|
||||
}
|
||||
},
|
||||
mutationToDom: function () {
|
||||
const container = xmlUtils.createElement("mutation");
|
||||
container.setAttribute("mode", this.getFieldValue("MODE"));
|
||||
return container;
|
||||
},
|
||||
domToMutation: function (xmlElement) {
|
||||
this.updateType_(xmlElement.getAttribute("mode"));
|
||||
},
|
||||
saveExtraState: function () {
|
||||
return { mode: this.getFieldValue("MODE") };
|
||||
},
|
||||
loadExtraState: function (state) {
|
||||
this.updateType_(state["mode"]);
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["json_parse"] = function (block) {
|
||||
const input =
|
||||
BlocklyJS.javascriptGenerator.valueToCode(
|
||||
block,
|
||||
"INPUT",
|
||||
BlocklyJS.Order.ATOMIC
|
||||
) || "null";
|
||||
const mode = block.getFieldValue("MODE");
|
||||
|
||||
if (mode === "PARSE") {
|
||||
return [`JSON.parse(${input})`, BlocklyJS.Order.NONE];
|
||||
} else if (mode === "STRINGIFY") {
|
||||
return [`JSON.stringify(${input})`, BlocklyJS.Order.NONE];
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks["json_clone"] = {
|
||||
init: function () {
|
||||
this.appendValueInput("OBJECT")
|
||||
.setCheck("Object")
|
||||
.appendField("clone object");
|
||||
this.setOutput(true, "Object");
|
||||
this.setStyle("json_category");
|
||||
this.setTooltip("Creates a duplicate of a JSON object.");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["json_clone"] = function (block) {
|
||||
const obj =
|
||||
BlocklyJS.javascriptGenerator.valueToCode(
|
||||
block,
|
||||
"OBJECT",
|
||||
BlocklyJS.Order.ATOMIC
|
||||
) || "{}";
|
||||
return [`JSON.parse(JSON.stringify(${obj}))`, BlocklyJS.Order.FUNCTION_CALL];
|
||||
};
|
||||
454
src/blocks/list.js
Normal file
454
src/blocks/list.js
Normal file
@@ -0,0 +1,454 @@
|
||||
import * as Blockly from "blockly";
|
||||
import * as BlocklyJS from "blockly/javascript";
|
||||
|
||||
Blockly.Blocks["lists_filter"] = {
|
||||
init: function () {
|
||||
this.appendValueInput("list").setCheck("Array").appendField("filter list");
|
||||
this.appendValueInput("method").setCheck("Boolean").appendField("by");
|
||||
this.setInputsInline(true);
|
||||
this.setOutput(true, "Array");
|
||||
this.setStyle("list_blocks");
|
||||
this.setTooltip(
|
||||
"Remove all items in a list which doesn't match the boolean"
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["lists_filter"] = function (
|
||||
block,
|
||||
generator
|
||||
) {
|
||||
var val_list = generator.valueToCode(block, "list", BlocklyJS.Order.ATOMIC);
|
||||
var val_method = generator.valueToCode(
|
||||
block,
|
||||
"method",
|
||||
BlocklyJS.Order.ATOMIC
|
||||
);
|
||||
var code = `${val_list}.filter(findOrFilterItem => ${val_method})`;
|
||||
return [code, BlocklyJS.Order.NONE];
|
||||
};
|
||||
|
||||
Blockly.Blocks["lists_find"] = {
|
||||
init: function () {
|
||||
this.appendValueInput("list").setCheck("Array").appendField("in list");
|
||||
this.appendValueInput("method")
|
||||
.setCheck("Boolean")
|
||||
.appendField("find first that matches");
|
||||
this.setOutput(true, null);
|
||||
this.setInputsInline(true);
|
||||
this.setStyle("list_blocks");
|
||||
this.setTooltip(
|
||||
"Returns the first item in a list that matches the boolean"
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["lists_find"] = function (
|
||||
block,
|
||||
generator
|
||||
) {
|
||||
var val_list = generator.valueToCode(block, "list", BlocklyJS.Order.ATOMIC);
|
||||
var val_method = generator.valueToCode(
|
||||
block,
|
||||
"method",
|
||||
BlocklyJS.Order.ATOMIC
|
||||
);
|
||||
var code = `${val_list}.find(findOrFilterItem => ${val_method})`;
|
||||
return [code, BlocklyJS.Order.NONE];
|
||||
};
|
||||
|
||||
Blockly.Blocks["lists_filter_item"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput("name").appendField("item in loop");
|
||||
this.setInputsInline(true);
|
||||
this.setOutput(true, null);
|
||||
this.setStyle("list_blocks");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["lists_filter_item"] = () => [
|
||||
"findOrFilterItem",
|
||||
BlocklyJS.Order.NONE,
|
||||
];
|
||||
|
||||
Blockly.Blocks["lists_merge"] = {
|
||||
init: function () {
|
||||
this.appendValueInput("list").setCheck("Array").appendField("merge list");
|
||||
this.appendValueInput("list2").setCheck("Array").appendField("with");
|
||||
this.setInputsInline(true);
|
||||
this.setOutput(true, "Array");
|
||||
this.setStyle("list_blocks");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["lists_merge"] = function (
|
||||
block,
|
||||
generator
|
||||
) {
|
||||
const val_list = generator.valueToCode(block, "list", BlocklyJS.Order.ATOMIC);
|
||||
const val_list2 = generator.valueToCode(
|
||||
block,
|
||||
"list2",
|
||||
BlocklyJS.Order.ATOMIC
|
||||
);
|
||||
const code = `${val_list}.concat(${val_list2})`;
|
||||
return [code, BlocklyJS.Order.NONE];
|
||||
};
|
||||
|
||||
Blockly.Blocks["lists_foreach"] = {
|
||||
init: function () {
|
||||
this.appendValueInput("LIST")
|
||||
.setCheck("Array")
|
||||
.appendField("for each item in list");
|
||||
this.appendStatementInput("DO").appendField("do");
|
||||
this.setInputsInline(false);
|
||||
this.setPreviousStatement(true, null);
|
||||
this.setNextStatement(true, null);
|
||||
this.setStyle("list_blocks");
|
||||
this.setTooltip(
|
||||
"Loops through every item in a list and runs the code inside for each one"
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["lists_foreach"] = function (
|
||||
block,
|
||||
generator
|
||||
) {
|
||||
const list =
|
||||
generator.valueToCode(block, "LIST", BlocklyJS.Order.NONE) || "[]";
|
||||
const branch = generator.statementToCode(block, "DO");
|
||||
const code = `${list}.forEach(findOrFilterItem => {\n${branch}});\n`;
|
||||
return code;
|
||||
};
|
||||
|
||||
Blockly.Blocks["lists_getIndex_modified"] = {
|
||||
init: function () {
|
||||
const MODE_OPTIONS = [
|
||||
[Blockly.Msg.LISTS_GET_INDEX_GET, "GET"],
|
||||
[Blockly.Msg.LISTS_GET_INDEX_REMOVE, "REMOVE"],
|
||||
];
|
||||
this.WHERE_OPTIONS = [
|
||||
[Blockly.Msg.LISTS_GET_INDEX_FROM_START, "FROM_START"],
|
||||
[Blockly.Msg.LISTS_GET_INDEX_FROM_END, "FROM_END"],
|
||||
[Blockly.Msg.LISTS_GET_INDEX_FIRST, "FIRST"],
|
||||
[Blockly.Msg.LISTS_GET_INDEX_LAST, "LAST"],
|
||||
[Blockly.Msg.LISTS_GET_INDEX_RANDOM, "RANDOM"],
|
||||
];
|
||||
|
||||
this.setHelpUrl(Blockly.Msg.LISTS_GET_INDEX_HELPURL);
|
||||
this.setStyle("list_blocks");
|
||||
|
||||
this.appendValueInput("VALUE")
|
||||
.setCheck("Array")
|
||||
.appendField(Blockly.Msg.LISTS_GET_INDEX_INPUT_IN_LIST);
|
||||
|
||||
const modeField = new Blockly.FieldDropdown(MODE_OPTIONS, newMode => {
|
||||
this.updateMode_(newMode);
|
||||
return newMode;
|
||||
});
|
||||
this.appendDummyInput()
|
||||
.appendField(modeField, "MODE")
|
||||
.appendField("", "SPACE");
|
||||
|
||||
const whereField = new Blockly.FieldDropdown(
|
||||
this.WHERE_OPTIONS,
|
||||
newWhere => {
|
||||
const cur = this.getFieldValue("WHERE");
|
||||
const newNeedsAt = newWhere === "FROM_START" || newWhere === "FROM_END";
|
||||
const curNeedsAt = cur === "FROM_START" || cur === "FROM_END";
|
||||
if (newNeedsAt !== curNeedsAt) this.updateAt_(newNeedsAt);
|
||||
}
|
||||
);
|
||||
this.appendDummyInput().appendField(whereField, "WHERE");
|
||||
|
||||
this.appendDummyInput("AT");
|
||||
|
||||
this.setInputsInline(true);
|
||||
|
||||
this.updateAt_(true);
|
||||
this.updateMode_(this.getFieldValue("MODE") || "GET");
|
||||
|
||||
this.setTooltip(() => {
|
||||
const mode = this.getFieldValue("MODE");
|
||||
const where = this.getFieldValue("WHERE");
|
||||
if (mode === "GET") {
|
||||
switch (where) {
|
||||
case "FROM_START":
|
||||
case "FROM_END":
|
||||
return Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_FROM;
|
||||
case "FIRST":
|
||||
return Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_FIRST;
|
||||
case "LAST":
|
||||
return Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_LAST;
|
||||
case "RANDOM":
|
||||
return Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_RANDOM;
|
||||
}
|
||||
} else {
|
||||
switch (where) {
|
||||
case "FROM_START":
|
||||
case "FROM_END":
|
||||
return Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_FROM;
|
||||
case "FIRST":
|
||||
return Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_FIRST;
|
||||
case "LAST":
|
||||
return Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_LAST;
|
||||
case "RANDOM":
|
||||
return Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_RANDOM;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
});
|
||||
},
|
||||
|
||||
mutationToDom: function () {
|
||||
const container = document.createElement("mutation");
|
||||
const input = this.getInput("AT");
|
||||
const isValueInput = !!input && !!input.connection;
|
||||
container.setAttribute("at", String(isValueInput));
|
||||
container.setAttribute("mode", this.getFieldValue("MODE") || "GET");
|
||||
return container;
|
||||
},
|
||||
|
||||
domToMutation: function (xmlElement) {
|
||||
const at = xmlElement.getAttribute("at") !== "false";
|
||||
const mode = xmlElement.getAttribute("mode") || "GET";
|
||||
this.updateAt_(at);
|
||||
this.updateMode_(mode);
|
||||
},
|
||||
|
||||
updateAt_: function (useValueInput) {
|
||||
if (this.getInput("AT")) this.removeInput("AT", true);
|
||||
if (this.getInput("ORDINAL")) this.removeInput("ORDINAL", true);
|
||||
|
||||
if (useValueInput) {
|
||||
this.appendValueInput("AT").setCheck("Number");
|
||||
if (Blockly.Msg.ORDINAL_NUMBER_SUFFIX) {
|
||||
this.appendDummyInput("ORDINAL").appendField(
|
||||
Blockly.Msg.ORDINAL_NUMBER_SUFFIX
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.appendDummyInput("AT");
|
||||
}
|
||||
},
|
||||
|
||||
updateMode_: function (mode) {
|
||||
if (mode === "GET") {
|
||||
if (!this.outputConnection) this.setOutput(true);
|
||||
this.outputConnection.setCheck(null);
|
||||
} else {
|
||||
if (!this.outputConnection) this.setOutput(true);
|
||||
this.outputConnection.setCheck("Array");
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["lists_getIndex_modified"] = function (
|
||||
block,
|
||||
generator
|
||||
) {
|
||||
const workspaceOneBased =
|
||||
block.workspace.options && block.workspace.options.oneBasedIndex;
|
||||
const mode = block.getFieldValue("MODE");
|
||||
const where = block.getFieldValue("WHERE");
|
||||
|
||||
const listCode =
|
||||
generator.valueToCode(block, "VALUE", BlocklyJS.Order.NONE) || "[]";
|
||||
const atCodeRaw =
|
||||
generator.valueToCode(block, "AT", BlocklyJS.Order.NONE) || "0";
|
||||
|
||||
let i;
|
||||
|
||||
switch (where) {
|
||||
case "FIRST":
|
||||
i = "0";
|
||||
break;
|
||||
case "LAST":
|
||||
i = "-1";
|
||||
break;
|
||||
case "RANDOM":
|
||||
i = `Math.floor(Math.random() * _.length)`;
|
||||
break;
|
||||
case "FROM_START":
|
||||
i = workspaceOneBased ? `${atCodeRaw} - 1` : atCodeRaw;
|
||||
break;
|
||||
case "FROM_END":
|
||||
i = workspaceOneBased ? `-${atCodeRaw}` : `-(${atCodeRaw} + 1)`;
|
||||
break;
|
||||
default:
|
||||
i = "0";
|
||||
}
|
||||
|
||||
return [
|
||||
mode === "REMOVE"
|
||||
? `${listCode}.toSpliced(${i}, 1)`
|
||||
: `${listCode}.at(${i})`,
|
||||
BlocklyJS.Order.FUNCTION_CALL,
|
||||
];
|
||||
};
|
||||
|
||||
Blockly.Blocks["lists_setIndex_modified"] = {
|
||||
init: function () {
|
||||
const MODE_OPTIONS = [
|
||||
[Blockly.Msg.LISTS_SET_INDEX_SET, "SET"],
|
||||
[Blockly.Msg.LISTS_SET_INDEX_INSERT, "INSERT"],
|
||||
];
|
||||
this.WHERE_OPTIONS = [
|
||||
[Blockly.Msg.LISTS_GET_INDEX_FROM_START, "FROM_START"],
|
||||
[Blockly.Msg.LISTS_GET_INDEX_FROM_END, "FROM_END"],
|
||||
[Blockly.Msg.LISTS_GET_INDEX_FIRST, "FIRST"],
|
||||
[Blockly.Msg.LISTS_GET_INDEX_LAST, "LAST"],
|
||||
[Blockly.Msg.LISTS_GET_INDEX_RANDOM, "RANDOM"],
|
||||
];
|
||||
|
||||
this.setHelpUrl(Blockly.Msg.LISTS_SET_INDEX_HELPURL);
|
||||
this.setStyle("list_blocks");
|
||||
|
||||
this.appendValueInput("LIST")
|
||||
.setCheck("Array")
|
||||
.appendField(Blockly.Msg.LISTS_SET_INDEX_INPUT_IN_LIST);
|
||||
|
||||
const modeField = new Blockly.FieldDropdown(MODE_OPTIONS);
|
||||
this.appendDummyInput()
|
||||
.appendField(modeField, "MODE")
|
||||
.appendField("", "SPACE");
|
||||
|
||||
const whereField = new Blockly.FieldDropdown(
|
||||
this.WHERE_OPTIONS,
|
||||
newWhere => {
|
||||
this.updateAt_(newWhere === "FROM_START" || newWhere === "FROM_END");
|
||||
return newWhere;
|
||||
}
|
||||
);
|
||||
this.appendDummyInput().appendField(whereField, "WHERE");
|
||||
|
||||
this.appendDummyInput("AT");
|
||||
|
||||
this.appendValueInput("TO").appendField(
|
||||
Blockly.Msg.LISTS_SET_INDEX_INPUT_TO
|
||||
);
|
||||
|
||||
this.setInputsInline(true);
|
||||
|
||||
this.setOutput(true, "Array");
|
||||
|
||||
this.updateAt_(true);
|
||||
|
||||
this.setTooltip(() => {
|
||||
const mode = this.getFieldValue("MODE");
|
||||
const where = this.getFieldValue("WHERE");
|
||||
if (mode === "SET") {
|
||||
switch (where) {
|
||||
case "FROM_START":
|
||||
case "FROM_END":
|
||||
return Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_SET_FROM;
|
||||
case "FIRST":
|
||||
return Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_SET_FIRST;
|
||||
case "LAST":
|
||||
return Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_SET_LAST;
|
||||
case "RANDOM":
|
||||
return Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_SET_RANDOM;
|
||||
}
|
||||
} else {
|
||||
switch (where) {
|
||||
case "FROM_START":
|
||||
case "FROM_END":
|
||||
return Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_FROM;
|
||||
case "FIRST":
|
||||
return Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_FIRST;
|
||||
case "LAST":
|
||||
return Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_LAST;
|
||||
case "RANDOM":
|
||||
return Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_RANDOM;
|
||||
}
|
||||
}
|
||||
return Blockly.Msg.LISTS_SET_INDEX_TOOLTIP;
|
||||
});
|
||||
},
|
||||
|
||||
mutationToDom: function () {
|
||||
const container = document.createElement("mutation");
|
||||
const input = this.getInput("AT");
|
||||
const isValueInput = !!input && !!input.connection;
|
||||
container.setAttribute("at", String(isValueInput));
|
||||
return container;
|
||||
},
|
||||
|
||||
domToMutation: function (xmlElement) {
|
||||
const at = xmlElement.getAttribute("at") !== "false";
|
||||
this.updateAt_(at);
|
||||
},
|
||||
|
||||
updateAt_: function (useValueInput) {
|
||||
if (this.getInput("AT")) this.removeInput("AT", true);
|
||||
if (this.getInput("ORDINAL")) this.removeInput("ORDINAL", true);
|
||||
|
||||
if (useValueInput) {
|
||||
this.appendValueInput("AT").setCheck("Number");
|
||||
if (Blockly.Msg.ORDINAL_NUMBER_SUFFIX) {
|
||||
this.appendDummyInput("ORDINAL").appendField(
|
||||
Blockly.Msg.ORDINAL_NUMBER_SUFFIX
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.appendDummyInput("AT");
|
||||
}
|
||||
try {
|
||||
this.moveInputBefore("AT", "TO");
|
||||
if (this.getInput("ORDINAL")) this.moveInputBefore("ORDINAL", "TO");
|
||||
} catch (e) {}
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["lists_setIndex_modified"] = function (
|
||||
block,
|
||||
generator
|
||||
) {
|
||||
const oneBased = block.workspace.options?.oneBasedIndex;
|
||||
const mode = block.getFieldValue("MODE");
|
||||
const where = block.getFieldValue("WHERE");
|
||||
|
||||
const listCode =
|
||||
generator.valueToCode(block, "LIST", BlocklyJS.Order.NONE) || "[]";
|
||||
const valueCode =
|
||||
generator.valueToCode(block, "TO", BlocklyJS.Order.NONE) || "undefined";
|
||||
const atCode =
|
||||
generator.valueToCode(block, "AT", BlocklyJS.Order.NONE) || "0";
|
||||
|
||||
let indexExpr;
|
||||
switch (where) {
|
||||
case "FIRST":
|
||||
indexExpr = "0";
|
||||
break;
|
||||
case "LAST":
|
||||
indexExpr = "-1";
|
||||
break;
|
||||
case "RANDOM":
|
||||
indexExpr = "Math.floor(Math.random() * _.length)";
|
||||
break;
|
||||
case "FROM_START":
|
||||
indexExpr = oneBased ? `(${atCode} - 1)` : atCode;
|
||||
break;
|
||||
case "FROM_END":
|
||||
indexExpr = oneBased ? `-${atCode}` : `-(${atCode} + 1)`;
|
||||
break;
|
||||
default:
|
||||
indexExpr = "0";
|
||||
}
|
||||
|
||||
const code = `((a) => {
|
||||
const _ = [...a];
|
||||
let i = ${indexExpr};
|
||||
if (i < 0) i += _.length;
|
||||
if (i < 0) i = 0;
|
||||
if (i > _.length) i = _.length;
|
||||
return ${
|
||||
mode === "INSERT"
|
||||
? `_.toSpliced(i, 0, ${valueCode})`
|
||||
: `i < _.length ? _.toSpliced(i, 1, ${valueCode}) : _`
|
||||
};
|
||||
})(${listCode})`;
|
||||
|
||||
return [code, BlocklyJS.Order.FUNCTION_CALL];
|
||||
};
|
||||
218
src/blocks/looks.js
Normal file
218
src/blocks/looks.js
Normal file
@@ -0,0 +1,218 @@
|
||||
import * as Blockly from "blockly";
|
||||
import * as BlocklyJS from "blockly/javascript";
|
||||
|
||||
function getAvailableCostumes() {
|
||||
if (window.projectCostumes && window.projectCostumes.length > 0) {
|
||||
return window.projectCostumes.map(costume => [costume, costume]);
|
||||
}
|
||||
return [["default", "default"]];
|
||||
}
|
||||
|
||||
Blockly.Blocks["say_message"] = {
|
||||
init: function () {
|
||||
this.appendValueInput("MESSAGE").appendField("say");
|
||||
this.setPreviousStatement(true, "default");
|
||||
this.setNextStatement(true, "default");
|
||||
this.setInputsInline(true);
|
||||
this.setStyle("looks_blocks");
|
||||
},
|
||||
};
|
||||
|
||||
Blockly.Blocks["say_message_duration"] = {
|
||||
init: function () {
|
||||
this.appendValueInput("MESSAGE").appendField("say");
|
||||
this.appendValueInput("DURATION").setCheck("Number").appendField("for");
|
||||
this.appendDummyInput().appendField("seconds");
|
||||
this.setPreviousStatement(true, "default");
|
||||
this.setNextStatement(true, "default");
|
||||
this.setInputsInline(true);
|
||||
this.setStyle("looks_blocks");
|
||||
},
|
||||
};
|
||||
|
||||
Blockly.Blocks["switch_costume"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput()
|
||||
.appendField("switch costume to")
|
||||
.appendField(new Blockly.FieldDropdown(
|
||||
function() {
|
||||
return getAvailableCostumes();
|
||||
}
|
||||
), "COSTUME_NAME");
|
||||
this.setPreviousStatement(true);
|
||||
this.setNextStatement(true);
|
||||
this.setStyle("looks_blocks");
|
||||
},
|
||||
};
|
||||
|
||||
Blockly.Blocks["set_size"] = {
|
||||
init: function () {
|
||||
this.appendValueInput("AMOUNT")
|
||||
.setCheck("Number")
|
||||
.appendField("set size to");
|
||||
this.appendDummyInput().appendField("%");
|
||||
this.setPreviousStatement(true);
|
||||
this.setNextStatement(true);
|
||||
this.setStyle("looks_blocks");
|
||||
},
|
||||
};
|
||||
|
||||
Blockly.Blocks["change_size"] = {
|
||||
init: function () {
|
||||
this.appendValueInput("AMOUNT")
|
||||
.setCheck("Number")
|
||||
.appendField("change size by");
|
||||
this.appendDummyInput().appendField("%");
|
||||
this.setPreviousStatement(true);
|
||||
this.setNextStatement(true);
|
||||
this.setStyle("looks_blocks");
|
||||
},
|
||||
};
|
||||
|
||||
Blockly.Blocks["get_costume_size"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput()
|
||||
.appendField("costume")
|
||||
.appendField(
|
||||
new Blockly.FieldDropdown([
|
||||
["width", "width"],
|
||||
["height", "height"],
|
||||
]),
|
||||
"MENU"
|
||||
);
|
||||
this.setOutput(true, "Number");
|
||||
this.setStyle("looks_blocks");
|
||||
},
|
||||
};
|
||||
|
||||
Blockly.Blocks["get_sprite_scale"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput().appendField("size");
|
||||
this.setOutput(true, "Number");
|
||||
this.setStyle("looks_blocks");
|
||||
},
|
||||
};
|
||||
|
||||
Blockly.Blocks["looks_hide_sprite"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput().appendField("hide sprite");
|
||||
this.setPreviousStatement(true, "default");
|
||||
this.setNextStatement(true, "default");
|
||||
this.setStyle("looks_blocks");
|
||||
},
|
||||
};
|
||||
|
||||
Blockly.Blocks["looks_show_sprite"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput().appendField("show sprite");
|
||||
this.setPreviousStatement(true, "default");
|
||||
this.setNextStatement(true, "default");
|
||||
this.setStyle("looks_blocks");
|
||||
},
|
||||
};
|
||||
|
||||
Blockly.Blocks["looks_isVisible"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput().appendField("is visible");
|
||||
this.setOutput(true, "Boolean");
|
||||
this.setStyle("looks_blocks");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["say_message"] = function (
|
||||
block,
|
||||
generator
|
||||
) {
|
||||
const message =
|
||||
generator.valueToCode(block, "MESSAGE", BlocklyJS.Order.NONE) || "";
|
||||
|
||||
return `sayMessage(${message});\n`;
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["say_message_duration"] = function (
|
||||
block,
|
||||
generator
|
||||
) {
|
||||
const message =
|
||||
generator.valueToCode(block, "MESSAGE", BlocklyJS.Order.NONE) || "";
|
||||
const duration =
|
||||
generator.valueToCode(block, "DURATION", BlocklyJS.Order.ATOMIC) || 2;
|
||||
|
||||
return `sayMessage(${message}, ${duration});\n`;
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["switch_costume"] = function (
|
||||
block,
|
||||
generator
|
||||
) {
|
||||
var costume = generator.valueToCode(block, "COSTUME", BlocklyJS.Order.ATOMIC);
|
||||
return `switchCostume(${costume});\n`;
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["switch_costume"] = function (block, generator) {
|
||||
var costume = "'" + block.getFieldValue("COSTUME_NAME") + "'";
|
||||
return `switchCostume(${costume});\n`;
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["set_size"] = function (
|
||||
block,
|
||||
generator
|
||||
) {
|
||||
const amount =
|
||||
generator.valueToCode(block, "AMOUNT", BlocklyJS.Order.ATOMIC) || 100;
|
||||
return `setSize(${amount}, false);\n`;
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["change_size"] = function (
|
||||
block,
|
||||
generator
|
||||
) {
|
||||
const amount =
|
||||
generator.valueToCode(block, "AMOUNT", BlocklyJS.Order.ATOMIC) || 100;
|
||||
return `setSize(${amount}, true);\n`;
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["get_costume_size"] = function (block) {
|
||||
const menu = block.getFieldValue("MENU");
|
||||
return [`getCostumeSize("${menu}")`, BlocklyJS.Order.NONE];
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["get_sprite_scale"] = function () {
|
||||
return [`getSpriteScale()`, BlocklyJS.Order.NONE];
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["looks_hide_sprite"] = function () {
|
||||
return "toggleVisibility(false);\n";
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["looks_show_sprite"] = function () {
|
||||
return "toggleVisibility(true);\n";
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["looks_isVisible"] = () => [
|
||||
"sprite.visible",
|
||||
BlocklyJS.Order.NONE,
|
||||
];
|
||||
|
||||
Blockly.Blocks["switch_backdrop"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput()
|
||||
.appendField("switch backdrop to")
|
||||
.appendField(new Blockly.FieldDropdown(
|
||||
function() {
|
||||
if (window.projectBackdrops && window.projectBackdrops.length > 0) {
|
||||
return window.projectBackdrops.map((backdrop) => [backdrop.name, backdrop.name]);
|
||||
}
|
||||
return [["no backdrops", ""]];
|
||||
}
|
||||
), "BACKDROP_NAME");
|
||||
this.setPreviousStatement(true);
|
||||
this.setNextStatement(true);
|
||||
this.setStyle("looks_blocks");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["switch_backdrop"] = function (block) {
|
||||
const name = block.getFieldValue("BACKDROP_NAME");
|
||||
return `setBackdropByName('${name}');\n`;
|
||||
};
|
||||
223
src/blocks/motion.js
Normal file
223
src/blocks/motion.js
Normal file
@@ -0,0 +1,223 @@
|
||||
import * as Blockly from "blockly";
|
||||
import * as BlocklyJS from "blockly/javascript";
|
||||
|
||||
Blockly.Blocks["move_steps"] = {
|
||||
init: function () {
|
||||
this.appendValueInput("STEPS").setCheck("Number").appendField("step");
|
||||
this.appendDummyInput().appendField("times");
|
||||
this.setPreviousStatement(true, "default");
|
||||
this.setNextStatement(true, "default");
|
||||
this.setStyle("motion_blocks");
|
||||
},
|
||||
};
|
||||
|
||||
Blockly.Blocks["change_position"] = {
|
||||
init: function () {
|
||||
this.appendValueInput("AMOUNT")
|
||||
.setCheck("Number")
|
||||
.appendField("change")
|
||||
.appendField(
|
||||
new Blockly.FieldDropdown([
|
||||
["x", "x"],
|
||||
["y", "y"],
|
||||
]),
|
||||
"MENU"
|
||||
)
|
||||
.appendField("by");
|
||||
this.setPreviousStatement(true, "default");
|
||||
this.setNextStatement(true, "default");
|
||||
this.setStyle("motion_blocks");
|
||||
},
|
||||
};
|
||||
|
||||
Blockly.Blocks["set_position"] = {
|
||||
init: function () {
|
||||
this.appendValueInput("AMOUNT")
|
||||
.setCheck("Number")
|
||||
.appendField("set")
|
||||
.appendField(
|
||||
new Blockly.FieldDropdown([
|
||||
["x", "x"],
|
||||
["y", "y"],
|
||||
]),
|
||||
"MENU"
|
||||
)
|
||||
.appendField("to");
|
||||
this.setPreviousStatement(true, "default");
|
||||
this.setNextStatement(true, "default");
|
||||
this.setStyle("motion_blocks");
|
||||
},
|
||||
};
|
||||
|
||||
Blockly.Blocks["goto_position"] = {
|
||||
init: function () {
|
||||
this.appendValueInput("x").setCheck("Number").appendField("go to x");
|
||||
this.appendValueInput("y").setCheck("Number").appendField("y");
|
||||
this.setInputsInline(true);
|
||||
this.setPreviousStatement(true, "default");
|
||||
this.setNextStatement(true, "default");
|
||||
this.setStyle("motion_blocks");
|
||||
},
|
||||
};
|
||||
|
||||
Blockly.Blocks["get_position"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput().appendField(
|
||||
new Blockly.FieldDropdown([
|
||||
["x", "x"],
|
||||
["y", "y"],
|
||||
]),
|
||||
"MENU"
|
||||
);
|
||||
this.setOutput(true, "Number");
|
||||
this.setStyle("motion_blocks");
|
||||
},
|
||||
};
|
||||
|
||||
Blockly.Blocks["angle_turn"] = {
|
||||
init: function () {
|
||||
this.appendValueInput("AMOUNT")
|
||||
.setCheck("Number")
|
||||
.appendField("turn")
|
||||
.appendField(
|
||||
new Blockly.FieldDropdown([
|
||||
[
|
||||
{
|
||||
src: "icons/right.svg",
|
||||
height: 30,
|
||||
width: 30,
|
||||
alt: "A circular arrow rotating to the right",
|
||||
},
|
||||
"right",
|
||||
],
|
||||
[
|
||||
{
|
||||
src: "icons/left.svg",
|
||||
height: 30,
|
||||
width: 30,
|
||||
alt: "A circular arrow rotating to the left",
|
||||
},
|
||||
"left",
|
||||
],
|
||||
]),
|
||||
"DIRECTION"
|
||||
);
|
||||
this.appendDummyInput().appendField("degrees");
|
||||
this.setPreviousStatement(true, "default");
|
||||
this.setNextStatement(true, "default");
|
||||
this.setStyle("motion_blocks");
|
||||
},
|
||||
};
|
||||
|
||||
Blockly.Blocks["angle_set"] = {
|
||||
init: function () {
|
||||
this.appendValueInput("AMOUNT")
|
||||
.setCheck("Number")
|
||||
.appendField("set angle to");
|
||||
this.appendDummyInput().appendField("degrees");
|
||||
this.setInputsInline(true);
|
||||
this.setPreviousStatement(true, "default");
|
||||
this.setNextStatement(true, "default");
|
||||
this.setStyle("motion_blocks");
|
||||
},
|
||||
};
|
||||
|
||||
Blockly.Blocks["point_towards"] = {
|
||||
init: function () {
|
||||
this.appendValueInput("x")
|
||||
.setCheck("Number")
|
||||
.appendField("point towards x");
|
||||
this.appendValueInput("y").setCheck("Number").appendField("y");
|
||||
this.setInputsInline(true);
|
||||
this.setPreviousStatement(true, "default");
|
||||
this.setNextStatement(true, "default");
|
||||
this.setStyle("motion_blocks");
|
||||
},
|
||||
};
|
||||
|
||||
Blockly.Blocks["get_angle"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput().appendField("angle");
|
||||
this.setOutput(true, "Number");
|
||||
this.setStyle("motion_blocks");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["move_steps"] = function (
|
||||
block,
|
||||
generator
|
||||
) {
|
||||
const steps =
|
||||
generator.valueToCode(block, "STEPS", BlocklyJS.Order.ATOMIC) || 0;
|
||||
return `moveSteps(${steps});\n`;
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["change_position"] = function (
|
||||
block,
|
||||
generator
|
||||
) {
|
||||
const amount =
|
||||
generator.valueToCode(block, "AMOUNT", BlocklyJS.Order.ATOMIC) || 0;
|
||||
const menu = block.getFieldValue("MENU");
|
||||
if (menu === "y") return `sprite["${menu}"] -= ${amount};\n`;
|
||||
else return `sprite["${menu}"] += ${amount};\n`;
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["set_position"] = function (
|
||||
block,
|
||||
generator
|
||||
) {
|
||||
const amount =
|
||||
generator.valueToCode(block, "AMOUNT", BlocklyJS.Order.ATOMIC) || 0;
|
||||
const menu = block.getFieldValue("MENU");
|
||||
if (menu === "y") return `sprite["${menu}"] = -${amount};\n`;
|
||||
else return `sprite["${menu}"] = ${amount};\n`;
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["goto_position"] = function (
|
||||
block,
|
||||
generator
|
||||
) {
|
||||
const x = generator.valueToCode(block, "x", BlocklyJS.Order.ATOMIC) || 0;
|
||||
const y = generator.valueToCode(block, "y", BlocklyJS.Order.ATOMIC) || 0;
|
||||
return `sprite.x = ${x};\nsprite.y = -${y};\n`;
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["point_towards"] = function (
|
||||
block,
|
||||
generator
|
||||
) {
|
||||
const x = generator.valueToCode(block, "x", BlocklyJS.Order.ATOMIC) || 0;
|
||||
const y = generator.valueToCode(block, "y", BlocklyJS.Order.ATOMIC) || 0;
|
||||
return `pointsTowards(${x}, ${y});\n`;
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["get_position"] = function (block) {
|
||||
const menu = block.getFieldValue("MENU");
|
||||
return [`sprite["${menu}"]`, BlocklyJS.Order.NONE];
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["angle_turn"] = function (
|
||||
block,
|
||||
generator
|
||||
) {
|
||||
const direction = block.getFieldValue("DIRECTION");
|
||||
let amount =
|
||||
generator.valueToCode(block, "AMOUNT", BlocklyJS.Order.ATOMIC) || 0;
|
||||
if (direction === "left") amount = `-(${amount})`;
|
||||
return `setAngle(${amount}, true);\n`;
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["angle_set"] = function (
|
||||
block,
|
||||
generator
|
||||
) {
|
||||
const amount =
|
||||
generator.valueToCode(block, "AMOUNT", BlocklyJS.Order.ATOMIC) || 0;
|
||||
return `setAngle(${amount}, false);\n`;
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["get_angle"] = () => [
|
||||
"sprite.angle",
|
||||
BlocklyJS.Order.NONE,
|
||||
];
|
||||
115
src/blocks/pen.js
Normal file
115
src/blocks/pen.js
Normal file
@@ -0,0 +1,115 @@
|
||||
import * as Blockly from "blockly";
|
||||
import * as BlocklyJS from "blockly/javascript";
|
||||
|
||||
Blockly.Blocks["pen_down"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput().appendField("pen down");
|
||||
this.setPreviousStatement(true, "default");
|
||||
this.setNextStatement(true, "default");
|
||||
this.setColour("#0fbd8c");
|
||||
this.setTooltip("Put the pen down to draw");
|
||||
},
|
||||
};
|
||||
BlocklyJS.javascriptGenerator.forBlock["pen_down"] = function () {
|
||||
return "setPenStatus(true);\n";
|
||||
};
|
||||
|
||||
Blockly.Blocks["pen_up"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput().appendField("pen up");
|
||||
this.setPreviousStatement(true, "default");
|
||||
this.setNextStatement(true, "default");
|
||||
this.setColour("#0fbd8c");
|
||||
this.setTooltip("Lift the pen up");
|
||||
},
|
||||
};
|
||||
BlocklyJS.javascriptGenerator.forBlock["pen_up"] = function () {
|
||||
return "setPenStatus(false);\n";
|
||||
};
|
||||
|
||||
Blockly.Blocks["set_pen_color"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput().appendField("set pen color");
|
||||
this.appendValueInput("R").setCheck("Number").appendField("R");
|
||||
this.appendValueInput("G").setCheck("Number").appendField("G");
|
||||
this.appendValueInput("B").setCheck("Number").appendField("B");
|
||||
this.setInputsInline(true);
|
||||
this.setPreviousStatement(true, "default");
|
||||
this.setNextStatement(true, "default");
|
||||
this.setColour("#0fbd8c");
|
||||
this.setTooltip("Set the pen color to a RGB value");
|
||||
},
|
||||
};
|
||||
BlocklyJS.javascriptGenerator.forBlock["set_pen_color"] = function (
|
||||
block,
|
||||
generator
|
||||
) {
|
||||
const r = generator.valueToCode(block, "R", BlocklyJS.Order.ATOMIC) || 0;
|
||||
const g = generator.valueToCode(block, "G", BlocklyJS.Order.ATOMIC) || 0;
|
||||
const b = generator.valueToCode(block, "B", BlocklyJS.Order.ATOMIC) || 0;
|
||||
return `setPenColor(${r}, ${g}, ${b});\n`;
|
||||
};
|
||||
|
||||
Blockly.Blocks["set_pen_color_combined"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput("MODE")
|
||||
.appendField("set pen color to")
|
||||
.appendField(
|
||||
new Blockly.FieldDropdown([
|
||||
["RGB", "RGB"],
|
||||
["HEX", "HEX"],
|
||||
]),
|
||||
"MODE"
|
||||
);
|
||||
this.appendValueInput("VALUE").setCheck(["String", "Number"]);
|
||||
this.setInputsInline(true);
|
||||
this.setPreviousStatement(true, "default");
|
||||
this.setNextStatement(true, "default");
|
||||
this.setColour("#0fbd8c");
|
||||
this.setTooltip("Set the pen color to a RGB or HEX value.");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["set_pen_color_combined"] = function (
|
||||
block,
|
||||
generator
|
||||
) {
|
||||
const mode = block.getFieldValue("MODE");
|
||||
const value = generator.valueToCode(block, "VALUE", BlocklyJS.Order.ATOMIC);
|
||||
if (mode === "HEX") return `setPenColorHex(${value});\n`;
|
||||
else return `setPenColor(${value});\n`;
|
||||
};
|
||||
|
||||
Blockly.Blocks["set_pen_size"] = {
|
||||
init: function () {
|
||||
this.appendValueInput("SIZE")
|
||||
.setCheck("Number")
|
||||
.appendField("set pen size to");
|
||||
this.appendDummyInput().appendField("px");
|
||||
this.setPreviousStatement(true, "default");
|
||||
this.setNextStatement(true, "default");
|
||||
this.setColour("#0fbd8c");
|
||||
this.setTooltip("Set the pen thickness to a specific value in pixels");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["set_pen_size"] = function (
|
||||
block,
|
||||
generator
|
||||
) {
|
||||
const size =
|
||||
generator.valueToCode(block, "SIZE", BlocklyJS.Order.ATOMIC) || 1;
|
||||
return `setPenSize("${size}");\n`;
|
||||
};
|
||||
|
||||
Blockly.Blocks["clear_pen"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput().appendField("clear pen");
|
||||
this.setPreviousStatement(true, "default");
|
||||
this.setNextStatement(true, "default");
|
||||
this.setColour("#0fbd8c");
|
||||
this.setTooltip("Clear all pen drawings");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["clear_pen"] = () => "clearPen();\n";
|
||||
353
src/blocks/set.js
Normal file
353
src/blocks/set.js
Normal file
@@ -0,0 +1,353 @@
|
||||
import * as Blockly from "blockly";
|
||||
import * as BlocklyJS from "blockly/javascript";
|
||||
|
||||
Blockly.Blocks["sets_create_with"] = {
|
||||
init: function () {
|
||||
this.setStyle("set_blocks");
|
||||
this.setHelpUrl("");
|
||||
this.itemCount_ = 0;
|
||||
this.updateShape_();
|
||||
this.setOutput(true, "Set");
|
||||
this.setMutator(
|
||||
new Blockly.icons.MutatorIcon(["sets_create_with_item"], this)
|
||||
);
|
||||
this.setTooltip("Create a set with any number of elements.");
|
||||
},
|
||||
|
||||
mutationToDom: function () {
|
||||
const container = Blockly.utils.xml.createElement("mutation");
|
||||
container.setAttribute("items", String(this.itemCount_));
|
||||
return container;
|
||||
},
|
||||
|
||||
domToMutation: function (xmlElement) {
|
||||
const items = xmlElement.getAttribute("items");
|
||||
this.itemCount_ = items ? parseInt(items, 10) : 0;
|
||||
this.updateShape_();
|
||||
},
|
||||
|
||||
saveExtraState: function () {
|
||||
return { itemCount: this.itemCount_ };
|
||||
},
|
||||
|
||||
loadExtraState: function (state) {
|
||||
if (state && typeof state.itemCount === "number") {
|
||||
this.itemCount_ = state.itemCount;
|
||||
} else {
|
||||
this.itemCount_ = 0;
|
||||
}
|
||||
this.updateShape_();
|
||||
},
|
||||
|
||||
decompose: function (workspace) {
|
||||
const containerBlock = workspace.newBlock("sets_create_with_container");
|
||||
containerBlock.initSvg();
|
||||
let connection = containerBlock.getInput("STACK").connection;
|
||||
for (let i = 0; i < this.itemCount_; i++) {
|
||||
const itemBlock = workspace.newBlock("sets_create_with_item");
|
||||
itemBlock.initSvg();
|
||||
connection.connect(itemBlock.previousConnection);
|
||||
connection = itemBlock.nextConnection;
|
||||
}
|
||||
return containerBlock;
|
||||
},
|
||||
|
||||
compose: function (containerBlock) {
|
||||
let itemBlock = containerBlock.getInputTargetBlock("STACK");
|
||||
const connections = [];
|
||||
while (itemBlock) {
|
||||
if (itemBlock.isInsertionMarker && itemBlock.isInsertionMarker()) {
|
||||
itemBlock = itemBlock.getNextBlock();
|
||||
continue;
|
||||
}
|
||||
connections.push(itemBlock.valueConnection_ || null);
|
||||
itemBlock = itemBlock.getNextBlock();
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.itemCount_; i++) {
|
||||
const input = this.getInput("ADD" + i);
|
||||
const targetConnection =
|
||||
input && input.connection && input.connection.targetConnection;
|
||||
if (targetConnection && !connections.includes(targetConnection)) {
|
||||
targetConnection.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
this.itemCount_ = connections.length;
|
||||
this.updateShape_();
|
||||
|
||||
for (let i = 0; i < this.itemCount_; i++) {
|
||||
if (connections[i]) {
|
||||
connections[i].reconnect(this, "ADD" + i);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
saveConnections: function (containerBlock) {
|
||||
let itemBlock = containerBlock.getInputTargetBlock("STACK");
|
||||
let i = 0;
|
||||
while (itemBlock) {
|
||||
if (itemBlock.isInsertionMarker && itemBlock.isInsertionMarker()) {
|
||||
itemBlock = itemBlock.getNextBlock();
|
||||
continue;
|
||||
}
|
||||
const input = this.getInput("ADD" + i);
|
||||
itemBlock.valueConnection_ =
|
||||
input && input.connection && input.connection.targetConnection;
|
||||
itemBlock = itemBlock.getNextBlock();
|
||||
i++;
|
||||
}
|
||||
},
|
||||
|
||||
updateShape_: function () {
|
||||
if (this.itemCount_ === 0) {
|
||||
if (!this.getInput("EMPTY")) {
|
||||
this.appendDummyInput("EMPTY").appendField("create empty set");
|
||||
}
|
||||
} else {
|
||||
if (this.getInput("EMPTY")) {
|
||||
this.removeInput("EMPTY");
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.itemCount_; i++) {
|
||||
if (!this.getInput("ADD" + i)) {
|
||||
const input = this.appendValueInput("ADD" + i).setAlign(
|
||||
Blockly.inputs.Align.RIGHT
|
||||
);
|
||||
if (i === 0) {
|
||||
input.appendField("create set with");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let i = this.itemCount_;
|
||||
while (this.getInput("ADD" + i)) {
|
||||
this.removeInput("ADD" + i);
|
||||
i++;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Blockly.Blocks["sets_create_with_container"] = {
|
||||
init: function () {
|
||||
this.setStyle("set_blocks");
|
||||
this.appendDummyInput().appendField("set");
|
||||
this.appendStatementInput("STACK");
|
||||
this.contextMenu = false;
|
||||
},
|
||||
};
|
||||
|
||||
Blockly.Blocks["sets_create_with_item"] = {
|
||||
init: function () {
|
||||
this.setStyle("set_blocks");
|
||||
this.appendDummyInput().appendField("element");
|
||||
this.setPreviousStatement(true);
|
||||
this.setNextStatement(true);
|
||||
this.contextMenu = false;
|
||||
/** @type {Blockly.Connection?} */
|
||||
this.valueConnection_ = null;
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["sets_create_with"] = function (
|
||||
block,
|
||||
generator
|
||||
) {
|
||||
const elements = [];
|
||||
for (let i = 0; i < block.itemCount_; i++) {
|
||||
const code =
|
||||
generator.valueToCode(block, "ADD" + i, BlocklyJS.Order.NONE) || "null";
|
||||
elements.push(code);
|
||||
}
|
||||
let code;
|
||||
if (elements.length === 0) {
|
||||
code = "new Set()";
|
||||
} else {
|
||||
code = "new Set([" + elements.join(", ") + "])";
|
||||
}
|
||||
return [code, BlocklyJS.Order.NONE];
|
||||
};
|
||||
|
||||
Blockly.Blocks["sets_convert"] = {
|
||||
init: function () {
|
||||
const dropdown = new Blockly.FieldDropdown([
|
||||
["set from list", "SET"],
|
||||
["list from set", "LIST"],
|
||||
]);
|
||||
dropdown.setValidator((newMode) => {
|
||||
this.updateType_(newMode);
|
||||
});
|
||||
|
||||
this.setStyle("set_blocks");
|
||||
this.appendValueInput("INPUT")
|
||||
.setCheck("String")
|
||||
.appendField("make")
|
||||
.appendField(dropdown, "MODE");
|
||||
this.setInputsInline(true);
|
||||
this.setOutput(true, "Set");
|
||||
this.setTooltip(() => {
|
||||
const mode = this.getFieldValue("MODE");
|
||||
if (mode === "SET") {
|
||||
return "Convert a list into a set (removes duplicates).";
|
||||
} else if (mode === "LIST") {
|
||||
return "Convert a set into a list.";
|
||||
}
|
||||
throw Error("Unknown mode: " + mode);
|
||||
});
|
||||
},
|
||||
updateType_: function (newMode) {
|
||||
const mode = this.getFieldValue("MODE");
|
||||
if (mode !== newMode) {
|
||||
const inputConnection = this.getInput("INPUT")?.connection;
|
||||
inputConnection?.setShadowDom(null);
|
||||
const inputBlock = inputConnection?.targetBlock();
|
||||
|
||||
if (inputBlock) {
|
||||
inputConnection.disconnect();
|
||||
if (inputBlock.isShadow()) {
|
||||
inputBlock.dispose(false);
|
||||
} else {
|
||||
this.bumpNeighbours();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (newMode === "SET") {
|
||||
this.outputConnection.setCheck("Set");
|
||||
this.getInput("INPUT").setCheck("Array");
|
||||
} else {
|
||||
this.outputConnection.setCheck("Array");
|
||||
this.getInput("INPUT").setCheck("Set");
|
||||
}
|
||||
},
|
||||
mutationToDom: function () {
|
||||
const container = Blockly.utils.xml.createElement("mutation");
|
||||
container.setAttribute("mode", this.getFieldValue("MODE"));
|
||||
return container;
|
||||
},
|
||||
domToMutation: function (xmlElement) {
|
||||
this.updateType_(xmlElement.getAttribute("mode"));
|
||||
},
|
||||
saveExtraState: function () {
|
||||
return { mode: this.getFieldValue("MODE") };
|
||||
},
|
||||
loadExtraState: function (state) {
|
||||
this.updateType_(state["mode"]);
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["sets_convert"] = function (block) {
|
||||
const input =
|
||||
BlocklyJS.javascriptGenerator.valueToCode(
|
||||
block,
|
||||
"INPUT",
|
||||
BlocklyJS.Order.ATOMIC
|
||||
) || "null";
|
||||
const mode = block.getFieldValue("MODE");
|
||||
|
||||
if (mode === "SET") {
|
||||
return [`new Set(${input})`, BlocklyJS.Order.NONE];
|
||||
} else if (mode === "LIST") {
|
||||
return [`[...${input}]`, BlocklyJS.Order.NONE];
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks["sets_add"] = {
|
||||
init: function () {
|
||||
this.appendValueInput("SET").setCheck("Set").appendField("in set");
|
||||
this.appendValueInput("VALUE").setCheck(null).appendField("add");
|
||||
this.setPreviousStatement(true, null);
|
||||
this.setNextStatement(true, null);
|
||||
this.setInputsInline(true);
|
||||
this.setStyle("set_blocks");
|
||||
this.setTooltip("Adds a value to the set.");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["sets_add"] = function (
|
||||
block,
|
||||
generator
|
||||
) {
|
||||
const set = generator.valueToCode(block, "SET", BlocklyJS.Order.ATOMIC);
|
||||
const value = generator.valueToCode(block, "VALUE", BlocklyJS.Order.ATOMIC);
|
||||
return `${set}.add(${value});\n`;
|
||||
};
|
||||
|
||||
Blockly.Blocks["sets_delete"] = {
|
||||
init: function () {
|
||||
this.appendValueInput("SET").setCheck("Set").appendField("in set");
|
||||
this.appendValueInput("VALUE").setCheck(null).appendField("delete");
|
||||
this.setPreviousStatement(true, null);
|
||||
this.setNextStatement(true, null);
|
||||
this.setInputsInline(true);
|
||||
this.setStyle("set_blocks");
|
||||
this.setTooltip("Deletes a value from the set.");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["sets_delete"] = function (
|
||||
block,
|
||||
generator
|
||||
) {
|
||||
const set = generator.valueToCode(block, "SET", BlocklyJS.Order.ATOMIC);
|
||||
const value = generator.valueToCode(block, "VALUE", BlocklyJS.Order.ATOMIC);
|
||||
return `${set}.delete(${value});\n`;
|
||||
};
|
||||
|
||||
Blockly.Blocks["sets_has"] = {
|
||||
init: function () {
|
||||
this.appendValueInput("SET").setCheck("Set").appendField("does set");
|
||||
this.appendValueInput("VALUE").setCheck(null).appendField("have");
|
||||
this.setOutput(true, "Boolean");
|
||||
this.setInputsInline(true);
|
||||
this.setStyle("set_blocks");
|
||||
this.setTooltip("Returns true if the set contains the value.");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["sets_has"] = function (
|
||||
block,
|
||||
generator
|
||||
) {
|
||||
const set = generator.valueToCode(block, "SET", BlocklyJS.Order.ATOMIC);
|
||||
const value = generator.valueToCode(block, "VALUE", BlocklyJS.Order.ATOMIC);
|
||||
return [`${set}.has(${value})`, BlocklyJS.Order.NONE];
|
||||
};
|
||||
|
||||
Blockly.Blocks["sets_size"] = {
|
||||
init: function () {
|
||||
this.appendValueInput("SET").setCheck("Set").appendField("size of set");
|
||||
this.setOutput(true, "Number");
|
||||
this.setInputsInline(true);
|
||||
this.setStyle("set_blocks");
|
||||
this.setTooltip("Returns how many items are in the set.");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["sets_size"] = function (
|
||||
block,
|
||||
generator
|
||||
) {
|
||||
const set = generator.valueToCode(block, "SET", BlocklyJS.Order.ATOMIC);
|
||||
return [`${set}.size`, BlocklyJS.Order.NONE];
|
||||
};
|
||||
|
||||
Blockly.Blocks["sets_merge"] = {
|
||||
init: function () {
|
||||
this.appendValueInput("SET1").setCheck("Set").appendField("merge set");
|
||||
this.appendValueInput("SET2").setCheck("Set").appendField("with");
|
||||
this.setOutput(true, "Set");
|
||||
this.setInputsInline(true);
|
||||
this.setStyle("set_blocks");
|
||||
this.setTooltip("Creates a new set combining all values from two sets");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["sets_merge"] = function (
|
||||
block,
|
||||
generator
|
||||
) {
|
||||
const set1 = generator.valueToCode(block, "SET1", BlocklyJS.Order.ATOMIC);
|
||||
const set2 = generator.valueToCode(block, "SET2", BlocklyJS.Order.ATOMIC);
|
||||
return [`new Set([...${set1}, ...${set2}])`, BlocklyJS.Order.NONE];
|
||||
};
|
||||
137
src/blocks/sound.js
Normal file
137
src/blocks/sound.js
Normal file
@@ -0,0 +1,137 @@
|
||||
import * as Blockly from "blockly";
|
||||
import * as BlocklyJS from "blockly/javascript";
|
||||
|
||||
// Function to get available sounds - you'll need to implement this in your main editor
|
||||
// This should return an array of sound names from your project
|
||||
function getAvailableSounds() {
|
||||
// This needs to be connected to your sound list
|
||||
// For now, returning a default option
|
||||
if (window.projectSounds && window.projectSounds.length > 0) {
|
||||
return window.projectSounds.map(sound => [sound, sound]);
|
||||
}
|
||||
return [["no sounds", ""]];
|
||||
}
|
||||
|
||||
Blockly.Blocks["play_sound"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput()
|
||||
.appendField("play sound")
|
||||
.appendField(new Blockly.FieldDropdown(
|
||||
function() {
|
||||
return getAvailableSounds();
|
||||
}
|
||||
), "SOUND_NAME");
|
||||
this.appendDummyInput().appendField(
|
||||
new Blockly.FieldDropdown([
|
||||
["until finished", "true"],
|
||||
["without waiting", "false"],
|
||||
]),
|
||||
"wait"
|
||||
);
|
||||
this.setColour("#ff66ba");
|
||||
this.setPreviousStatement(true, "default");
|
||||
this.setNextStatement(true, "default");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["play_sound"] = function (block, generator) {
|
||||
var name = "'" + block.getFieldValue("SOUND_NAME") + "'";
|
||||
var wait = block.getFieldValue("wait");
|
||||
return `await playSound(${name}, ${wait});\n`;
|
||||
};
|
||||
|
||||
Blockly.Blocks["stop_sound"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput()
|
||||
.appendField("stop sound")
|
||||
.appendField(new Blockly.FieldDropdown(
|
||||
function() {
|
||||
return getAvailableSounds();
|
||||
}
|
||||
), "SOUND_NAME");
|
||||
this.setColour("#ff66ba");
|
||||
this.setPreviousStatement(true, "default");
|
||||
this.setNextStatement(true, "default");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["stop_sound"] = function (block, generator) {
|
||||
var name = "'" + block.getFieldValue("SOUND_NAME") + "'";
|
||||
return `stopSound(${name});\n`;
|
||||
};
|
||||
|
||||
Blockly.Blocks["stop_all_sounds"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput()
|
||||
.appendField("stop")
|
||||
.appendField(
|
||||
new Blockly.FieldDropdown([
|
||||
["all", "false"],
|
||||
["my", "true"],
|
||||
]),
|
||||
"who"
|
||||
)
|
||||
.appendField("sounds");
|
||||
this.setColour("#ff66ba");
|
||||
this.setPreviousStatement(true, "default");
|
||||
this.setNextStatement(true, "default");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["stop_all_sounds"] = function (block) {
|
||||
var who = block.getFieldValue("who");
|
||||
var code = `stopAllSounds(${who});\n`;
|
||||
return code;
|
||||
};
|
||||
|
||||
Blockly.Blocks["set_sound_property"] = {
|
||||
init: function () {
|
||||
this.appendValueInput("value")
|
||||
.setCheck("Number")
|
||||
.appendField("set")
|
||||
.appendField(
|
||||
new Blockly.FieldDropdown([
|
||||
["volume", "volume"],
|
||||
["speed", "speed"],
|
||||
]),
|
||||
"property"
|
||||
)
|
||||
.appendField("to");
|
||||
this.appendDummyInput().appendField("%");
|
||||
this.setColour("#ff66ba");
|
||||
this.setPreviousStatement(true, "default");
|
||||
this.setNextStatement(true, "default");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["set_sound_property"] = function (
|
||||
block,
|
||||
generator
|
||||
) {
|
||||
var value = generator.valueToCode(
|
||||
block,
|
||||
"value",
|
||||
BlocklyJS.Order.ATOMIC
|
||||
);
|
||||
var property = block.getFieldValue("property");
|
||||
return `setSoundProperty("${property}", ${value});\n`;
|
||||
};
|
||||
|
||||
Blockly.Blocks["get_sound_property"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput().appendField(
|
||||
new Blockly.FieldDropdown([
|
||||
["volume", "volume"],
|
||||
["speed", "speed"],
|
||||
]),
|
||||
"property"
|
||||
);
|
||||
this.setColour("#ff66ba");
|
||||
this.setOutput(true, "Number");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["get_sound_property"] = function (block) {
|
||||
var property = block.getFieldValue("property");
|
||||
return [`soundProperties["${property}"]`, BlocklyJS.Order.NONE];
|
||||
};
|
||||
137
src/blocks/system.js
Normal file
137
src/blocks/system.js
Normal file
@@ -0,0 +1,137 @@
|
||||
import * as Blockly from "blockly";
|
||||
import * as BlocklyJS from "blockly/javascript";
|
||||
|
||||
const normalKeys = [
|
||||
..."abcdefghijklmnopqrstuvwxyz",
|
||||
..."abcdefghijklmnopqrstuvwxyz0123456789".toUpperCase(),
|
||||
];
|
||||
|
||||
Blockly.Blocks["key_pressed"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput()
|
||||
.appendField("is")
|
||||
.appendField(
|
||||
new Blockly.FieldDropdown([
|
||||
["any", "any"],
|
||||
["space", " "],
|
||||
["enter", "Enter"],
|
||||
["escape", "Escape"],
|
||||
["up arrow", "ArrowUp"],
|
||||
["down arrow", "ArrowDown"],
|
||||
["left arrow", "ArrowLeft"],
|
||||
["right arrow", "ArrowRight"],
|
||||
...normalKeys.map((i) => [i, i]),
|
||||
]),
|
||||
"KEY"
|
||||
)
|
||||
.appendField("key down");
|
||||
this.setOutput(true, "Boolean");
|
||||
this.setColour("#5CB1D6");
|
||||
},
|
||||
};
|
||||
|
||||
Blockly.Blocks["get_mouse_position"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput()
|
||||
.appendField("mouse")
|
||||
.appendField(
|
||||
new Blockly.FieldDropdown([
|
||||
["x", "x"],
|
||||
["y", "y"],
|
||||
]),
|
||||
"MENU"
|
||||
);
|
||||
this.setOutput(true, "Number");
|
||||
this.setColour("#5CB1D6");
|
||||
},
|
||||
};
|
||||
|
||||
Blockly.Blocks["mouse_button_pressed"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput()
|
||||
.appendField("is")
|
||||
.appendField(
|
||||
new Blockly.FieldDropdown([
|
||||
["left", "0"],
|
||||
["middle", "1"],
|
||||
["right", "2"],
|
||||
["back", "3"],
|
||||
["forward", "4"],
|
||||
["any", "any"],
|
||||
]),
|
||||
"BUTTON"
|
||||
)
|
||||
.appendField("mouse button down");
|
||||
this.setOutput(true, "Boolean");
|
||||
this.setColour("#5CB1D6");
|
||||
},
|
||||
};
|
||||
|
||||
Blockly.Blocks["all_keys_pressed"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput().appendField("keys currently down");
|
||||
this.setOutput(true, "Array");
|
||||
this.setColour("#5CB1D6");
|
||||
},
|
||||
};
|
||||
|
||||
Blockly.Blocks["mouse_over"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput().appendField("is cursor over me");
|
||||
this.setOutput(true, "Boolean");
|
||||
this.setColour("#5CB1D6");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["key_pressed"] = function (block, generator) {
|
||||
const key = block.getFieldValue("KEY");
|
||||
const safeKey = generator.quote_(key);
|
||||
return [`isKeyPressed(${safeKey})`, BlocklyJS.Order.NONE];
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["get_mouse_position"] = function (block) {
|
||||
const menu = block.getFieldValue("MENU");
|
||||
return [`getMousePosition("${menu}")`, BlocklyJS.Order.NONE];
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["mouse_button_pressed"] = function (
|
||||
block,
|
||||
generator
|
||||
) {
|
||||
const button = block.getFieldValue("BUTTON");
|
||||
const safeButton = generator.quote_(button);
|
||||
return [`isMouseButtonPressed(${safeButton})`, BlocklyJS.Order.NONE];
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["all_keys_pressed"] = () => [
|
||||
"Object.keys(keysPressed).filter(k => keysPressed[k])",
|
||||
BlocklyJS.Order.NONE,
|
||||
];
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["mouse_over"] = () => [
|
||||
"isMouseTouchingSprite()",
|
||||
BlocklyJS.Order.NONE,
|
||||
];
|
||||
|
||||
Blockly.Blocks["window_size"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput()
|
||||
.appendField("window")
|
||||
.appendField(
|
||||
new Blockly.FieldDropdown([
|
||||
["width", "width"],
|
||||
["height", "height"],
|
||||
]),
|
||||
"MENU"
|
||||
);
|
||||
this.setOutput(true, "Number");
|
||||
this.setColour("#5CB1D6");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["window_size"] = function (block) {
|
||||
return [
|
||||
`window.inner${block.getFieldValue("MENU") === "width" ? "Width" : "Height"}`,
|
||||
BlocklyJS.Order.NONE,
|
||||
];
|
||||
};
|
||||
291
src/blocks/tween.js
Normal file
291
src/blocks/tween.js
Normal file
@@ -0,0 +1,291 @@
|
||||
import * as Blockly from "blockly";
|
||||
import * as BlocklyJS from "blockly/javascript";
|
||||
|
||||
const TweenEasing = {
|
||||
InLinear: (t) => t,
|
||||
OutLinear: (t) => t,
|
||||
InOutLinear: (t) => t,
|
||||
InSine: (t) => 1 - Math.cos((t * Math.PI) / 2),
|
||||
OutSine: (t) => Math.sin((t * Math.PI) / 2),
|
||||
InOutSine: (t) => -(Math.cos(Math.PI * t) - 1) / 2,
|
||||
InQuad: (t) => t * t,
|
||||
OutQuad: (t) => 1 - (1 - t) * (1 - t),
|
||||
InOutQuad: (t) => (t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2),
|
||||
InCubic: (t) => t * t * t,
|
||||
OutCubic: (t) => 1 - Math.pow(1 - t, 3),
|
||||
InOutCubic: (t) =>
|
||||
t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2,
|
||||
InQuart: (t) => t * t * t * t,
|
||||
OutQuart: (t) => 1 - Math.pow(1 - t, 4),
|
||||
InOutQuart: (t) =>
|
||||
t < 0.5 ? 8 * t * t * t * t : 1 - Math.pow(-2 * t + 2, 4) / 2,
|
||||
InQuint: (t) => t * t * t * t * t,
|
||||
OutQuint: (t) => 1 - Math.pow(1 - t, 5),
|
||||
InOutQuint: (t) =>
|
||||
t < 0.5 ? 16 * t * t * t * t * t : 1 - Math.pow(-2 * t + 2, 5) / 2,
|
||||
InExpo: (t) => (t === 0 ? 0 : Math.pow(2, 10 * t - 10)),
|
||||
OutExpo: (t) => (t === 1 ? 1 : 1 - Math.pow(2, -10 * t)),
|
||||
InOutExpo: (t) => {
|
||||
if (t === 0) return 0;
|
||||
if (t === 1) return 1;
|
||||
return t < 0.5
|
||||
? Math.pow(2, 20 * t - 10) / 2
|
||||
: (2 - Math.pow(2, -20 * t + 10)) / 2;
|
||||
},
|
||||
InCirc: (t) => 1 - Math.sqrt(1 - Math.pow(t, 2)),
|
||||
OutCirc: (t) => Math.sqrt(1 - Math.pow(t - 1, 2)),
|
||||
InOutCirc: (t) =>
|
||||
t < 0.5
|
||||
? (1 - Math.sqrt(1 - Math.pow(2 * t, 2))) / 2
|
||||
: (Math.sqrt(1 - Math.pow(-2 * t + 2, 2)) + 1) / 2,
|
||||
InBack: (t) => {
|
||||
const c1 = 1.70158,
|
||||
c3 = c1 + 1;
|
||||
return c3 * t * t * t - c1 * t * t;
|
||||
},
|
||||
OutBack: (t) => {
|
||||
const c1 = 1.70158,
|
||||
c3 = c1 + 1;
|
||||
return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);
|
||||
},
|
||||
InOutBack: (t) => {
|
||||
const c1 = 1.70158,
|
||||
c2 = c1 * 1.525;
|
||||
return t < 0.5
|
||||
? (Math.pow(2 * t, 2) * ((c2 + 1) * 2 * t - c2)) / 2
|
||||
: (Math.pow(2 * t - 2, 2) * ((c2 + 1) * (2 * t - 2) + c2) + 2) / 2;
|
||||
},
|
||||
InElastic: (t) => {
|
||||
const c4 = (2 * Math.PI) / 3;
|
||||
if (t === 0) return 0;
|
||||
if (t === 1) return 1;
|
||||
return -Math.pow(2, 10 * t - 10) * Math.sin((t * 10 - 10.75) * c4);
|
||||
},
|
||||
OutElastic: (t) => {
|
||||
const c4 = (2 * Math.PI) / 3;
|
||||
if (t === 0) return 0;
|
||||
if (t === 1) return 1;
|
||||
return Math.pow(2, -10 * t) * Math.sin((t * 10 - 0.75) * c4) + 1;
|
||||
},
|
||||
InOutElastic: (t) => {
|
||||
const c5 = (2 * Math.PI) / 4.5;
|
||||
if (t === 0) return 0;
|
||||
if (t === 1) return 1;
|
||||
return t < 0.5
|
||||
? -(Math.pow(2, 20 * t - 10) * Math.sin((20 * t - 11.125) * c5)) / 2
|
||||
: (Math.pow(2, -20 * t + 10) * Math.sin((20 * t - 11.125) * c5)) / 2 + 1;
|
||||
},
|
||||
InBounce: (t) => 1 - TweenEasing.OutBounce(1 - t),
|
||||
OutBounce: (t) => {
|
||||
const n1 = 7.5625,
|
||||
d1 = 2.75;
|
||||
if (t < 1 / d1) {
|
||||
return n1 * t * t;
|
||||
} else if (t < 2 / d1) {
|
||||
return n1 * (t -= 1.5 / d1) * t + 0.75;
|
||||
} else if (t < 2.5 / d1) {
|
||||
return n1 * (t -= 2.25 / d1) * t + 0.9375;
|
||||
} else {
|
||||
return n1 * (t -= 2.625 / d1) * t + 0.984375;
|
||||
}
|
||||
},
|
||||
InOutBounce: (t) =>
|
||||
t < 0.5
|
||||
? (1 - TweenEasing.OutBounce(1 - 2 * t)) / 2
|
||||
: (1 + TweenEasing.OutBounce(2 * t - 1)) / 2,
|
||||
};
|
||||
Object.defineProperty(window, "TweenEasing", {
|
||||
value: Object.freeze(TweenEasing),
|
||||
configurable: false,
|
||||
writable: false,
|
||||
enumerable: true
|
||||
});
|
||||
|
||||
Blockly.Blocks["tween_block"] = {
|
||||
init: function () {
|
||||
this.appendValueInput("FROM").setCheck("Number").appendField("tween from");
|
||||
this.appendValueInput("TO").setCheck("Number").appendField("to");
|
||||
this.appendDummyInput().appendField("in");
|
||||
this.appendValueInput("DURATION").setCheck("Number");
|
||||
this.appendDummyInput().appendField("seconds using");
|
||||
this.appendDummyInput()
|
||||
.appendField(
|
||||
new Blockly.FieldDropdown([
|
||||
["linear", "Linear"],
|
||||
["sine", "Sine"],
|
||||
["quadratic", "Quad"],
|
||||
["cubic", "Cubic"],
|
||||
["quartic", "Quart"],
|
||||
["quintic", "Quint"],
|
||||
["expo", "Expo"],
|
||||
["circ", "Circ"],
|
||||
["back", "Back"],
|
||||
["elastic", "Elastic"],
|
||||
["bounce", "Bounce"],
|
||||
]),
|
||||
"EASING_TYPE"
|
||||
)
|
||||
.appendField(
|
||||
new Blockly.FieldDropdown([
|
||||
["in", "In"],
|
||||
["out", "Out"],
|
||||
["in-out", "InOut"],
|
||||
]),
|
||||
"EASING_MODE"
|
||||
);
|
||||
|
||||
this.appendStatementInput("DO").setCheck("default");
|
||||
this.appendDummyInput()
|
||||
.setAlign(1)
|
||||
.appendField(
|
||||
new Blockly.FieldDropdown([
|
||||
["wait", "WAIT"],
|
||||
["don't wait", "DONT_WAIT"],
|
||||
]),
|
||||
"WAIT_MODE"
|
||||
)
|
||||
.appendField("until finished");
|
||||
this.setInputsInline(true);
|
||||
this.setPreviousStatement(true, "default");
|
||||
this.setNextStatement(true, "default");
|
||||
this.setColour("#32a2c0");
|
||||
this.setTooltip(
|
||||
"Tween a value from one number to another over time using easing"
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["tween_block"] = function (block, generator) {
|
||||
const easingType = block.getFieldValue("EASING_TYPE");
|
||||
const easingMode = block.getFieldValue("EASING_MODE");
|
||||
const from =
|
||||
generator.valueToCode(block, "FROM", BlocklyJS.Order.ATOMIC) ||
|
||||
"0";
|
||||
const to =
|
||||
generator.valueToCode(block, "TO", BlocklyJS.Order.ATOMIC) || "0";
|
||||
const duration =
|
||||
generator.valueToCode(block, "DURATION", BlocklyJS.Order.ATOMIC) ||
|
||||
"1";
|
||||
const waitMode = block.getFieldValue("WAIT_MODE");
|
||||
|
||||
let branch = BlocklyJS.javascriptGenerator.statementToCode(block, "DO");
|
||||
branch = BlocklyJS.javascriptGenerator.addLoopTrap(branch, block);
|
||||
|
||||
const code = `await startTween({
|
||||
from: ${from},
|
||||
to: ${to},
|
||||
duration: ${duration},
|
||||
easing: "${easingMode + easingType}",
|
||||
wait: ${waitMode === "WAIT"},
|
||||
onUpdate: async (tweenValue) => {
|
||||
${branch} }
|
||||
});\n`;
|
||||
|
||||
return code;
|
||||
};
|
||||
|
||||
Blockly.Blocks["tween_block_value"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput("name").appendField("current tween value");
|
||||
this.setInputsInline(true);
|
||||
this.setColour("#32a2c0");
|
||||
this.setOutput(true, "Number");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["tween_block_value"] = () => [
|
||||
"tweenValue",
|
||||
BlocklyJS.Order.NONE,
|
||||
];
|
||||
|
||||
Blockly.Blocks["tween_sprite_property"] = {
|
||||
init: function () {
|
||||
this.appendValueInput("TO")
|
||||
.setCheck("Number")
|
||||
.appendField("tween")
|
||||
.appendField(
|
||||
new Blockly.FieldDropdown([
|
||||
["x position", "x"],
|
||||
["y position", "y"],
|
||||
["angle", "angle"],
|
||||
["size", "size"],
|
||||
]),
|
||||
"PROPERTY"
|
||||
)
|
||||
.appendField("to");
|
||||
this.appendValueInput("DURATION").setCheck("Number").appendField("in");
|
||||
this.appendDummyInput()
|
||||
.appendField("seconds using")
|
||||
.appendField(
|
||||
new Blockly.FieldDropdown([
|
||||
["linear", "Linear"],
|
||||
["sine", "Sine"],
|
||||
["quadratic", "Quad"],
|
||||
["cubic", "Cubic"],
|
||||
["quartic", "Quart"],
|
||||
["quintic", "Quint"],
|
||||
["expo", "Expo"],
|
||||
["circ", "Circ"],
|
||||
["back", "Back"],
|
||||
["elastic", "Elastic"],
|
||||
["bounce", "Bounce"],
|
||||
]),
|
||||
"EASING_TYPE"
|
||||
)
|
||||
.appendField(
|
||||
new Blockly.FieldDropdown([
|
||||
["in", "In"],
|
||||
["out", "Out"],
|
||||
["in-out", "InOut"],
|
||||
]),
|
||||
"EASING_MODE"
|
||||
);
|
||||
this.setInputsInline(true);
|
||||
this.setPreviousStatement(true, "default");
|
||||
this.setNextStatement(true, "default");
|
||||
this.setColour("#32a2c0");
|
||||
this.setTooltip(
|
||||
"Tween a sprite property to a target value over time using easing"
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["tween_sprite_property"] = function (
|
||||
block,
|
||||
generator
|
||||
) {
|
||||
const prop = block.getFieldValue("PROPERTY");
|
||||
const to =
|
||||
generator.valueToCode(block, "TO", BlocklyJS.Order.ATOMIC) || "0";
|
||||
const duration =
|
||||
generator.valueToCode(block, "DURATION", BlocklyJS.Order.ATOMIC) ||
|
||||
"1";
|
||||
const easingType = block.getFieldValue("EASING_TYPE");
|
||||
const easingMode = block.getFieldValue("EASING_MODE");
|
||||
|
||||
let fromGetter, setter;
|
||||
if (prop === "size") {
|
||||
fromGetter = "getSpriteScale()";
|
||||
setter = `setSize(tweenValue, false)`;
|
||||
} else if (prop === "angle") {
|
||||
fromGetter = `sprite.angle`;
|
||||
setter = `setAngle(tweenValue, false)`;
|
||||
} else {
|
||||
fromGetter = `sprite["${prop}"]`;
|
||||
setter = `sprite["${prop}"] = tweenValue`;
|
||||
}
|
||||
|
||||
setter = generator.addLoopTrap(setter, block);
|
||||
|
||||
const code = `await startTween({
|
||||
from: ${fromGetter},
|
||||
to: ${to},
|
||||
duration: ${duration},
|
||||
easing: "${easingMode + easingType}",
|
||||
onUpdate: async (tweenValue) => {
|
||||
${setter};
|
||||
}
|
||||
});\n`;
|
||||
|
||||
return code;
|
||||
};
|
||||
85
src/blocks/variable.js
Normal file
85
src/blocks/variable.js
Normal file
@@ -0,0 +1,85 @@
|
||||
import * as Blockly from "blockly";
|
||||
import * as BlocklyJS from "blockly/javascript";
|
||||
import { projectVariables } from "../scripts/editor";
|
||||
|
||||
function getVariables() {
|
||||
if (Object.keys(projectVariables).length === 0)
|
||||
return [["unknown", "unknown"]];
|
||||
else return Object.keys(projectVariables).map((name) => [name, name]);
|
||||
}
|
||||
|
||||
Blockly.Blocks["get_global_var"] = {
|
||||
init: function () {
|
||||
this.appendDummyInput().appendField(
|
||||
new Blockly.FieldDropdown(() => getVariables()),
|
||||
"VAR"
|
||||
);
|
||||
this.setOutput(true);
|
||||
this.setTooltip("Get a global variable");
|
||||
this.setStyle("variable_blocks");
|
||||
this.customContextMenu = function (options) {
|
||||
const varName = this.getFieldValue("VAR");
|
||||
options.push({
|
||||
text: `Delete "${varName}" variable`,
|
||||
enabled: true,
|
||||
callback: () => {
|
||||
delete projectVariables[varName];
|
||||
this.workspace.refreshToolboxSelection();
|
||||
},
|
||||
});
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
Blockly.Blocks["set_global_var"] = {
|
||||
init: function () {
|
||||
this.appendValueInput("VALUE")
|
||||
.setCheck(null)
|
||||
.appendField("set")
|
||||
.appendField(new Blockly.FieldDropdown(() => getVariables()), "VAR")
|
||||
.appendField("to");
|
||||
this.setPreviousStatement(true, "default");
|
||||
this.setNextStatement(true, "default");
|
||||
this.setStyle("variable_blocks");
|
||||
},
|
||||
};
|
||||
|
||||
Blockly.Blocks["change_global_var"] = {
|
||||
init: function () {
|
||||
this.appendValueInput("VALUE")
|
||||
.setCheck("Number")
|
||||
.appendField("change")
|
||||
.appendField(new Blockly.FieldDropdown(() => getVariables()), "VAR")
|
||||
.appendField("by");
|
||||
this.setPreviousStatement(true, "default");
|
||||
this.setNextStatement(true, "default");
|
||||
this.setStyle("variable_blocks");
|
||||
},
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["get_global_var"] = function (block) {
|
||||
const name = block.getFieldValue("VAR");
|
||||
return [`projectVariables["${name}"]`, BlocklyJS.Order.ATOMIC];
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["set_global_var"] = function (block) {
|
||||
const name = block.getFieldValue("VAR");
|
||||
const value =
|
||||
BlocklyJS.javascriptGenerator.valueToCode(
|
||||
block,
|
||||
"VALUE",
|
||||
BlocklyJS.Order.ASSIGNMENT
|
||||
) || "0";
|
||||
return `projectVariables["${name}"] = ${value};\n`;
|
||||
};
|
||||
|
||||
BlocklyJS.javascriptGenerator.forBlock["change_global_var"] = function (block) {
|
||||
const name = block.getFieldValue("VAR");
|
||||
const value =
|
||||
BlocklyJS.javascriptGenerator.valueToCode(
|
||||
block,
|
||||
"VALUE",
|
||||
BlocklyJS.Order.ATOMIC
|
||||
) || "0";
|
||||
return `projectVariables["${name}"] += ${value};\n`;
|
||||
};
|
||||
Reference in New Issue
Block a user