diff --git a/package.json b/package.json index 54d7f7f2f..eeabbe19f 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "gh-pages": "^1.0.0", "html-webpack-plugin": "2.28.0", "lodash.bindall": "4.4.0", + "lodash.debounce": "4.0.8", "lodash.defaultsdeep": "4.6.0", "lodash.isequal": "4.5.0", "lodash.omit": "4.5.0", diff --git a/src/components/close-button/close-button.css b/src/components/close-button/close-button.css index ce9cc99ac..66b79d8a6 100644 --- a/src/components/close-button/close-button.css +++ b/src/components/close-button/close-button.css @@ -6,40 +6,46 @@ align-items: center; justify-content: center; - color: gray; - background-color: $blue; - + overflow: hidden; /* Mask the icon animation */ + background-color: rgba(0, 0, 0, 0.1); border-radius: 50%; - border-color: #dbdbdb; - border-style: solid; - + box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1); font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - cursor: pointer; user-select: none; - transition: all 0.15s ease-out; /* @todo: standardize with var */ + cursor: pointer; + transition: all 0.15s ease-out; +} + +.close-button.large:hover { + transform: scale(1.1, 1.1); + box-shadow: 0 0 0 4px rgba(0, 0, 0, 0.15); } .small { width: 1rem; height: 1rem; - border-width: 1px; + color: #FFF; + background-color: $blue; } .large { - width: 2.75rem; - height: 2.75rem; - border-width: 2px; + width: 1.75rem; + height: 1.75rem; } -.close-button:hover { - transform: scale(1.1, 1.1); -} - -/* Same icon as Sprite Selector Add button, but rotated. - @todo: reuse? -*/ .close-icon { + position: relative; + margin: 0.25rem; + user-select: none; transform-origin: 50%; transform: rotate(45deg); +} + +.small .close-icon { width: 40%; } + +.large .close-icon { + width: 0.75rem; + height: 0.75rem; +} diff --git a/src/components/close-button/close-button.jsx b/src/components/close-button/close-button.jsx index f7c7fc141..c2d78c796 100644 --- a/src/components/close-button/close-button.jsx +++ b/src/components/close-button/close-button.jsx @@ -11,8 +11,8 @@ const CloseButton = props => ( styles.closeButton, props.className, { - [styles.large]: props.size === CloseButton.SIZE_LARGE, - [styles.small]: props.size === CloseButton.SIZE_SMALL + [styles.small]: props.size === CloseButton.SIZE_SMALL, + [styles.large]: props.size === CloseButton.SIZE_LARGE } )} onClick={props.onClick} @@ -24,13 +24,13 @@ const CloseButton = props => ( ); -CloseButton.SIZE_LARGE = 'large'; CloseButton.SIZE_SMALL = 'small'; +CloseButton.SIZE_LARGE = 'large'; CloseButton.propTypes = { className: PropTypes.string, onClick: PropTypes.func.isRequired, - size: PropTypes.oneOf([CloseButton.SIZE_LARGE, CloseButton.SIZE_SMALL]) + size: PropTypes.oneOf([CloseButton.SIZE_SMALL, CloseButton.SIZE_LARGE]) }; CloseButton.defaultProps = { diff --git a/src/components/filter/filter.css b/src/components/filter/filter.css new file mode 100644 index 000000000..fa8e072e1 --- /dev/null +++ b/src/components/filter/filter.css @@ -0,0 +1,103 @@ +@import "../../css/units.css"; +@import "../../css/colors.css"; + +.filter { + display: flex; + flex-direction: row; + align-items: center; + /* + Should we use width 100% instead, for cases when component's + parent is not a flexbox container? + */ + flex-grow: 1; + + padding: 0.3rem 0.5rem; + background: rgba(0, 0, 0, 0.10); + border: 1px solid rgba(255, 255, 255, 0.10); + border-radius: 10rem; + user-select: none; +} + +.filter:hover { + background: rgba(0, 0, 0, 0.15); +} + +.filter-icon { + height: 0.9rem; + width: 0.9rem; + margin: 0 0.5rem 0 0.75rem; +} + +/* + Hidden state +*/ +.x-icon-wrapper { + opacity: 0; + + display: flex; + justify-content: center; + align-items: center; + + overflow: hidden; /* Mask the icon animation */ + height: 1.25rem; + width: 1.25rem; + margin: 0 0.25rem 0 0.5rem; /* @todo: move to parent to make component*/ + + border-radius: 50%; + pointer-events: none; + cursor: default; + transition: opacity 0.05s linear; +} + +/* + Shown state +*/ +.filter.is-active .x-icon-wrapper { + pointer-events: auto; + cursor: pointer; + opacity: 1; + transition: opacity 0.05s linear; +} + +.filter.is-active .x-icon-wrapper:hover { + transform: scale(1.2, 1.2); +} + +/* + Hidden state +*/ +.x-icon { + position: relative; + margin: 0.25rem; + user-select: none; + transform: translateX(0.5rem); + transition: transform 0.085s cubic-bezier(0.78, 1, 1, 1); +} + +/* + Shown state +*/ +.filter.is-active .x-icon-wrapper .x-icon { + transform: translateX(0); +} + +.filter-input { + flex-grow: 1; + line-height: 1.25rem; + background-color: transparent; + -webkit-appearance: none; + outline: none; + border: 0; + color: white; + font-size: 0.75rem; + letter-spacing: 0.15px; + cursor: text; +} + +.filter-input::placeholder { + opacity: 0.75; + padding: 0 0 0 0.25rem; + color: white; + font-size: 0.75rem; + letter-spacing: 0.15px; +} diff --git a/src/components/filter/filter.jsx b/src/components/filter/filter.jsx new file mode 100644 index 000000000..6e140eb80 --- /dev/null +++ b/src/components/filter/filter.jsx @@ -0,0 +1,57 @@ +const classNames = require('classnames'); +const PropTypes = require('prop-types'); +const React = require('react'); + +const filterIcon = require('./icon--filter.svg'); +const xIcon = require('./icon--x.svg'); +const styles = require('./filter.css'); + +const FilterComponent = props => { + const { + onChange, + onClear, + placeholderText, + filterQuery + } = props; + return ( +
0 + })} + > + + +
+ +
+
+ ); +}; + +FilterComponent.propTypes = { + filterQuery: PropTypes.string, + onChange: PropTypes.func, + onClear: PropTypes.func, + placeholderText: PropTypes.string +}; +FilterComponent.defaultProps = { + placeholderText: 'what are you looking for?' +}; +module.exports = FilterComponent; diff --git a/src/components/filter/icon--filter.svg b/src/components/filter/icon--filter.svg new file mode 100644 index 000000000..2a0c75fcb --- /dev/null +++ b/src/components/filter/icon--filter.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/filter/icon--x.svg b/src/components/filter/icon--x.svg new file mode 100644 index 000000000..e78a2a8e2 --- /dev/null +++ b/src/components/filter/icon--x.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/library-item/library-item.css b/src/components/library-item/library-item.css index e25c478d2..12253a552 100644 --- a/src/components/library-item/library-item.css +++ b/src/components/library-item/library-item.css @@ -6,8 +6,9 @@ align-items: center; justify-content: center; flex-basis: 160px; + max-width: 160px; height: 160px; - margin: calc($space / 2); + margin: $space; padding: 1rem 1rem 0 1rem; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; color: #575e75; @@ -23,18 +24,18 @@ .library-item:hover { border-width: 2px; border-color: #1dacf4; - transition: 0.1s ease-out; - transform: scale(1.02, 1.02); } -.library-item.is-selected { - border-width: 2px; - border-color: #1dacf4; - transition: 0.25s ease-out; +.library-item-image-container-wrapper { + height: 100px; + width: 100%; + position: relative; } .library-item-image-container { + position: absolute; height: 100px; + width: 100%; } .library-item-image { @@ -47,12 +48,12 @@ margin: 0.25rem 0; text-align: center; - /* + /* For truncating overflowing text gracefully Min-width is for a bug: https://css-tricks.com/flexbox-truncated-text */ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - min-width: 0; + min-width: 0; } diff --git a/src/components/library-item/library-item.jsx b/src/components/library-item/library-item.jsx index 31e722b0e..cdb1415a1 100644 --- a/src/components/library-item/library-item.jsx +++ b/src/components/library-item/library-item.jsx @@ -1,4 +1,3 @@ -const classNames = require('classnames'); const bindAll = require('lodash.bindall'); const PropTypes = require('prop-types'); const React = require('react'); @@ -9,26 +8,41 @@ const styles = require('./library-item.css'); class LibraryItem extends React.Component { constructor (props) { super(props); - bindAll(this, ['handleClick']); + bindAll(this, [ + 'handleClick', + 'handleMouseEnter', + 'handleMouseLeave' + ]); + } + shouldComponentUpdate (nextProps) { + return this.props.iconURL !== nextProps.iconURL; } handleClick (e) { this.props.onSelect(this.props.id); e.preventDefault(); } + handleMouseEnter () { + this.props.onMouseEnter(this.props.id); + } + handleMouseLeave () { + this.props.onMouseLeave(this.props.id); + } render () { return ( - - + {/* Layers of wrapping is to prevent layout thrashing on animation */} + + + + {this.props.name} @@ -40,8 +54,9 @@ LibraryItem.propTypes = { iconURL: PropTypes.string.isRequired, id: PropTypes.number.isRequired, name: PropTypes.string.isRequired, - onSelect: PropTypes.func.isRequired, - selected: PropTypes.bool.isRequired + onMouseEnter: PropTypes.func.isRequired, + onMouseLeave: PropTypes.func.isRequired, + onSelect: PropTypes.func.isRequired }; module.exports = LibraryItem; diff --git a/src/components/library/library.css b/src/components/library/library.css index b8147a173..73f615191 100644 --- a/src/components/library/library.css +++ b/src/components/library/library.css @@ -1,24 +1,14 @@ +@import "../../css/units.css"; +@import "../../css/colors.css"; + .library-scroll-grid { display: flex; + justify-content: center; + align-content: flex-start; + background: $ui-pane-gray; flex-grow: 1; - justify-content: space-between; flex-wrap: wrap; - overflow-y: scroll; - height: calc(100% - 6rem); /* @todo: currently estimate, fix precision */ - - /* - Gives sprites a bit of room so they don't get cut off when they grow on hover - @todo: sync as a var, with the transform defined on .library-item:hover - */ - padding: 0.15rem; -} - -.modal-header { - width: 100%; - margin-bottom: 2rem; + overflow-y: auto; + height: calc(100% - $library-header-height); padding: 0.5rem; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 1.5rem; - font-weight: normal; - color: #8e8f95; } diff --git a/src/components/library/library.jsx b/src/components/library/library.jsx index 16327c10c..ad33743be 100644 --- a/src/components/library/library.jsx +++ b/src/components/library/library.jsx @@ -10,42 +10,62 @@ const styles = require('./library.css'); class LibraryComponent extends React.Component { constructor (props) { super(props); - bindAll(this, ['handleSelect']); - this.state = {selectedItem: null}; + bindAll(this, [ + 'handleFilterChange', + 'handleFilterClear', + 'handleMouseEnter', + 'handleMouseLeave', + 'handleSelect' + ]); + this.state = { + selectedItem: null, + filterQuery: '' + }; } handleSelect (id) { - if (this.state.selectedItem === id) { - // Double select: select as the library's value. - this.props.onRequestClose(); - this.props.onItemSelected(this.props.data[id]); - } else { - if (this.props.onItemChosen) { - this.props.onItemChosen(this.props.data[id]); - } - } - this.setState({selectedItem: id}); + this.props.onRequestClose(); + this.props.onItemSelected(this.getFilteredData()[id]); + } + handleMouseEnter (id) { + if (this.props.onItemMouseEnter) this.props.onItemMouseEnter(this.getFilteredData()[id]); + } + handleMouseLeave (id) { + if (this.props.onItemMouseLeave) this.props.onItemMouseLeave(this.getFilteredData()[id]); + } + handleFilterChange (event) { + this.setState({filterQuery: event.target.value}); + } + handleFilterClear () { + this.setState({filterQuery: ''}); + } + getFilteredData () { + return this.props.data.filter(dataItem => + dataItem.name.toLowerCase().indexOf(this.state.filterQuery.toLowerCase()) !== -1); } render () { if (!this.props.visible) return null; return ( -

