109 lines
5.1 KiB
JavaScript
109 lines
5.1 KiB
JavaScript
// TODO: access `BlockType` and `ArgumentType` without reaching into VM
|
|
// Should we move these into a new extension support module or something?
|
|
import ArgumentType from 'scratch-vm/src/extension-support/argument-type';
|
|
import BlockType from 'scratch-vm/src/extension-support/block-type';
|
|
|
|
/**
|
|
* Define a block using extension info which has the ability to dynamically determine (and update) its layout.
|
|
* This functionality is used for extension blocks which can change its properties based on different state
|
|
* information. For example, the `control_stop` block changes its shape based on which menu item is selected
|
|
* and a variable block changes its text to reflect the variable name without using an editable field.
|
|
* @param {object} ScratchBlocks - The ScratchBlocks name space.
|
|
* @param {object} categoryInfo - Information about this block's extension category, including any menus and icons.
|
|
* @param {object} staticBlockInfo - The base block information before any dynamic changes.
|
|
* @param {string} extendedOpcode - The opcode for the block (including the extension ID).
|
|
*/
|
|
// TODO: grow this until it can fully replace `_convertForScratchBlocks` in the VM runtime
|
|
const defineDynamicBlock = (ScratchBlocks, categoryInfo, staticBlockInfo, extendedOpcode) => ({
|
|
init: function () {
|
|
const blockJson = {
|
|
type: extendedOpcode,
|
|
inputsInline: true,
|
|
category: categoryInfo.name,
|
|
colour: categoryInfo.color1,
|
|
colourSecondary: categoryInfo.color2,
|
|
colourTertiary: categoryInfo.color3
|
|
};
|
|
// There is a scratch-blocks / Blockly extension called "scratch_extension" which adjusts the styling of
|
|
// blocks to allow for an icon, a feature of Scratch extension blocks. However, Scratch "core" extension
|
|
// blocks don't have icons and so they should not use 'scratch_extension'. Adding a scratch-blocks / Blockly
|
|
// extension after `jsonInit` isn't fully supported (?), so we decide now whether there will be an icon.
|
|
if (staticBlockInfo.blockIconURI || categoryInfo.blockIconURI) {
|
|
blockJson.extensions = ['scratch_extension'];
|
|
}
|
|
// initialize the basics of the block, to be overridden & extended later by `domToMutation`
|
|
this.jsonInit(blockJson);
|
|
// initialize the cached block info used to carry block info from `domToMutation` to `mutationToDom`
|
|
this.blockInfoText = '{}';
|
|
// we need a block info update (through `domToMutation`) before we have a completely initialized block
|
|
this.needsBlockInfoUpdate = true;
|
|
},
|
|
mutationToDom: function () {
|
|
const container = document.createElement('mutation');
|
|
container.setAttribute('blockInfo', this.blockInfoText);
|
|
return container;
|
|
},
|
|
domToMutation: function (xmlElement) {
|
|
const blockInfoText = xmlElement.getAttribute('blockInfo');
|
|
if (!blockInfoText) return;
|
|
if (!this.needsBlockInfoUpdate) {
|
|
throw new Error('Attempted to update block info twice');
|
|
}
|
|
delete this.needsBlockInfoUpdate;
|
|
this.blockInfoText = blockInfoText;
|
|
const blockInfo = JSON.parse(blockInfoText);
|
|
|
|
switch (blockInfo.blockType) {
|
|
case BlockType.COMMAND:
|
|
case BlockType.CONDITIONAL:
|
|
case BlockType.LOOP:
|
|
this.setOutputShape(ScratchBlocks.OUTPUT_SHAPE_SQUARE);
|
|
this.setPreviousStatement(true);
|
|
this.setNextStatement(!blockInfo.isTerminal);
|
|
break;
|
|
case BlockType.REPORTER:
|
|
this.setOutput(true);
|
|
this.setOutputShape(ScratchBlocks.OUTPUT_SHAPE_ROUND);
|
|
if (!blockInfo.disableMonitor) {
|
|
this.setCheckboxInFlyout(true);
|
|
}
|
|
break;
|
|
case BlockType.BOOLEAN:
|
|
this.setOutput(true);
|
|
this.setOutputShape(ScratchBlocks.OUTPUT_SHAPE_HEXAGONAL);
|
|
break;
|
|
case BlockType.HAT:
|
|
case BlockType.EVENT:
|
|
this.setOutputShape(ScratchBlocks.OUTPUT_SHAPE_SQUARE);
|
|
this.setNextStatement(true);
|
|
break;
|
|
}
|
|
|
|
if (blockInfo.color1 || blockInfo.color2 || blockInfo.color3) {
|
|
// `setColour` handles undefined parameters by adjusting defined colors
|
|
this.setColour(blockInfo.color1, blockInfo.color2, blockInfo.color3);
|
|
}
|
|
|
|
// Layout block arguments
|
|
// TODO handle E/C Blocks
|
|
const blockText = blockInfo.text;
|
|
const args = [];
|
|
let argCount = 0;
|
|
const scratchBlocksStyleText = blockText.replace(/\[(.+?)]/g, (match, argName) => {
|
|
const arg = blockInfo.arguments[argName];
|
|
switch (arg.type) {
|
|
case ArgumentType.STRING:
|
|
args.push({type: 'input_value', name: argName});
|
|
break;
|
|
case ArgumentType.BOOLEAN:
|
|
args.push({type: 'input_value', name: argName, check: 'Boolean'});
|
|
break;
|
|
}
|
|
return `%${++argCount}`;
|
|
});
|
|
this.interpolate_(scratchBlocksStyleText, args);
|
|
}
|
|
});
|
|
|
|
export default defineDynamicBlock;
|