From 8d3a85860e2e878c528cf7165f48333b72d6219d Mon Sep 17 00:00:00 2001
From: picklesrus <picklesrus@users.noreply.github.com>
Date: Thu, 20 Jun 2019 11:21:56 -0400
Subject: [PATCH] Revert "Merge pull request #4790 from LLK/e16n"

This reverts commit c061f2e2f0900f099024617535a50720b719d3a9, reversing
changes made to 6e007b1e4a56297b47ff60ff44e2edc4a4db4288.
---
 package.json                                |   2 +-
 src/containers/blocks.jsx                   |  45 +----
 src/lib/define-dynamic-block.js             | 108 -----------
 src/lib/make-toolbox-xml.js                 |  49 ++---
 test/unit/util/define-dynamic-block.test.js | 199 --------------------
 5 files changed, 21 insertions(+), 382 deletions(-)
 delete mode 100644 src/lib/define-dynamic-block.js
 delete mode 100644 test/unit/util/define-dynamic-block.test.js

diff --git a/package.json b/package.json
index b0564c305..905e371b9 100644
--- a/package.json
+++ b/package.json
@@ -111,7 +111,7 @@
     "scratch-render": "0.1.0-prerelease.20190605151415",
     "scratch-storage": "1.3.2",
     "scratch-svg-renderer": "0.2.0-prerelease.20190523193400",
-    "scratch-vm": "0.2.0-prerelease.20190619042313",
+    "scratch-vm": "0.2.0-prerelease.20190610152034",
     "selenium-webdriver": "3.6.0",
     "startaudiocontext": "1.2.1",
     "style-loader": "^0.23.0",
diff --git a/src/containers/blocks.jsx b/src/containers/blocks.jsx
index fb01ce15d..d79c7e9c7 100644
--- a/src/containers/blocks.jsx
+++ b/src/containers/blocks.jsx
@@ -17,7 +17,6 @@ import errorBoundaryHOC from '../lib/error-boundary-hoc.jsx';
 import {STAGE_DISPLAY_SIZES} from '../lib/layout-constants';
 import DropAreaHOC from '../lib/drop-area-hoc.jsx';
 import DragConstants from '../lib/drag-constants';
-import defineDynamicBlock from '../lib/define-dynamic-block';
 
 import {connect} from 'react-redux';
 import {updateToolbox} from '../reducers/toolbox';
@@ -394,50 +393,20 @@ class Blocks extends React.Component {
         // workspace to be 'undone' here.
         this.workspace.clearUndo();
     }