{this.props.title}

- {this.props.data.map((dataItem, itemId) => { + {this.getFilteredData().map((dataItem, index) => { const scratchURL = dataItem.md5 ? `https://cdn.assets.scratch.mit.edu/internalapi/asset/${dataItem.md5}/get/` : dataItem.rawURL; return ( ); @@ -59,6 +79,7 @@ class LibraryComponent extends React.Component { LibraryComponent.propTypes = { data: PropTypes.arrayOf( /* eslint-disable react/no-unused-prop-types, lines-around-comment */ + // An item in the library PropTypes.shape({ // @todo remove md5/rawURL prop from library, refactor to use storage md5: PropTypes.string, @@ -67,7 +88,8 @@ LibraryComponent.propTypes = { }) /* eslint-enable react/no-unused-prop-types, lines-around-comment */ ), - onItemChosen: PropTypes.func, + onItemMouseEnter: PropTypes.func, + onItemMouseLeave: PropTypes.func, onItemSelected: PropTypes.func, onRequestClose: PropTypes.func, title: PropTypes.string.isRequired, diff --git a/src/components/modal/modal.css b/src/components/modal/modal.css index 25a134f5e..a72a4f06d 100644 --- a/src/components/modal/modal.css +++ b/src/components/modal/modal.css @@ -11,32 +11,69 @@ background-color: rgba(0, 0, 0, .75); } -.modal-content { +/* @todo: extract to type: full ? */ +.full-modal-content { position: absolute; outline: none; - overflow-y: scroll; + overflow-y: auto; -webkit-overflow-scrolling: 'touch'; - border: 1px solid #ccc; - padding: 0; - top: 5%; - right: 5%; - bottom: 5%; - left: 5%; - border-radius: $space; + user-select: none; + height: 100%; + width: 100%; + display: flex; +} + +/* + Modal header has 3 items: + |filter title x| + + Use the same width for both side item containers, + so that title remains centered +*/ +$sides: 20rem; + +.header { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: flex-start; + height: $library-header-height; + + box-sizing: border-box; + width: 100%; + background-color: $blue; + + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 1rem; + font-weight: normal; +} + +.header-item { + display: flex; + align-items: center; + padding: 1rem; + text-decoration: none; + color: white; user-select: none; } -.modal-children { - overflow: hidden; - height: 100%; - z-index: 0; - padding: 2rem; - background: $ui-pane-gray; +.header-item-filter { + display: flex; + flex-basis: $sides; + justify-content: flex-start; } -.close-button { - position: absolute; - top: 1rem; - right: 1rem; - z-index: 2; +.header-item-title { + flex-grow: 1; + flex-shrink: 0; + justify-content: center; + user-select: none; + letter-spacing: 0.4px; + cursor: default; } + +.header-item-close { + flex-basis: $sides; + justify-content: flex-end; +} + diff --git a/src/components/modal/modal.jsx b/src/components/modal/modal.jsx index 3a3fc25db..2a743bd74 100644 --- a/src/components/modal/modal.jsx +++ b/src/components/modal/modal.jsx @@ -1,9 +1,11 @@ +const classNames = require('classnames'); const PropTypes = require('prop-types'); const React = require('react'); const ReactModal = require('react-modal'); const Box = require('../box/box.jsx'); const CloseButton = require('../close-button/close-button.jsx'); +const Filter = require('../filter/filter.jsx'); const styles = require('./modal.css'); @@ -11,21 +13,44 @@ class ModalComponent extends React.Component { render () { return ( (this.modal = m)} - onRequestClose={this.props.onRequestClose} > - +
+
+ +
+
+ {this.props.contentLabel} +
+
+ +
+
{this.props.children}
@@ -36,7 +61,10 @@ class ModalComponent extends React.Component { ModalComponent.propTypes = { children: PropTypes.node, contentLabel: PropTypes.string.isRequired, - onRequestClose: PropTypes.func.isRequired, + filterQuery: PropTypes.string, + onFilterChange: PropTypes.func, + onFilterClear: PropTypes.func, + onRequestClose: PropTypes.func, visible: PropTypes.bool.isRequired }; diff --git a/src/components/monitor/monitor.jsx b/src/components/monitor/monitor.jsx index edd0fb043..54b1ebe8a 100644 --- a/src/components/monitor/monitor.jsx +++ b/src/components/monitor/monitor.jsx @@ -7,7 +7,7 @@ const styles = require('./monitor.css'); const categories = { data: '#FF8C1A', sensing: '#5CB1D6', - sounds: '#CF63CF', + sound: '#CF63CF', looks: '#9966FF', motion: '#4C97FF' }; diff --git a/src/containers/backdrop-library.jsx b/src/containers/backdrop-library.jsx index b19921b4c..62e9cd473 100644 --- a/src/containers/backdrop-library.jsx +++ b/src/containers/backdrop-library.jsx @@ -4,7 +4,7 @@ const React = require('react'); const VM = require('scratch-vm'); const backdropLibraryContent = require('../lib/libraries/backdrops.json'); -const LibaryComponent = require('../components/library/library.jsx'); +const LibraryComponent = require('../components/library/library.jsx'); class BackdropLibrary extends React.Component { @@ -26,7 +26,7 @@ class BackdropLibrary extends React.Component { } render () { return ( - { + this.updateToolboxBlockValue(`${prefix}x`, this.props.vm.editingTarget.x.toFixed(0)); + this.updateToolboxBlockValue(`${prefix}y`, this.props.vm.editingTarget.y.toFixed(0)); + }); + } } - onWorkspaceMetricsChange () { const target = this.props.vm.editingTarget; if (target && target.id) { @@ -127,7 +148,6 @@ class Blocks extends React.Component { if (this.props.vm.editingTarget && !this.state.workspaceMetrics[this.props.vm.editingTarget.id]) { this.onWorkspaceMetricsChange(); } - this.ScratchBlocks.Events.disable(); this.workspace.clear(); @@ -152,6 +172,7 @@ class Blocks extends React.Component { } handlePromptCallback (data) { this.state.prompt.callback(data); + this.props.vm.createVariable(data); this.handlePromptClose(); } handlePromptClose () { diff --git a/src/containers/costume-library.jsx b/src/containers/costume-library.jsx index 1fea39a2d..f245c098f 100644 --- a/src/containers/costume-library.jsx +++ b/src/containers/costume-library.jsx @@ -4,10 +4,10 @@ const React = require('react'); const VM = require('scratch-vm'); const costumeLibraryContent = require('../lib/libraries/costumes.json'); -const LibaryComponent = require('../components/library/library.jsx'); +const LibraryComponent = require('../components/library/library.jsx'); -class CostumeLibrary extends React.Component { +class CostumeLibrary extends React.PureComponent { constructor (props) { super(props); bindAll(this, [ @@ -26,7 +26,7 @@ class CostumeLibrary extends React.Component { } render () { return ( - diff --git a/src/containers/sprite-library.jsx b/src/containers/sprite-library.jsx index c08748eb2..6395b421a 100644 --- a/src/containers/sprite-library.jsx +++ b/src/containers/sprite-library.jsx @@ -5,24 +5,70 @@ const VM = require('scratch-vm'); const spriteLibraryContent = require('../lib/libraries/sprites.json'); -const LibaryComponent = require('../components/library/library.jsx'); +const LibraryComponent = require('../components/library/library.jsx'); -class SpriteLibrary extends React.Component { +class SpriteLibrary extends React.PureComponent { constructor (props) { super(props); bindAll(this, [ - 'handleItemSelect' + 'handleItemSelect', + 'handleMouseEnter', + 'handleMouseLeave', + 'rotateCostume', + 'startRotatingCostumes', + 'stopRotatingCostumes' ]); + this.state = { + activeSprite: null, + costumeIndex: 0, + sprites: spriteLibraryContent + }; + } + componentWillReceiveProps (newProps) { + if (!newProps.visible) clearInterval(this.intervalId); } handleItemSelect (item) { this.props.vm.addSprite2(JSON.stringify(item.json)); } + handleMouseEnter (item) { + this.stopRotatingCostumes(); + this.setState({activeSprite: item}, this.startRotatingCostumes); + } + handleMouseLeave () { + this.stopRotatingCostumes(); + } + startRotatingCostumes () { + if (!this.state.activeSprite) return; + this.rotateCostume(); + this.intervalId = setInterval(this.rotateCostume, 300); + } + stopRotatingCostumes () { + this.intervalId = clearInterval(this.intervalId); + } + rotateCostume () { + const costumes = this.state.activeSprite.json.costumes; + const nextCostumeIndex = (this.state.costumeIndex + 1) % costumes.length; + this.setState({ + costumeIndex: nextCostumeIndex, + sprites: this.state.sprites.map(sprite => { + if (sprite.name === this.state.activeSprite.name) { + return { + ...sprite, + md5: sprite.json.costumes[nextCostumeIndex].baseLayerMD5 + }; + } + return sprite; + }) + }); + } render () { return ( - diff --git a/src/css/units.css b/src/css/units.css index ab4393ac1..3f9aaba17 100644 --- a/src/css/units.css +++ b/src/css/units.css @@ -3,5 +3,7 @@ $space: 0.5rem; $sprites-per-row: 5; $menu-bar-height: 3rem; -$sprite-info-height: 5.25rem; /* TODO: SpriteInfo isn't explicitly set to this height yet */ +$sprite-info-height: 5.25rem; /* @todo: SpriteInfo isn't explicitly set to this height yet */ $stage-menu-height: 3rem; + +$library-header-height: 4.375rem; \ No newline at end of file diff --git a/src/lib/monitor-adapter.js b/src/lib/monitor-adapter.js new file mode 100644 index 000000000..8dcc802a4 --- /dev/null +++ b/src/lib/monitor-adapter.js @@ -0,0 +1,26 @@ +/** + * Convert monitors from VM format to what the GUI needs to render. + * - Convert opcode to a label and a category + * - Add missing XY position data if needed + */ +const OpcodeLabels = require('../lib/opcode-labels.js'); + +const PADDING = 5; +const MONITOR_HEIGHT = 23; + +const isUndefined = a => typeof a === 'undefined'; + +module.exports = function ({id, opcode, params, value, x, y}, monitorIndex) { + let {label, category, labelFn} = OpcodeLabels(opcode); + + // Use labelFn if provided for dynamic labelling (e.g. variables) + if (!isUndefined(labelFn)) label = labelFn(params); + + // Simple layout if x or y are undefined + // @todo scratch2 has a more complex layout behavior we may want to adopt + // @todo e.g. this does not work well when monitors have already been moved + if (isUndefined(x)) x = PADDING; + if (isUndefined(y)) y = PADDING + (monitorIndex * (PADDING + MONITOR_HEIGHT)); + + return {id, label, category, value, x, y}; +}; diff --git a/src/lib/opcode-labels.js b/src/lib/opcode-labels.js new file mode 100644 index 000000000..bebe14743 --- /dev/null +++ b/src/lib/opcode-labels.js @@ -0,0 +1,75 @@ +const opcodeMap = { + // Motion + motion_direction: { + category: 'motion', + label: 'direction' + }, + motion_xposition: { + category: 'motion', + label: 'x position' + }, + motion_yposition: { + category: 'motion', + label: 'y position' + }, + + // Looks + looks_size: { + category: 'looks', + label: 'size' + }, + looks_costumeorder: { + category: 'looks', + label: 'costume #' + }, + looks_backdroporder: { + category: 'looks', + label: 'backdrop #' + }, + looks_backdropname: { + category: 'looks', + label: 'backdrop name' + }, + + // Data + data_variable: { + category: 'data', + labelFn: params => params.VARIABLE + }, + + // Sound + sound_volume: { + category: 'sound', + label: 'volume' + }, + sound_tempo: { + category: 'sound', + label: 'tempo' + }, + + // Sensing + sensing_loudness: { + category: 'sensing', + label: 'loudness' + }, + sensing_of: { + category: 'sensing', + labelFn: params => `${params.PROPERTY} of ${params.OBJECT}` + }, + sensing_current: { + category: 'sensing', + labelFn: params => params.CURRENTMENU.toLowerCase() + }, + sensing_timer: { + category: 'sensing', + label: 'timer' + } +}; + +module.exports = function (opcode) { + if (opcode in opcodeMap) return opcodeMap[opcode]; + return { + category: 'data', + label: opcode + }; +}; diff --git a/src/reducers/monitors.js b/src/reducers/monitors.js index e8b4fa947..18e9529f4 100644 --- a/src/reducers/monitors.js +++ b/src/reducers/monitors.js @@ -1,3 +1,5 @@ +const monitorAdapter = require('../lib/monitor-adapter.js'); + const UPDATE_MONITORS = 'scratch-gui/monitors/UPDATE_MONITORS'; const initialState = []; @@ -6,7 +8,7 @@ const reducer = function (state, action) { if (typeof state === 'undefined') state = initialState; switch (action.type) { case UPDATE_MONITORS: - return [...action.monitors]; + return action.monitors.map(monitorAdapter); default: return state; }