Spaces:
Runtime error
Runtime error
| // 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; | |