354 lines
10 KiB
JavaScript
354 lines
10 KiB
JavaScript
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];
|
|
};
|