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