-    handleExtensionAdded (categoryInfo) {
-        const defineBlocks = blockInfoArray => {
-            if (blockInfoArray && blockInfoArray.length > 0) {
-                const staticBlocksJson = [];
-                const dynamicBlocksInfo = [];
-                blockInfoArray.forEach(blockInfo => {
-                    if (blockInfo.info && blockInfo.info.isDynamic) {
-                        dynamicBlocksInfo.push(blockInfo);
-                    } else if (blockInfo.json) {
-                        staticBlocksJson.push(blockInfo.json);
-                    }
-                    // otherwise it's a non-block entry such as '---'
-                });
-
-                this.ScratchBlocks.defineBlocksWithJsonArray(staticBlocksJson);
-                dynamicBlocksInfo.forEach(blockInfo => {
-                    // This is creating the block factory / constructor -- NOT a specific instance of the block.
-                    // The factory should only know static info about the block: the category info and the opcode.
-                    // Anything else will be picked up from the XML attached to the block instance.
-                    const extendedOpcode = `${categoryInfo.id}_${blockInfo.info.opcode}`;
-                    const blockDefinition =
-                        defineDynamicBlock(this.ScratchBlocks, categoryInfo, blockInfo, extendedOpcode);
-                    this.ScratchBlocks.Blocks[extendedOpcode] = blockDefinition;
-                });
-            }
-        };
-
-        // scratch-blocks implements a menu or custom field as a special kind of block ("shadow" block)
-        // these actually define blocks and MUST run regardless of the UI state
-        defineBlocks(
-            Object.getOwnPropertyNames(categoryInfo.customFieldTypes)
-                .map(fieldTypeName => categoryInfo.customFieldTypes[fieldTypeName].scratchBlocksDefinition));
-        defineBlocks(categoryInfo.menus);
-        defineBlocks(categoryInfo.blocks);
+    handleExtensionAdded (blocksInfo) {
+        // select JSON from each block info object then reject the pseudo-blocks which don't have JSON, like separators
+        // this actually defines blocks and MUST run regardless of the UI state
+        this.ScratchBlocks.defineBlocksWithJsonArray(blocksInfo.map(blockInfo => blockInfo.json).filter(x => x));
 
-        // Update the toolbox with new blocks if possible
+        // Update the toolbox with new blocks
         const toolboxXML = this.getToolboxXML();
         if (toolboxXML) {
             this.props.updateToolboxState(toolboxXML);
         }
     }
-    handleBlocksInfoUpdate (categoryInfo) {
+    handleBlocksInfoUpdate (blocksInfo) {
         // @todo Later we should replace this to avoid all the warnings from redefining blocks.
-        this.handleExtensionAdded(categoryInfo);
+        this.handleExtensionAdded(blocksInfo);
     }
     handleCategorySelected (categoryId) {
         const extension = extensionData.find(ext => ext.extensionId === categoryId);
diff --git a/src/lib/define-dynamic-block.js b/src/lib/define-dynamic-block.js
deleted file mode 100644
index 33f1c8732..000000000
--- a/src/lib/define-dynamic-block.js
+++ /dev/null
@@ -1,108 +0,0 @@
-// 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;
diff --git a/src/lib/make-toolbox-xml.js b/src/lib/make-toolbox-xml.js
index 79cf35cc2..3443e05d2 100644
--- a/src/lib/make-toolbox-xml.js
+++ b/src/lib/make-toolbox-xml.js
@@ -716,16 +716,13 @@ const xmlClose = '</xml>';
 /**
  * @param {!boolean} isStage - Whether the toolbox is for a stage-type target.
  * @param {?string} targetId - The current editing target
- * @param {?Array.<object>} categoriesXML - optional array of `{id,xml}` for categories. This can include both core
- * and other extensions: core extensions will be placed in the normal Scratch order; others will go at the bottom.
- * @property {string} id - the extension / category ID.
- * @property {string} xml - the `<category>...</category>` XML for this extension / category.
+ * @param {?string} categoriesXML - null for default toolbox, or an XML string with <category> elements.
  * @param {?string} costumeName - The name of the default selected costume dropdown.
  * @param {?string} backdropName - The name of the default selected backdrop dropdown.
  * @param {?string} soundName -  The name of the default selected sound dropdown.
  * @returns {string} - a ScratchBlocks-style XML document for the contents of the toolbox.
  */
-const makeToolboxXML = function (isStage, targetId, categoriesXML = [],
+const makeToolboxXML = function (isStage, targetId, categoriesXML,
     costumeName = '', backdropName = '', soundName = '') {
     const gap = [categorySeparator];
 
@@ -733,41 +730,21 @@ const makeToolboxXML = function (isStage, targetId, categoriesXML = [],
     backdropName = xmlEscape(backdropName);
     soundName = xmlEscape(soundName);
 
-    categoriesXML = categoriesXML.slice();
-    const moveCategory = categoryId => {
-        const index = categoriesXML.findIndex(categoryInfo => categoryInfo.id === categoryId);
-        if (index >= 0) {
-            // remove the category from categoriesXML and return its XML
-            const [categoryInfo] = categoriesXML.splice(index, 1);
-            return categoryInfo.xml;
-        }
-        // return `undefined`
-    };
-    const motionXML = moveCategory('motion') || motion(isStage, targetId);
-    const looksXML = moveCategory('looks') || looks(isStage, targetId, costumeName, backdropName);
-    const soundXML = moveCategory('sound') || sound(isStage, targetId, soundName);
-    const eventsXML = moveCategory('event') || events(isStage, targetId);
-    const controlXML = moveCategory('control') || control(isStage, targetId);
-    const sensingXML = moveCategory('sensing') || sensing(isStage, targetId);
-    const operatorsXML = moveCategory('operators') || operators(isStage, targetId);
-    const variablesXML = moveCategory('data') || variables(isStage, targetId);
-    const myBlocksXML = moveCategory('procedures') || myBlocks(isStage, targetId);
-
     const everything = [
         xmlOpen,
-        motionXML, gap,
-        looksXML, gap,
-        soundXML, gap,
-        eventsXML, gap,
-        controlXML, gap,
-        sensingXML, gap,
-        operatorsXML, gap,
-        variablesXML, gap,
-        myBlocksXML
+        motion(isStage, targetId), gap,
+        looks(isStage, targetId, costumeName, backdropName), gap,
+        sound(isStage, targetId, soundName), gap,
+        events(isStage, targetId), gap,
+        control(isStage, targetId), gap,
+        sensing(isStage, targetId), gap,
+        operators(isStage, targetId), gap,
+        variables(isStage, targetId), gap,
+        myBlocks(isStage, targetId)
     ];
 
-    for (const extensionCategory of categoriesXML) {
-        everything.push(gap, extensionCategory.xml);
+    if (categoriesXML) {
+        everything.push(gap, categoriesXML);
     }
 
     everything.push(xmlClose);
diff --git a/test/unit/util/define-dynamic-block.test.js b/test/unit/util/define-dynamic-block.test.js
deleted file mode 100644
index 257e5529b..000000000
--- a/test/unit/util/define-dynamic-block.test.js
+++ /dev/null
@@ -1,199 +0,0 @@
-import defineDynamicBlock from '../../../src/lib/define-dynamic-block';
-
-import BlockType from 'scratch-vm/src/extension-support/block-type';
-
-const MockScratchBlocks = {
-    OUTPUT_SHAPE_HEXAGONAL: 1,
-    OUTPUT_SHAPE_ROUND: 2,
-    OUTPUT_SHAPE_SQUARE: 3
-};
-
-const categoryInfo = {
-    name: 'test category',
-    color1: '#111',
-    color2: '#222',
-    color3: '#333'
-};
-
-const penIconURI = 'data:image/svg+xml;base64,fake_pen_icon_svg_base64_data';
-
-const testBlockInfo = {
-    commandWithIcon: {
-        blockType: BlockType.COMMAND,
-        blockIconURI: penIconURI,
-        text: 'command with icon'
-    },
-    commandWithoutIcon: {
-        blockType: BlockType.COMMAND,
-        text: 'command without icon'
-    },
-    terminalCommand: {
-        blockType: BlockType.COMMAND,
-        isTerminal: true,
-        text: 'terminal command'
-    },
-    reporter: {
-        blockType: BlockType.REPORTER,
-        text: 'reporter'
-    },
-    boolean: {
-        blockType: BlockType.BOOLEAN,
-        text: 'Boolean'
-    },
-    hat: {
-        blockType: BlockType.HAT,
-        text: 'hat'
-    }
-};
-
-// similar to goog.mixin from the Closure library
-const mixin = function (target, source) {
-    for (const x in source) {
-        target[x] = source[x];
-    }
-};
-
-class MockBlock {
-    constructor (blockInfo, extendedOpcode) {
-        // mimic Closure-style inheritance by mixing in `defineDynamicBlock` output as this instance's prototype
-        // see also the `Blockly.Block` constructor
-        const prototype = defineDynamicBlock(MockScratchBlocks, categoryInfo, blockInfo, extendedOpcode);
-        mixin(this, prototype);
-        this.init();
-
-        // bootstrap the mutation<->DOM cycle
-        this.blockInfoText = JSON.stringify(blockInfo);
-        const xmlElement = this.mutationToDom();
-
-        // parse blockInfo from XML to fill dynamic properties
-        this.domToMutation(xmlElement);
-    }
-
-    jsonInit (json) {
-        this.result = Object.assign({}, json);
-    }
-    interpolate_ () {
-        // TODO: add tests for this?
-    }
-    setCheckboxInFlyout (isEnabled) {
-        this.result.checkboxInFlyout_ = isEnabled;
-    }
-    setOutput (isEnabled) {
-        this.result.outputConnection = isEnabled; // Blockly calls `makeConnection_` here
-    }
-    setOutputShape (outputShape) {
-        this.result.outputShape_ = outputShape;
-    }
-    setNextStatement (isEnabled) {
-        this.result.nextConnection = isEnabled; // Blockly calls `makeConnection_` here
-    }
-    setPreviousStatement (isEnabled) {
-        this.result.previousConnection = isEnabled; // Blockly calls `makeConnection_` here
-    }
-}
-
-describe('defineDynamicBlock', () => {
-    test('is a function', () => {
-        expect(typeof defineDynamicBlock).toBe('function');
-    });
-    test('can define a command block with an icon', () => {
-        const extendedOpcode = 'test.commandWithIcon';
-        const block = new MockBlock(testBlockInfo.commandWithIcon, extendedOpcode);
-        expect(block.result).toEqual({
-            category: categoryInfo.name,
-            colour: categoryInfo.color1,
-            colourSecondary: categoryInfo.color2,
-            colourTertiary: categoryInfo.color3,
-            extensions: ['scratch_extension'],
-            inputsInline: true,
-            nextConnection: true,
-            outputShape_: MockScratchBlocks.OUTPUT_SHAPE_SQUARE,
-            previousConnection: true,
-            type: extendedOpcode
-        });
-    });
-    test('can define a command block without an icon', () => {
-        const extendedOpcode = 'test.commandWithoutIcon';
-        const block = new MockBlock(testBlockInfo.commandWithoutIcon, extendedOpcode);
-        expect(block.result).toEqual({
-            category: categoryInfo.name,
-            colour: categoryInfo.color1,
-            colourSecondary: categoryInfo.color2,
-            colourTertiary: categoryInfo.color3,
-            // extensions: undefined, // no icon means no extension
-            inputsInline: true,
-            nextConnection: true,
-            outputShape_: MockScratchBlocks.OUTPUT_SHAPE_SQUARE,
-            previousConnection: true,
-            type: extendedOpcode
-        });
-    });
-    test('can define a terminal command', () => {
-        const extendedOpcode = 'test.terminal';
-        const block = new MockBlock(testBlockInfo.terminalCommand, extendedOpcode);
-        expect(block.result).toEqual({
-            category: categoryInfo.name,
-            colour: categoryInfo.color1,
-            colourSecondary: categoryInfo.color2,
-            colourTertiary: categoryInfo.color3,
-            // extensions: undefined, // no icon means no extension
-            inputsInline: true,
-            nextConnection: false, // terminal
-            outputShape_: MockScratchBlocks.OUTPUT_SHAPE_SQUARE,
-            previousConnection: true,
-            type: extendedOpcode
-        });
-    });
-    test('can define a reporter', () => {
-        const extendedOpcode = 'test.reporter';
-        const block = new MockBlock(testBlockInfo.reporter, extendedOpcode);
-        expect(block.result).toEqual({
-            category: categoryInfo.name,
-            checkboxInFlyout_: true,
-            colour: categoryInfo.color1,
-            colourSecondary: categoryInfo.color2,
-            colourTertiary: categoryInfo.color3,
-            // extensions: undefined, // no icon means no extension
-            inputsInline: true,
-            // nextConnection: undefined, // reporter
-            outputConnection: true, // reporter
-            outputShape_: MockScratchBlocks.OUTPUT_SHAPE_ROUND, // reporter
-            // previousConnection: undefined, // reporter
-            type: extendedOpcode
-        });
-    });
-    test('can define a Boolean', () => {
-        const extendedOpcode = 'test.boolean';
-        const block = new MockBlock(testBlockInfo.boolean, extendedOpcode);
-        expect(block.result).toEqual({
-            category: categoryInfo.name,
-            // checkboxInFlyout_: undefined,
-            colour: categoryInfo.color1,
-            colourSecondary: categoryInfo.color2,
-            colourTertiary: categoryInfo.color3,
-            // extensions: undefined, // no icon means no extension
-            inputsInline: true,
-            // nextConnection: undefined, // reporter
-            outputConnection: true, // reporter
-            outputShape_: MockScratchBlocks.OUTPUT_SHAPE_HEXAGONAL, // Boolean
-            // previousConnection: undefined, // reporter
-            type: extendedOpcode
-        });
-    });
-    test('can define a hat', () => {
-        const extendedOpcode = 'test.hat';
-        const block = new MockBlock(testBlockInfo.hat, extendedOpcode);
-        expect(block.result).toEqual({
-            category: categoryInfo.name,
-            colour: categoryInfo.color1,
-            colourSecondary: categoryInfo.color2,
-            colourTertiary: categoryInfo.color3,
-            // extensions: undefined, // no icon means no extension
-            inputsInline: true,
-            nextConnection: true,
-            outputShape_: MockScratchBlocks.OUTPUT_SHAPE_SQUARE,
-            // previousConnection: undefined, // hat
-            type: extendedOpcode
-        });
-    });
-});
-- 
GitLab