From e0c628df342529312ef6251a0e7da28788616b4d Mon Sep 17 00:00:00 2001
From: Christopher Willis-Ford <cwillisf@media.mit.edu>
Date: Tue, 29 Aug 2017 11:41:21 -0700
Subject: [PATCH] WIP for demo

---
 .../sprite-selector/sprite-selector.css       |  33 +-
 .../sprite-selector/sprite-selector.jsx       |  32 +-
 src/components/target-pane/target-pane.jsx    |   3 +
 src/containers/blocks.jsx                     |  49 +-
 src/containers/target-pane.jsx                |   6 +
 src/lib/make-toolbox-xml.js                   | 639 ++++++++++++++++++
 src/lib/static-blocks.xml                     | 578 ++++++++++++++++
 src/reducers/gui.js                           |   2 +
 src/reducers/toolbox.js                       |  31 +
 webpack.config.js                             |   8 +
 10 files changed, 1358 insertions(+), 23 deletions(-)
 create mode 100644 src/lib/make-toolbox-xml.js
 create mode 100644 src/lib/static-blocks.xml
 create mode 100644 src/reducers/toolbox.js

diff --git a/src/components/sprite-selector/sprite-selector.css b/src/components/sprite-selector/sprite-selector.css
index 8c1e2315a..c35c0194b 100644
--- a/src/components/sprite-selector/sprite-selector.css
+++ b/src/components/sprite-selector/sprite-selector.css
@@ -2,7 +2,7 @@
 
 .sprite-selector {
     flex-grow: 1;
-    position: relative; 
+    position: relative;
     font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
     margin-right: calc($space / 2);
     background-color: #f9f9f9;
@@ -16,14 +16,14 @@
 
 /* In prep for renaming sprite-selector-item to sprite */
 .sprite {
-    /* 
+    /*
         Our goal is to fit sprites evenly in a row without leftover space.
-        Flexbox's `space between` property gets us close, but doesn't flow 
+        Flexbox's `space between` property gets us close, but doesn't flow
         well when the # of items per row > 1 and less than the max per row.
 
-        Solving by explicitly calc'ing the width of each sprite. Setting 
-        `border-box` simplifies things, because content, padding and 
-        border-width all are included in the width, leaving us only to subtract 
+        Solving by explicitly calc'ing the width of each sprite. Setting
+        `border-box` simplifies things, because content, padding and
+        border-width all are included in the width, leaving us only to subtract
         the left + right margins.
 
         @todo: make room for the scrollbar
@@ -32,7 +32,7 @@
     width: calc((100% / $sprites-per-row ) - $space);
     min-width: 4rem;
     min-height: 4rem; /* @todo: calc height same as width */
-    margin: calc($space / 2); 
+    margin: calc($space / 2);
 }
 
 
@@ -41,11 +41,11 @@
        Sets the sprite-selector items as a scrollable pane
 
         @todo: Safari: pane doesn't stretch to fill height;
-        @todo: Adding `position: relative` still doesn't fix Safari scrolling pane, and 
-              also introduces a new bug in Chrome when vertically resizing window down, 
-              then back up, introduces white space in the outside the page container. 
+        @todo: Adding `position: relative` still doesn't fix Safari scrolling pane, and
+              also introduces a new bug in Chrome when vertically resizing window down,
+              then back up, introduces white space in the outside the page container.
     */
-    height: calc(100% - $sprite-info-height); 
+    height: calc(100% - $sprite-info-height);
     overflow-y: scroll;
 }
 
@@ -57,13 +57,16 @@
     padding-top: calc($space / 2);
     padding-left: calc($space / 2);
     padding-right: calc($space / 2);
-    padding-bottom: $space; 
+    padding-bottom: $space;
 }
 
-.add-button {
-    font-size: 0.55rem;
-    font-weight: bold;
+.add-buttons {
     position: absolute;
     bottom: 0.75rem;
     right: 1rem;
 }
+
+.add-button {
+    font-size: 0.55rem;
+    font-weight: bold;
+}
diff --git a/src/components/sprite-selector/sprite-selector.jsx b/src/components/sprite-selector/sprite-selector.jsx
index 399d8062b..e53f52bb1 100644
--- a/src/components/sprite-selector/sprite-selector.jsx
+++ b/src/components/sprite-selector/sprite-selector.jsx
@@ -10,6 +10,14 @@ import IconButton from '../icon-button/icon-button.jsx';
 import styles from './sprite-selector.css';
 import spriteIcon from './icon--sprite.svg';
 
+const addExtensionMessage = (
+    <FormattedMessage
+        defaultMessage="Add Extension"
+        description="Button to add an extension in the target pane"
+        id="targetPane.addExtension"
+    />
+);
+
 const addSpriteMessage = (
     <FormattedMessage
         defaultMessage="Add Sprite"
@@ -27,6 +35,7 @@ const SpriteSelectorComponent = function (props) {
         onChangeSpriteX,
         onChangeSpriteY,
         onDeleteSprite,
+        onNewExtensionClick,
         onNewSpriteClick,
         onSelectSprite,
         selectedId,
@@ -82,12 +91,22 @@ const SpriteSelectorComponent = function (props) {
                     }
                 </Box>
             </Box>
-            <IconButton
-                className={styles.addButton}
-                img={spriteIcon}
-                title={addSpriteMessage}
-                onClick={onNewSpriteClick}
-            />
+            <Box
+                className={styles.addButtons}
+            >
+                <IconButton
+                    className={styles.addButton}
+                    img={spriteIcon}
+                    title={addExtensionMessage}
+                    onClick={onNewExtensionClick}
+                />
+                <IconButton
+                    className={styles.addButton}
+                    img={spriteIcon}
+                    title={addSpriteMessage}
+                    onClick={onNewSpriteClick}
+                />
+            </Box>
         </Box>
     );
 };
@@ -100,6 +119,7 @@ SpriteSelectorComponent.propTypes = {
     onChangeSpriteX: PropTypes.func,
     onChangeSpriteY: PropTypes.func,
     onDeleteSprite: PropTypes.func,
+    onNewExtensionClick: PropTypes.func,
     onNewSpriteClick: PropTypes.func,
     onSelectSprite: PropTypes.func,
     selectedId: PropTypes.string,
diff --git a/src/components/target-pane/target-pane.jsx b/src/components/target-pane/target-pane.jsx
index a88a994a0..067569a8b 100644
--- a/src/components/target-pane/target-pane.jsx
+++ b/src/components/target-pane/target-pane.jsx
@@ -32,6 +32,7 @@ const TargetPane = ({
     onChangeSpriteX,
     onChangeSpriteY,
     onDeleteSprite,
+    onNewExtensionClick,
     onNewSpriteClick,
     onRequestCloseBackdropLibrary,
     onRequestCloseCostumeLibrary,
@@ -58,6 +59,7 @@ const TargetPane = ({
             onChangeSpriteX={onChangeSpriteX}
             onChangeSpriteY={onChangeSpriteY}
             onDeleteSprite={onDeleteSprite}
+            onNewExtensionClick={onNewExtensionClick}
             onNewSpriteClick={onNewSpriteClick}
             onSelectSprite={onSelectSprite}
         />
@@ -127,6 +129,7 @@ TargetPane.propTypes = {
     onChangeSpriteX: PropTypes.func,
     onChangeSpriteY: PropTypes.func,
     onDeleteSprite: PropTypes.func,
+    onNewExtensionClick: PropTypes.func,
     onNewSpriteClick: PropTypes.func,
     onRequestCloseBackdropLibrary: PropTypes.func,
     onRequestCloseCostumeLibrary: PropTypes.func,
diff --git a/src/containers/blocks.jsx b/src/containers/blocks.jsx
index 63a762765..d3cd98342 100644
--- a/src/containers/blocks.jsx
+++ b/src/containers/blocks.jsx
@@ -1,6 +1,7 @@
 import bindAll from 'lodash.bindall';
 import debounce from 'lodash.debounce';
 import defaultsDeep from 'lodash.defaultsdeep';
+import makeToolboxXML from '../lib/make-toolbox-xml';
 import PropTypes from 'prop-types';
 import React from 'react';
 import VMScratchBlocks from '../lib/blocks';
@@ -8,6 +9,9 @@ import VM from 'scratch-vm';
 import Prompt from './prompt.jsx';
 import BlocksComponent from '../components/blocks/blocks.jsx';
 
+import {connect} from 'react-redux';
+import {updateToolbox} from '../reducers/toolbox';
+
 const addFunctionListener = (object, property, callback) => {
     const oldFn = object[property];
     object[property] = function () {
@@ -31,6 +35,7 @@ class Blocks extends React.Component {
             'onScriptGlowOff',
             'onBlockGlowOn',
             'onBlockGlowOff',
+            'onExtensionWasAdded',
             'onTargetsUpdate',
             'onVisualReport',
             'onWorkspaceUpdate',
@@ -55,9 +60,18 @@ class Blocks extends React.Component {
         this.attachVM();
     }
     shouldComponentUpdate (nextProps, nextState) {
-        return this.state.prompt !== nextState.prompt || this.props.isVisible !== nextProps.isVisible;
+        return (
+            this.state.prompt !== nextState.prompt ||
+            this.props.isVisible !== nextProps.isVisible ||
+            this.props.toolboxXML !== nextProps.toolboxXML
+        );
     }
     componentDidUpdate (prevProps) {
+        if (prevProps.toolboxXML !== this.props.toolboxXML) {
+            const selectedCategoryName = this.workspace.toolbox_.getSelectedItem().name_;
+            this.workspace.updateToolbox(this.props.toolboxXML);
+            this.setToolboxSelectedItemByName(selectedCategoryName);
+        }
         if (this.props.isVisible === prevProps.isVisible) {
             return;
         }
@@ -77,6 +91,14 @@ class Blocks extends React.Component {
         this.detachVM();
         this.workspace.dispose();
     }
+    setToolboxSelectedItemByName (name) {
+        const categories = this.workspace.toolbox_.categoryMenu_.categories_;
+        for (let i = 0; i < categories.length; i++) {
+            if (categories[i].name_ === name) {
+                this.workspace.toolbox_.setSelectedItem(categories[i]);
+            }
+        }
+    }
     attachVM () {
         this.workspace.addChangeListener(this.props.vm.blockListener);
         this.flyoutWorkspace = this.workspace
@@ -91,6 +113,7 @@ class Blocks extends React.Component {
         this.props.vm.addListener('VISUAL_REPORT', this.onVisualReport);
         this.props.vm.addListener('workspaceUpdate', this.onWorkspaceUpdate);
         this.props.vm.addListener('targetsUpdate', this.onTargetsUpdate);
+        this.props.vm.addListener('EXTENSION_WAS_ADDED', this.onExtensionWasAdded);
     }
     detachVM () {
         this.props.vm.removeListener('SCRIPT_GLOW_ON', this.onScriptGlowOn);
@@ -100,6 +123,7 @@ class Blocks extends React.Component {
         this.props.vm.removeListener('VISUAL_REPORT', this.onVisualReport);
         this.props.vm.removeListener('workspaceUpdate', this.onWorkspaceUpdate);
         this.props.vm.removeListener('targetsUpdate', this.onTargetsUpdate);
+        this.props.vm.removeListener('EXTENSION_WAS_ADDED', this.onExtensionWasAdded);
     }
     updateToolboxBlockValue (id, value) {
         const block = this.workspace
@@ -167,6 +191,12 @@ class Blocks extends React.Component {
             this.workspace.resize();
         }
     }
+    onExtensionWasAdded (blocksInfo) {
+        this.ScratchBlocks.defineBlocksWithJsonArray(blocksInfo.map(blockInfo => blockInfo.json));
+        const dynamicBlocksXML = this.props.vm.runtime.getBlocksXML();
+        const toolboxXML = makeToolboxXML(dynamicBlocksXML);
+        this.props.updateToolbox(toolboxXML);
+    }
     setBlocks (blocks) {
         this.blocks = blocks;
     }
@@ -230,6 +260,8 @@ Blocks.propTypes = {
         }),
         comments: PropTypes.bool
     }),
+    toolboxXML: PropTypes.string,
+    updateToolbox: PropTypes.func,
     vm: PropTypes.instanceOf(VM).isRequired
 };
 
@@ -264,4 +296,17 @@ Blocks.defaultProps = {
     options: Blocks.defaultOptions
 };
 
-export default Blocks;
+const mapStateToProps = state => ({
+    toolboxXML: state.toolbox.toolboxXML
+});
+
+const mapDispatchToProps = dispatch => ({
+    updateToolbox: toolboxXML => {
+        dispatch(updateToolbox(toolboxXML));
+    }
+});
+
+export default connect(
+    mapStateToProps,
+    mapDispatchToProps
+)(Blocks);
diff --git a/src/containers/target-pane.jsx b/src/containers/target-pane.jsx
index 9b70a79d4..b4bc8f5b4 100644
--- a/src/containers/target-pane.jsx
+++ b/src/containers/target-pane.jsx
@@ -24,6 +24,7 @@ class TargetPane extends React.Component {
             'handleChangeSpriteX',
             'handleChangeSpriteY',
             'handleDeleteSprite',
+            'handleNewExtensionClick',
             'handleSelectSprite'
         ]);
     }
@@ -48,6 +49,10 @@ class TargetPane extends React.Component {
     handleDeleteSprite (id) {
         this.props.vm.deleteSprite(id);
     }
+    handleNewExtensionClick () {
+        /** @TODO: Replace this with `dispatch(openExtensionLibrary());` in `mapDispatchToProps` below */
+        this.props.vm.extensionManager.loadExtensionURL('extensions/example-extension.js');
+    }
     handleSelectSprite (id) {
         this.props.vm.setEditingTarget(id);
     }
@@ -62,6 +67,7 @@ class TargetPane extends React.Component {
                 onChangeSpriteX={this.handleChangeSpriteX}
                 onChangeSpriteY={this.handleChangeSpriteY}
                 onDeleteSprite={this.handleDeleteSprite}
+                onNewExtensionClick={this.handleNewExtensionClick}
                 onSelectSprite={this.handleSelectSprite}
             />
         );
diff --git a/src/lib/make-toolbox-xml.js b/src/lib/make-toolbox-xml.js
new file mode 100644
index 000000000..27a917ff0
--- /dev/null
+++ b/src/lib/make-toolbox-xml.js
@@ -0,0 +1,639 @@
+const separator = '<sep gap="45"/>';
+
+const motion = [
+    '<category name="Motion" colour="#4C97FF" secondaryColour="#3373CC">',
+    '    <block type="motion_movesteps">',
+    '        <value name="STEPS">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM">10</field>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="motion_turnright">',
+    '        <value name="DEGREES">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM">15</field>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="motion_turnleft">',
+    '        <value name="DEGREES">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM">15</field>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="motion_pointindirection">',
+    '        <value name="DIRECTION">',
+    '            <shadow type="math_angle">',
+    '                <field name="NUM">90</field>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="motion_pointtowards">',
+    '        <value name="TOWARDS">',
+    '            <shadow type="motion_pointtowards_menu">',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="motion_gotoxy">',
+    '        <value name="X">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM">0</field>',
+    '            </shadow>',
+    '        </value>',
+    '        <value name="Y">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM">0</field>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="motion_goto">',
+    '        <value name="TO">',
+    '            <shadow type="motion_goto_menu">',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="motion_glidesecstoxy">',
+    '        <value name="SECS">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM">1</field>',
+    '            </shadow>',
+    '        </value>',
+    '        <value name="X">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM">0</field>',
+    '            </shadow>',
+    '        </value>',
+    '        <value name="Y">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM">0</field>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="motion_changexby">',
+    '        <value name="DX">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM">10</field>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="motion_setx">',
+    '        <value name="X">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM">0</field>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="motion_changeyby">',
+    '        <value name="DY">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM">10</field>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="motion_sety">',
+    '        <value name="Y">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM">0</field>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="motion_ifonedgebounce"/>',
+    '    <block type="motion_setrotationstyle"/>',
+    '    <block type="motion_xposition"/>',
+    '    <block type="motion_yposition"/>',
+    '    <block type="motion_direction"/>',
+    '</category>'
+];
+
+const looks = [
+    '<category name="Looks" colour="#9966FF" secondaryColour="#774DCB">',
+    '    <block type="looks_show"/>',
+    '    <block type="looks_hide"/>',
+    '    <block type="looks_switchcostumeto">',
+    '        <value name="COSTUME">',
+    '            <shadow type="looks_costume"/>',
+    '        </value>',
+    '    </block>',
+    '    <block type="looks_nextcostume"/>',
+    '    <block type="looks_nextbackdrop"/>',
+    '    <block type="looks_switchbackdropto">',
+    '        <value name="BACKDROP">',
+    '            <shadow type="looks_backdrops"/>',
+    '        </value>',
+    '    </block>',
+    '    <block type="looks_switchbackdroptoandwait">',
+    '        <value name="BACKDROP">',
+    '            <shadow type="looks_backdrops"/>',
+    '        </value>',
+    '    </block>',
+    '    <block type="looks_changeeffectby">',
+    '        <value name="CHANGE">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM">10</field>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="looks_seteffectto">',
+    '        <value name="VALUE">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM">10</field>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="looks_cleargraphiceffects"/>',
+    '    <block type="looks_changesizeby">',
+    '        <value name="CHANGE">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM">10</field>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="looks_setsizeto">',
+    '        <value name="SIZE">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM">100</field>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="looks_gotofront"/>',
+    '    <block type="looks_gobacklayers">',
+    '        <value name="NUM">',
+    '            <shadow type="math_integer">',
+    '                <field name="NUM">1</field>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="looks_costumeorder"/>',
+    '    <block type="looks_backdroporder"/>',
+    '    <block type="looks_backdropname"/>',
+    '    <block type="looks_size"/>',
+    '</category>'
+];
+
+const sound = [
+    '<category name="Sound" colour="#D65CD6" secondaryColour="#BD42BD">',
+    '    <block type="sound_play">',
+    '        <value name="SOUND_MENU">',
+    '            <shadow type="sound_sounds_menu"/>',
+    '        </value>',
+    '    </block>',
+    '    <block type="sound_playuntildone">',
+    '        <value name="SOUND_MENU">',
+    '            <shadow type="sound_sounds_menu"/>',
+    '        </value>',
+    '    </block>',
+    '    <block type="sound_stopallsounds"/>',
+    '    <block type="sound_playdrumforbeats">',
+    '        <value name="DRUM">',
+    '            <shadow type="sound_drums_menu"/>',
+    '        </value>',
+    '        <value name="BEATS">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM">0.25</field>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="sound_restforbeats">',
+    '        <value name="BEATS">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM">0.25</field>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="sound_playnoteforbeats">',
+    '        <value name="NOTE">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM">60</field>',
+    '            </shadow>',
+    '        </value>',
+    '        <value name="BEATS">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM">0.5</field>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="sound_setinstrumentto">',
+    '        <value name="INSTRUMENT">',
+    '            <shadow type="sound_instruments_menu"/>',
+    '        </value>',
+    '    </block>',
+    '    <block type="sound_changeeffectby">',
+    '        <value name="VALUE">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM">10</field>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="sound_seteffectto">',
+    '        <value name="VALUE">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM">100</field>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="sound_cleareffects"/>',
+    '    <block type="sound_changevolumeby">',
+    '        <value name="VOLUME">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM">-10</field>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="sound_setvolumeto">',
+    '        <value name="VOLUME">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM">100</field>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="sound_volume"/>',
+    '    <block type="sound_changetempoby">',
+    '        <value name="TEMPO">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM">20</field>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="sound_settempotobpm">',
+    '        <value name="TEMPO">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM">60</field>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="sound_tempo"/>',
+    '</category>'
+];
+
+const pen = [
+    '<category name="Pen" colour="#00B295" secondaryColour="#0B8E69">',
+    '    <block type="pen_clear"/>',
+    '    <block type="pen_stamp"/>',
+    '    <block type="pen_pendown"/>',
+    '    <block type="pen_penup"/>',
+    '    <block type="pen_setpencolortocolor">',
+    '        <value name="COLOR">',
+    '            <shadow type="colour_picker">',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="pen_changepencolorby">',
+    '        <value name="COLOR">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM">10</field>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="pen_setpencolortonum">',
+    '        <value name="COLOR">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM">0</field>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="pen_changepenshadeby">',
+    '        <value name="SHADE">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM">10</field>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="pen_setpenshadeto">',
+    '        <value name="SHADE">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM">50</field>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="pen_changepensizeby">',
+    '        <value name="SIZE">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM">1</field>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="pen_setpensizeto">',
+    '        <value name="SIZE">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM">1</field>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '</category>'
+];
+
+const events = [
+    '<category name="Events" colour="#FFD500" secondaryColour="#CC9900">',
+    '    <block type="event_whenflagclicked"/>',
+    '    <block type="event_whenkeypressed">',
+    '    </block>',
+    '    <block type="event_whenthisspriteclicked"/>',
+    '    <block type="event_whenbackdropswitchesto">',
+    '    </block>',
+    '    <block type="event_whengreaterthan">',
+    '        <value name="VALUE">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM">10</field>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="event_whenbroadcastreceived">',
+    '    </block>',
+    '    <block type="event_broadcast">',
+    '        <value name="BROADCAST_OPTION">',
+    '            <shadow type="event_broadcast_menu"/>',
+    '        </value>',
+    '    </block>',
+    '    <block type="event_broadcastandwait">',
+    '        <value name="BROADCAST_OPTION">',
+    '            <shadow type="event_broadcast_menu"/>',
+    '        </value>',
+    '    </block>',
+    '</category>'
+];
+
+const control = [
+    '<category name="Control" colour="#FFAB19" secondaryColour="#CF8B17">',
+    '    <block type="control_wait">',
+    '        <value name="DURATION">',
+    '            <shadow type="math_positive_number">',
+    '                <field name="NUM">1</field>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="control_repeat">',
+    '        <value name="TIMES">',
+    '            <shadow type="math_whole_number">',
+    '                <field name="NUM">10</field>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="control_forever"/>',
+    '    <block type="control_if"/>',
+    '    <block type="control_if_else"/>',
+    '    <block type="control_wait_until"/>',
+    '    <block type="control_repeat_until"/>',
+    '    <block type="control_stop"/>',
+    '    <block type="control_start_as_clone"/>',
+    '    <block type="control_create_clone_of">',
+    '        <value name="CLONE_OPTION">',
+    '            <shadow type="control_create_clone_of_menu"/>',
+    '        </value>',
+    '    </block>',
+    '    <block type="control_delete_this_clone"/>',
+    '</category>'
+];
+
+const sensing = [
+    '<category name="Sensing" colour="#4CBFE6" secondaryColour="#2E8EB8">',
+    '    <block type="sensing_touchingobject">',
+    '        <value name="TOUCHINGOBJECTMENU">',
+    '            <shadow type="sensing_touchingobjectmenu"/>',
+    '        </value>',
+    '    </block>',
+    '    <block type="sensing_touchingcolor">',
+    '        <value name="COLOR">',
+    '            <shadow type="colour_picker"/>',
+    '        </value>',
+    '    </block>',
+    '    <block type="sensing_coloristouchingcolor">',
+    '        <value name="COLOR">',
+    '            <shadow type="colour_picker"/>',
+    '        </value>',
+    '        <value name="COLOR2">',
+    '            <shadow type="colour_picker"/>',
+    '        </value>',
+    '    </block>',
+    '    <block type="sensing_distanceto">',
+    '        <value name="DISTANCETOMENU">',
+    '            <shadow type="sensing_distancetomenu"/>',
+    '        </value>',
+    '    </block>',
+    '    <block type="sensing_keypressed">',
+    '        <value name="KEY_OPTION">',
+    '            <shadow type="sensing_keyoptions"/>',
+    '        </value>',
+    '    </block>',
+    '    <block type="sensing_mousedown"/>',
+    '    <block type="sensing_mousex"/>',
+    '    <block type="sensing_mousey"/>',
+    '    <block type="sensing_loudness"/>',
+    '    <block type="sensing_timer"/>',
+    '    <block type="sensing_resettimer"/>',
+    '    <block type="sensing_of">',
+    '        <value name="PROPERTY">',
+    '            <shadow type="sensing_of_property_menu"/>',
+    '        </value>',
+    '        <value name="OBJECT">',
+    '            <shadow type="sensing_of_object_menu"/>',
+    '        </value>',
+    '    </block>',
+    '    <block type="sensing_current">',
+    '        <value name="CURRENTMENU">',
+    '            <shadow type="sensing_currentmenu"/>',
+    '        </value>',
+    '    </block>',
+    '    <block type="sensing_dayssince2000"/>',
+    '</category>'
+];
+
+const operators = [
+    '<category name="Operators" colour="#40BF4A" secondaryColour="#389438">',
+    '    <block type="operator_add">',
+    '        <value name="NUM1">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM"/>',
+    '            </shadow>',
+    '        </value>',
+    '        <value name="NUM2">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM"/>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="operator_subtract">',
+    '        <value name="NUM1">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM"/>',
+    '            </shadow>',
+    '        </value>',
+    '        <value name="NUM2">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM"/>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="operator_multiply">',
+    '        <value name="NUM1">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM"/>',
+    '            </shadow>',
+    '        </value>',
+    '        <value name="NUM2">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM"/>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="operator_divide">',
+    '        <value name="NUM1">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM"/>',
+    '            </shadow>',
+    '        </value>',
+    '        <value name="NUM2">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM"/>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="operator_random">',
+    '        <value name="FROM">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM">1</field>',
+    '            </shadow>',
+    '        </value>',
+    '        <value name="TO">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM">10</field>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="operator_lt">',
+    '        <value name="OPERAND1">',
+    '            <shadow type="text">',
+    '                <field name="TEXT"/>',
+    '            </shadow>',
+    '        </value>',
+    '        <value name="OPERAND2">',
+    '            <shadow type="text">',
+    '                <field name="TEXT"/>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="operator_equals">',
+    '        <value name="OPERAND1">',
+    '            <shadow type="text">',
+    '                <field name="TEXT"/>',
+    '            </shadow>',
+    '        </value>',
+    '        <value name="OPERAND2">',
+    '            <shadow type="text">',
+    '                <field name="TEXT"/>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="operator_gt">',
+    '        <value name="OPERAND1">',
+    '            <shadow type="text">',
+    '                <field name="TEXT"/>',
+    '            </shadow>',
+    '        </value>',
+    '        <value name="OPERAND2">',
+    '            <shadow type="text">',
+    '                <field name="TEXT"/>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="operator_and"/>',
+    '    <block type="operator_or"/>',
+    '    <block type="operator_not"/>',
+    '    <block type="operator_join">',
+    '        <value name="STRING1">',
+    '            <shadow type="text">',
+    '                <field name="TEXT">hello</field>',
+    '            </shadow>',
+    '        </value>',
+    '        <value name="STRING2">',
+    '            <shadow type="text">',
+    '                <field name="TEXT">world</field>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="operator_letter_of">',
+    '        <value name="LETTER">',
+    '            <shadow type="math_whole_number">',
+    '                <field name="NUM">1</field>',
+    '            </shadow>',
+    '        </value>',
+    '        <value name="STRING">',
+    '            <shadow type="text">',
+    '                <field name="TEXT">world</field>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="operator_length">',
+    '        <value name="STRING">',
+    '            <shadow type="text">',
+    '                <field name="TEXT">world</field>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="operator_mod">',
+    '        <value name="NUM1">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM"/>',
+    '            </shadow>',
+    '        </value>',
+    '        <value name="NUM2">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM"/>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="operator_round">',
+    '        <value name="NUM">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM"/>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '    <block type="operator_mathop">',
+    '        <value name="NUM">',
+    '            <shadow type="math_number">',
+    '                <field name="NUM"/>',
+    '            </shadow>',
+    '        </value>',
+    '    </block>',
+    '</category>'
+];
+
+const data = [
+    '<category name="Data" colour="#FF8C1A" secondaryColour="#DB6E00" custom="VARIABLE">',
+    '</category>'
+];
+
+const xmlOpen = '<xml style="display: none">';
+const xmlClose = '</xml>';
+
+/**
+ * @param {string?} categoriesXML - null for default toolbox, or an XML string with <category> elements.
+ * @returns {string} - a ScratchBlocks-style XML document for the contents of the toolbox.
+ */
+const makeToolboxXML = function (categoriesXML) {
+    const gap = [separator];
+
+    const everything = [].concat(
+        xmlOpen,
+        motion, gap,
+        looks, gap,
+        sound, gap,
+        pen, gap,
+        data, gap,
+        events, gap,
+        control, gap,
+        sensing, gap,
+        operators);
+
+    if (categoriesXML) {
+        everything.push(gap, categoriesXML);
+    }
+
+    everything.push(xmlClose);
+
+    return everything.join('\n');
+};
+
+export default makeToolboxXML;
diff --git a/src/lib/static-blocks.xml b/src/lib/static-blocks.xml
new file mode 100644
index 000000000..2081e5c8a
--- /dev/null
+++ b/src/lib/static-blocks.xml
@@ -0,0 +1,578 @@
+<category name="Motion" colour="#4C97FF" secondaryColour="#3373CC">
+    <block type="motion_movesteps">
+        <value name="STEPS">
+            <shadow type="math_number">
+                <field name="NUM">10</field>
+            </shadow>
+        </value>
+    </block>
+    <block type="motion_turnright">
+        <value name="DEGREES">
+            <shadow type="math_number">
+                <field name="NUM">15</field>
+            </shadow>
+        </value>
+    </block>
+    <block type="motion_turnleft">
+        <value name="DEGREES">
+            <shadow type="math_number">
+                <field name="NUM">15</field>
+            </shadow>
+        </value>
+    </block>
+    <block type="motion_pointindirection">
+        <value name="DIRECTION">
+            <shadow type="math_angle">
+                <field name="NUM">90</field>
+            </shadow>
+        </value>
+    </block>
+    <block type="motion_pointtowards">
+        <value name="TOWARDS">
+            <shadow type="motion_pointtowards_menu">
+            </shadow>
+        </value>
+    </block>
+    <block type="motion_gotoxy">
+        <value name="X">
+            <shadow type="math_number">
+                <field name="NUM">0</field>
+            </shadow>
+        </value>
+        <value name="Y">
+            <shadow type="math_number">
+                <field name="NUM">0</field>
+            </shadow>
+        </value>
+    </block>
+    <block type="motion_goto">
+        <value name="TO">
+            <shadow type="motion_goto_menu">
+            </shadow>
+        </value>
+    </block>
+    <block type="motion_glidesecstoxy">
+        <value name="SECS">
+            <shadow type="math_number">
+                <field name="NUM">1</field>
+            </shadow>
+        </value>
+        <value name="X">
+            <shadow type="math_number">
+                <field name="NUM">0</field>
+            </shadow>
+        </value>
+        <value name="Y">
+            <shadow type="math_number">
+                <field name="NUM">0</field>
+            </shadow>
+        </value>
+    </block>
+    <block type="motion_changexby">
+        <value name="DX">
+            <shadow type="math_number">
+                <field name="NUM">10</field>
+            </shadow>
+        </value>
+    </block>
+    <block type="motion_setx">
+        <value name="X">
+            <shadow type="math_number">
+                <field name="NUM">0</field>
+            </shadow>
+        </value>
+    </block>
+    <block type="motion_changeyby">
+        <value name="DY">
+            <shadow type="math_number">
+                <field name="NUM">10</field>
+            </shadow>
+        </value>
+    </block>
+    <block type="motion_sety">
+        <value name="Y">
+            <shadow type="math_number">
+                <field name="NUM">0</field>
+            </shadow>
+        </value>
+    </block>
+    <block type="motion_ifonedgebounce"/>
+    <block type="motion_setrotationstyle"/>
+    <block type="motion_xposition"/>
+    <block type="motion_yposition"/>
+    <block type="motion_direction"/>
+</category>
+<category name="Looks" colour="#9966FF" secondaryColour="#774DCB">
+    <block type="looks_show"/>
+    <block type="looks_hide"/>
+    <block type="looks_switchcostumeto">
+        <value name="COSTUME">
+            <shadow type="looks_costume"/>
+        </value>
+    </block>
+    <block type="looks_nextcostume"/>
+    <block type="looks_nextbackdrop"/>
+    <block type="looks_switchbackdropto">
+        <value name="BACKDROP">
+            <shadow type="looks_backdrops"/>
+        </value>
+    </block>
+    <block type="looks_switchbackdroptoandwait">
+        <value name="BACKDROP">
+            <shadow type="looks_backdrops"/>
+        </value>
+    </block>
+    <block type="looks_changeeffectby">
+        <value name="CHANGE">
+            <shadow type="math_number">
+                <field name="NUM">10</field>
+            </shadow>
+        </value>
+    </block>
+    <block type="looks_seteffectto">
+        <value name="VALUE">
+            <shadow type="math_number">
+                <field name="NUM">10</field>
+            </shadow>
+        </value>
+    </block>
+    <block type="looks_cleargraphiceffects"/>
+    <block type="looks_changesizeby">
+        <value name="CHANGE">
+            <shadow type="math_number">
+                <field name="NUM">10</field>
+            </shadow>
+        </value>
+    </block>
+    <block type="looks_setsizeto">
+        <value name="SIZE">
+            <shadow type="math_number">
+                <field name="NUM">100</field>
+            </shadow>
+        </value>
+    </block>
+    <block type="looks_gotofront"/>
+    <block type="looks_gobacklayers">
+        <value name="NUM">
+            <shadow type="math_integer">
+                <field name="NUM">1</field>
+            </shadow>
+        </value>
+    </block>
+    <block type="looks_costumeorder"/>
+    <block type="looks_backdroporder"/>
+    <block type="looks_backdropname"/>
+    <block type="looks_size"/>
+</category>
+<category name="Sound" colour="#D65CD6" secondaryColour="#BD42BD">
+    <block type="sound_play">
+        <value name="SOUND_MENU">
+            <shadow type="sound_sounds_menu"/>
+        </value>
+    </block>
+    <block type="sound_playuntildone">
+        <value name="SOUND_MENU">
+            <shadow type="sound_sounds_menu"/>
+        </value>
+    </block>
+    <block type="sound_stopallsounds"/>
+    <block type="sound_playdrumforbeats">
+        <value name="DRUM">
+            <shadow type="sound_drums_menu"/>
+        </value>
+        <value name="BEATS">
+            <shadow type="math_number">
+                <field name="NUM">0.25</field>
+            </shadow>
+        </value>
+    </block>
+    <block type="sound_restforbeats">
+        <value name="BEATS">
+            <shadow type="math_number">
+                <field name="NUM">0.25</field>
+            </shadow>
+        </value>
+    </block>
+    <block type="sound_playnoteforbeats">
+        <value name="NOTE">
+            <shadow type="math_number">
+                <field name="NUM">60</field>
+            </shadow>
+        </value>
+        <value name="BEATS">
+            <shadow type="math_number">
+                <field name="NUM">0.5</field>
+            </shadow>
+        </value>
+    </block>
+    <block type="sound_setinstrumentto">
+        <value name="INSTRUMENT">
+            <shadow type="sound_instruments_menu"/>
+        </value>
+    </block>
+    <block type="sound_changeeffectby">
+        <value name="VALUE">
+            <shadow type="math_number">
+                <field name="NUM">10</field>
+            </shadow>
+        </value>
+    </block>
+    <block type="sound_seteffectto">
+        <value name="VALUE">
+            <shadow type="math_number">
+                <field name="NUM">100</field>
+            </shadow>
+        </value>
+    </block>
+    <block type="sound_cleareffects"/>
+    <block type="sound_changevolumeby">
+        <value name="VOLUME">
+            <shadow type="math_number">
+                <field name="NUM">-10</field>
+            </shadow>
+        </value>
+    </block>
+    <block type="sound_setvolumeto">
+        <value name="VOLUME">
+            <shadow type="math_number">
+                <field name="NUM">100</field>
+            </shadow>
+        </value>
+    </block>
+    <block type="sound_volume"/>
+    <block type="sound_changetempoby">
+        <value name="TEMPO">
+            <shadow type="math_number">
+                <field name="NUM">20</field>
+            </shadow>
+        </value>
+    </block>
+    <block type="sound_settempotobpm">
+        <value name="TEMPO">
+            <shadow type="math_number">
+                <field name="NUM">60</field>
+            </shadow>
+        </value>
+    </block>
+    <block type="sound_tempo"/>
+</category>
+<category name="Pen" colour="#00B295" secondaryColour="#0B8E69">
+    <block type="pen_clear"/>
+    <block type="pen_stamp"/>
+    <block type="pen_pendown"/>
+    <block type="pen_penup"/>
+    <block type="pen_setpencolortocolor">
+        <value name="COLOR">
+            <shadow type="colour_picker">
+            </shadow>
+        </value>
+    </block>
+    <block type="pen_changepencolorby">
+        <value name="COLOR">
+            <shadow type="math_number">
+                <field name="NUM">10</field>
+            </shadow>
+        </value>
+    </block>
+    <block type="pen_setpencolortonum">
+        <value name="COLOR">
+            <shadow type="math_number">
+                <field name="NUM">0</field>
+            </shadow>
+        </value>
+    </block>
+    <block type="pen_changepenshadeby">
+        <value name="SHADE">
+            <shadow type="math_number">
+                <field name="NUM">10</field>
+            </shadow>
+        </value>
+    </block>
+    <block type="pen_setpenshadeto">
+        <value name="SHADE">
+            <shadow type="math_number">
+                <field name="NUM">50</field>
+            </shadow>
+        </value>
+    </block>
+    <block type="pen_changepensizeby">
+        <value name="SIZE">
+            <shadow type="math_number">
+                <field name="NUM">1</field>
+            </shadow>
+        </value>
+    </block>
+    <block type="pen_setpensizeto">
+        <value name="SIZE">
+            <shadow type="math_number">
+                <field name="NUM">1</field>
+            </shadow>
+        </value>
+    </block>
+</category>
+<category name="Events" colour="#FFD500" secondaryColour="#CC9900">
+    <block type="event_whenflagclicked"/>
+    <block type="event_whenkeypressed">
+    </block>
+    <block type="event_whenthisspriteclicked"/>
+    <block type="event_whenbackdropswitchesto">
+    </block>
+    <block type="event_whengreaterthan">
+        <value name="VALUE">
+            <shadow type="math_number">
+                <field name="NUM">10</field>
+            </shadow>
+        </value>
+    </block>
+    <block type="event_whenbroadcastreceived">
+    </block>
+    <block type="event_broadcast">
+        <value name="BROADCAST_OPTION">
+            <shadow type="event_broadcast_menu"/>
+        </value>
+    </block>
+    <block type="event_broadcastandwait">
+        <value name="BROADCAST_OPTION">
+            <shadow type="event_broadcast_menu"/>
+        </value>
+    </block>
+</category>
+<category name="Control" colour="#FFAB19" secondaryColour="#CF8B17">
+    <block type="control_wait">
+        <value name="DURATION">
+            <shadow type="math_positive_number">
+                <field name="NUM">1</field>
+            </shadow>
+        </value>
+    </block>
+    <block type="control_repeat">
+        <value name="TIMES">
+            <shadow type="math_whole_number">
+                <field name="NUM">10</field>
+            </shadow>
+        </value>
+    </block>
+    <block type="control_forever"/>
+    <block type="control_if"/>
+    <block type="control_if_else"/>
+    <block type="control_wait_until"/>
+    <block type="control_repeat_until"/>
+    <block type="control_stop"/>
+    <block type="control_start_as_clone"/>
+    <block type="control_create_clone_of">
+        <value name="CLONE_OPTION">
+            <shadow type="control_create_clone_of_menu"/>
+        </value>
+    </block>
+    <block type="control_delete_this_clone"/>
+</category>
+<category name="Sensing" colour="#4CBFE6" secondaryColour="#2E8EB8">
+    <block type="sensing_touchingobject">
+        <value name="TOUCHINGOBJECTMENU">
+            <shadow type="sensing_touchingobjectmenu"/>
+        </value>
+    </block>
+    <block type="sensing_touchingcolor">
+        <value name="COLOR">
+            <shadow type="colour_picker"/>
+        </value>
+    </block>
+    <block type="sensing_coloristouchingcolor">
+        <value name="COLOR">
+            <shadow type="colour_picker"/>
+        </value>
+        <value name="COLOR2">
+            <shadow type="colour_picker"/>
+        </value>
+    </block>
+    <block type="sensing_distanceto">
+        <value name="DISTANCETOMENU">
+            <shadow type="sensing_distancetomenu"/>
+        </value>
+    </block>
+    <block type="sensing_keypressed">
+        <value name="KEY_OPTION">
+            <shadow type="sensing_keyoptions"/>
+        </value>
+    </block>
+    <block type="sensing_mousedown"/>
+    <block type="sensing_mousex"/>
+    <block type="sensing_mousey"/>
+    <block type="sensing_loudness"/>
+    <block type="sensing_timer"/>
+    <block type="sensing_resettimer"/>
+    <block type="sensing_of">
+        <value name="PROPERTY">
+            <shadow type="sensing_of_property_menu"/>
+        </value>
+        <value name="OBJECT">
+            <shadow type="sensing_of_object_menu"/>
+        </value>
+    </block>
+    <block type="sensing_current">
+        <value name="CURRENTMENU">
+            <shadow type="sensing_currentmenu"/>
+        </value>
+    </block>
+    <block type="sensing_dayssince2000"/>
+</category>
+<category name="Operators" colour="#40BF4A" secondaryColour="#389438">
+    <block type="operator_add">
+        <value name="NUM1">
+            <shadow type="math_number">
+                <field name="NUM"/>
+            </shadow>
+        </value>
+        <value name="NUM2">
+            <shadow type="math_number">
+                <field name="NUM"/>
+            </shadow>
+        </value>
+    </block>
+    <block type="operator_subtract">
+        <value name="NUM1">
+            <shadow type="math_number">
+                <field name="NUM"/>
+            </shadow>
+        </value>
+        <value name="NUM2">
+            <shadow type="math_number">
+                <field name="NUM"/>
+            </shadow>
+        </value>
+    </block>
+    <block type="operator_multiply">
+        <value name="NUM1">
+            <shadow type="math_number">
+                <field name="NUM"/>
+            </shadow>
+        </value>
+        <value name="NUM2">
+            <shadow type="math_number">
+                <field name="NUM"/>
+            </shadow>
+        </value>
+    </block>
+    <block type="operator_divide">
+        <value name="NUM1">
+            <shadow type="math_number">
+                <field name="NUM"/>
+            </shadow>
+        </value>
+        <value name="NUM2">
+            <shadow type="math_number">
+                <field name="NUM"/>
+            </shadow>
+        </value>
+    </block>
+    <block type="operator_random">
+        <value name="FROM">
+            <shadow type="math_number">
+                <field name="NUM">1</field>
+            </shadow>
+        </value>
+        <value name="TO">
+            <shadow type="math_number">
+                <field name="NUM">10</field>
+            </shadow>
+        </value>
+    </block>
+    <block type="operator_lt">
+        <value name="OPERAND1">
+            <shadow type="text">
+                <field name="TEXT"/>
+            </shadow>
+        </value>
+        <value name="OPERAND2">
+            <shadow type="text">
+                <field name="TEXT"/>
+            </shadow>
+        </value>
+    </block>
+    <block type="operator_equals">
+        <value name="OPERAND1">
+            <shadow type="text">
+                <field name="TEXT"/>
+            </shadow>
+        </value>
+        <value name="OPERAND2">
+            <shadow type="text">
+                <field name="TEXT"/>
+            </shadow>
+        </value>
+    </block>
+    <block type="operator_gt">
+        <value name="OPERAND1">
+            <shadow type="text">
+                <field name="TEXT"/>
+            </shadow>
+        </value>
+        <value name="OPERAND2">
+            <shadow type="text">
+                <field name="TEXT"/>
+            </shadow>
+        </value>
+    </block>
+    <block type="operator_and"/>
+    <block type="operator_or"/>
+    <block type="operator_not"/>
+    <block type="operator_join">
+        <value name="STRING1">
+            <shadow type="text">
+                <field name="TEXT">hello</field>
+            </shadow>
+        </value>
+        <value name="STRING2">
+            <shadow type="text">
+                <field name="TEXT">world</field>
+            </shadow>
+        </value>
+    </block>
+    <block type="operator_letter_of">
+        <value name="LETTER">
+            <shadow type="math_whole_number">
+                <field name="NUM">1</field>
+            </shadow>
+        </value>
+        <value name="STRING">
+            <shadow type="text">
+                <field name="TEXT">world</field>
+            </shadow>
+        </value>
+    </block>
+    <block type="operator_length">
+        <value name="STRING">
+            <shadow type="text">
+                <field name="TEXT">world</field>
+            </shadow>
+        </value>
+    </block>
+    <block type="operator_mod">
+        <value name="NUM1">
+            <shadow type="math_number">
+                <field name="NUM"/>
+            </shadow>
+        </value>
+        <value name="NUM2">
+            <shadow type="math_number">
+                <field name="NUM"/>
+            </shadow>
+        </value>
+    </block>
+    <block type="operator_round">
+        <value name="NUM">
+            <shadow type="math_number">
+                <field name="NUM"/>
+            </shadow>
+        </value>
+    </block>
+    <block type="operator_mathop">
+        <value name="NUM">
+            <shadow type="math_number">
+                <field name="NUM"/>
+            </shadow>
+        </value>
+    </block>
+</category>
+<category name="Data" colour="#FF8C1A" secondaryColour="#DB6E00" custom="VARIABLE">
+</category>
diff --git a/src/reducers/gui.js b/src/reducers/gui.js
index c83dd7fca..dfe05dd84 100644
--- a/src/reducers/gui.js
+++ b/src/reducers/gui.js
@@ -3,6 +3,7 @@ import intlReducer from './intl';
 import modalReducer from './modals';
 import monitorReducer from './monitors';
 import targetReducer from './targets';
+import toolboxReducer from './toolbox';
 import vmReducer from './vm';
 
 
@@ -11,5 +12,6 @@ export default combineReducers({
     modals: modalReducer,
     monitors: monitorReducer,
     targets: targetReducer,
+    toolbox: toolboxReducer,
     vm: vmReducer
 });
diff --git a/src/reducers/toolbox.js b/src/reducers/toolbox.js
new file mode 100644
index 000000000..4b06a807c
--- /dev/null
+++ b/src/reducers/toolbox.js
@@ -0,0 +1,31 @@
+const UPDATE_TOOLBOX = 'scratch-gui/toolbox/UPDATE_TOOLBOX';
+
+import makeToolboxXML from '../lib/make-toolbox-xml';
+
+const initialState = {
+    toolboxXML: makeToolboxXML()
+};
+
+const reducer = function (state, action) {
+    if (typeof state === 'undefined') state = initialState;
+    switch (action.type) {
+    case UPDATE_TOOLBOX:
+        return Object.assign({}, state, {
+            toolboxXML: action.toolboxXML
+        });
+    default:
+        return state;
+    }
+};
+
+const updateToolbox = function (toolboxXML) {
+    return {
+        type: UPDATE_TOOLBOX,
+        toolboxXML: toolboxXML
+    };
+};
+
+export {
+    reducer as default,
+    updateToolbox
+};
diff --git a/webpack.config.js b/webpack.config.js
index f39fc81a9..eb077f78f 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -99,6 +99,14 @@ module.exports = {
         new CopyWebpackPlugin([{
             from: 'node_modules/scratch-blocks/media',
             to: 'static/blocks-media'
+        }]),
+        new CopyWebpackPlugin([{
+            from: 'extension-worker.{js,js.map}',
+            context: 'node_modules/scratch-vm/dist/web'
+        }]),
+        new CopyWebpackPlugin([{
+            from: 'extensions/**',
+            context: 'node_modules/scratch-vm/dist/web'
         }])
     ].concat(process.env.NODE_ENV === 'production' ? [
         new webpack.optimize.UglifyJsPlugin({
-- 
GitLab