diff --git a/.travis.yml b/.travis.yml index 991c69bba..59e1d15ba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,11 +3,8 @@ sudo: required dist: trusty addons: chrome: stable -before_script: - - "export DISPLAY=:99.0" - - "sh -e /etc/init.d/xvfb start" node_js: -- 6 +- 8 cache: directories: - node_modules diff --git a/package.json b/package.json index b8649d8c1..e9f49338f 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "react-dom": "^16.0.0" }, "devDependencies": { - "autoprefixer": "^7.1.3", + "autoprefixer": "^8.1.0", "babel-core": "^6.23.1", "babel-eslint": "^8.0.1", "babel-loader": "^7.1.0", @@ -37,7 +37,7 @@ "babel-preset-es2015": "^6.22.0", "babel-preset-react": "^6.22.0", "buffer-loader": "0.0.1", - "chromedriver": "2.35.0", + "chromedriver": "2.37.0", "classnames": "2.2.5", "copy-webpack-plugin": "^4.3.0", "css-loader": "^0.28.7", @@ -48,11 +48,11 @@ "eslint-config-scratch": "^5.0.0", "eslint-plugin-import": "^2.8.0", "eslint-plugin-react": "^7.5.1", - "file-loader": "1.1.9", + "file-loader": "1.1.11", "get-float-time-domain-data": "0.1.0", "get-user-media-promise": "1.1.1", "gh-pages": "github:rschamp/gh-pages#publish-branch-to-subfolder", - "html-webpack-plugin": "^2.30.0", + "html-webpack-plugin": "^3.0.5", "immutable": "3.8.2", "jest": "^21.0.0", "lodash.bindall": "4.4.0", @@ -77,10 +77,10 @@ "react-ga": "2.4.1", "react-intl": "2.4.0", "react-intl-redux": "0.7.0", - "react-modal": "3.3.1", + "react-modal": "3.3.2", "react-redux": "5.0.7", - "react-responsive": "4.0.3", - "react-style-proptype": "3.2.0", + "react-responsive": "4.1.0", + "react-style-proptype": "3.2.1", "react-tabs": "2.2.1", "react-test-renderer": "16.2.0", "react-tooltip": "3.4.0", @@ -89,12 +89,12 @@ "redux-throttle": "0.1.1", "rimraf": "^2.6.1", "scratch-audio": "0.1.0-prerelease.1516198804", - "scratch-blocks": "0.1.0-prerelease.1519256473", + "scratch-blocks": "0.1.0-prerelease.1522264400", "scratch-l10n": "2.0.20180108132626", - "scratch-paint": "0.2.0-prerelease.20180302160120", - "scratch-render": "0.1.0-prerelease.1516837442", + "scratch-paint": "0.2.0-prerelease.20180403143543", + "scratch-render": "0.1.0-prerelease.1522346711", "scratch-storage": "0.4.0", - "scratch-vm": "0.1.0-prerelease.1519681201-prerelease.1519681262", + "scratch-vm": "0.1.0-prerelease.1522355889-prerelease.1522355906", "selenium-webdriver": "3.6.0", "startaudiocontext": "1.2.1", "style-loader": "^0.20.0", diff --git a/src/components/asset-panel/selector.jsx b/src/components/asset-panel/selector.jsx index f2ab58cae..ff818415a 100644 --- a/src/components/asset-panel/selector.jsx +++ b/src/components/asset-panel/selector.jsx @@ -45,6 +45,7 @@ const Selector = props => { id={index} key={`asset-${index}`} name={item.name} + number={index + 1 /* 1-indexed */} selected={index === selectedItemIndex} onClick={onItemClick} onDeleteButtonClick={onDeleteClick} @@ -59,9 +60,9 @@ const Selector = props => { Selector.propTypes = { buttons: PropTypes.arrayOf(PropTypes.shape({ - message: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, img: PropTypes.string.isRequired, - onClick: PropTypes.func.isRequired + onClick: PropTypes.func })), items: PropTypes.arrayOf(PropTypes.shape({ url: PropTypes.string, diff --git a/src/components/browser-modal/browser-modal.jsx b/src/components/browser-modal/browser-modal.jsx index c410f7f62..9052d5f57 100644 --- a/src/components/browser-modal/browser-modal.jsx +++ b/src/components/browser-modal/browser-modal.jsx @@ -9,7 +9,7 @@ import styles from './browser-modal.css'; const messages = defineMessages({ label: { id: 'gui.unsupportedBrowser.label', - defaultMessage: 'Internet Explorer is not supported', + defaultMessage: 'Browser is not supported', description: '' } }); @@ -31,7 +31,7 @@ const BrowserModal = ({intl, ...props}) => (

{ /* eslint-disable max-len */ } diff --git a/src/components/button/button.css b/src/components/button/button.css index 44243650f..5e69aabd4 100644 --- a/src/components/button/button.css +++ b/src/components/button/button.css @@ -1,3 +1,19 @@ -.button { +.outlined-button { cursor: pointer; + border-radius: .25rem; + font-weight: bold; + display: flex; + flex-direction: row; + align-items: center; + padding-left: .75rem; + padding-right: .75rem; +} + +.icon { + margin-right: .5rem; + height: 1.5rem; +} + +.content { + white-space: nowrap; } diff --git a/src/components/button/button.jsx b/src/components/button/button.jsx index 59b39668f..1d348ba24 100644 --- a/src/components/button/button.jsx +++ b/src/components/button/button.jsx @@ -6,25 +6,38 @@ import styles from './button.css'; const ButtonComponent = ({ className, + disabled, + iconClassName, + iconSrc, onClick, children, ...props }) => { - if (props.disabled === true) { + + if (disabled) { onClick = function () {}; } + const icon = iconSrc && ( + + ); + return ( - {children} + {icon} +

{children}
); }; @@ -33,6 +46,8 @@ ButtonComponent.propTypes = { children: PropTypes.node, className: PropTypes.string, disabled: PropTypes.bool, + iconClassName: PropTypes.string, + iconSrc: PropTypes.string, onClick: PropTypes.func.isRequired }; diff --git a/src/components/filter/filter.jsx b/src/components/filter/filter.jsx index 5363ef081..6ccf4fdd4 100644 --- a/src/components/filter/filter.jsx +++ b/src/components/filter/filter.jsx @@ -25,7 +25,6 @@ const FilterComponent = props => { src={filterIcon} /> { const { activeTabIndex, @@ -69,7 +73,9 @@ const GUIComponent = props => { tabSelected: classNames(tabStyles.reactTabsTabSelected, styles.isSelected) }; - const isRendererSupported = Renderer.isSupported(); + if (isRendererSupported === null) { + isRendererSupported = Renderer.isSupported(); + } return ( + + + dropdown-caret + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/src/components/language-selector/language-icon.svg b/src/components/language-selector/language-icon.svg index 42bd409ab..e3a712ffb 100644 --- a/src/components/language-selector/language-icon.svg +++ b/src/components/language-selector/language-icon.svg @@ -1,12 +1,12 @@ - + - + Language Created with Sketch. - - - + + + diff --git a/src/components/language-selector/language-selector.css b/src/components/language-selector/language-selector.css index 0ae9e0a10..bae30cdd3 100644 --- a/src/components/language-selector/language-selector.css +++ b/src/components/language-selector/language-selector.css @@ -9,8 +9,11 @@ } .language-icon { - height: 2rem; - display: none; + height: 1.5rem; +} + +.disabled { + opacity: .5; } .language-select { diff --git a/src/components/language-selector/language-selector.jsx b/src/components/language-selector/language-selector.jsx index db846fef5..e4779d8c6 100644 --- a/src/components/language-selector/language-selector.jsx +++ b/src/components/language-selector/language-selector.jsx @@ -1,27 +1,22 @@ +import classNames from 'classnames'; import PropTypes from 'prop-types'; import React from 'react'; import Box from '../box/box.jsx'; -import {ComingSoonTooltip} from '../coming-soon/coming-soon.jsx'; import locales from 'scratch-l10n'; import languageIcon from './language-icon.svg'; +import dropdownCaret from './dropdown-caret.svg'; import styles from './language-selector.css'; const LanguageSelector = ({ currentLocale, onChange, + open, ...props }) => ( - -
- +
+ {open ? ( -
- + ) : ( + + + + + )} +
); LanguageSelector.propTypes = { currentLocale: PropTypes.string, - onChange: PropTypes.func + onChange: PropTypes.func, + open: PropTypes.boolean }; export default LanguageSelector; diff --git a/src/components/load-button/load-button.jsx b/src/components/load-button/load-button.jsx index 0f580278c..d437a9c02 100644 --- a/src/components/load-button/load-button.jsx +++ b/src/components/load-button/load-button.jsx @@ -26,6 +26,7 @@ const LoadButtonComponent = ({ { - const messages = [ - { - message: ( - - ), - weight: 50 - }, - { - message: ( - - ), - weight: 50 - }, - { - message: ( - - ), - weight: 50 - }, - { - message: ( - - ), - weight: 50 - }, - { - message: ( - - ), - weight: 20 - }, - { - message: ( - - ), - weight: 1 - }, - { - message: ( - - ), - weight: 1 - }, - { - message: ( - - ), - weight: 1 - }, - { - message: ( - - ), - weight: 1 - } - ]; - - let message; - const sum = messages.reduce((acc, m) => acc + m.weight, 0); - let rand = sum * Math.random(); - for (let i = 0; i < messages.length; i++) { - rand -= messages[i].weight; - if (rand <= 0) { - message = messages[i].message; - break; - } +const messages = [ + { + message: ( + + ), + weight: 50 + }, + { + message: ( + + ), + weight: 50 + }, + { + message: ( + + ), + weight: 50 + }, + { + message: ( + + ), + weight: 50 + }, + { + message: ( + + ), + weight: 20 + }, + { + message: ( + + ), + weight: 1 + }, + { + message: ( + + ), + weight: 1 + }, + { + message: ( + + ), + weight: 1 + }, + { + message: ( + + ), + weight: 1 } +]; - return ( -
-
-
- - - +class LoaderComponent extends React.Component { + constructor (props) { + super(props); + this.state = { + messageNumber: 0 + }; + } + componentDidMount () { + this.chooseRandomMessage(); + + // Start an interval to choose a new message every 5 seconds + this.intervalId = setInterval(() => { + this.chooseRandomMessage(); + }, 5000); + } + componentWillUnmount () { + clearInterval(this.intervalId); + } + chooseRandomMessage () { + let messageNumber; + const sum = messages.reduce((acc, m) => acc + m.weight, 0); + let rand = sum * Math.random(); + for (let i = 0; i < messages.length; i++) { + rand -= messages[i].weight; + if (rand <= 0) { + messageNumber = i; + break; + } + } + this.setState({messageNumber}); + } + render () { + return ( +
+
+
+ + + +
+

+ +

+
+
+ {messages.map((m, i) => ( +
+ {m.message} +
+ ))} +
+
-

- -

-

{message}

-
- ); -}; + ); + } +} export default LoaderComponent; diff --git a/src/components/menu-bar/icon--mystuff.png b/src/components/menu-bar/icon--mystuff.png new file mode 100644 index 000000000..79bbcb7f9 Binary files /dev/null and b/src/components/menu-bar/icon--mystuff.png differ diff --git a/src/components/menu-bar/icon--profile.png b/src/components/menu-bar/icon--profile.png new file mode 100644 index 000000000..41e62b614 Binary files /dev/null and b/src/components/menu-bar/icon--profile.png differ diff --git a/src/components/menu-bar/icon--see-community.svg b/src/components/menu-bar/icon--see-community.svg new file mode 100644 index 000000000..1f5b6ee6e --- /dev/null +++ b/src/components/menu-bar/icon--see-community.svg @@ -0,0 +1,12 @@ + + + + See Community + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/src/components/menu-bar/menu-bar.css b/src/components/menu-bar/menu-bar.css index d1f4c3185..7e59dd4ef 100644 --- a/src/components/menu-bar/menu-bar.css +++ b/src/components/menu-bar/menu-bar.css @@ -6,24 +6,26 @@ flex-direction: row; justify-content: space-between; flex-wrap: nowrap; - - /* - For most things, we shouldn't explicitly set height, and let the - content push the element to whatever fits. Using a fixed height - instead, will help us subtract the value we assign from the body, - adding up to a perfect 100%. This means we don't need to set - overflow: hidden, which makes it hard to debug. border-box - simplifies by all of this by removing padding from the equation. + + /* + For most things, we shouldn't explicitly set height, and let the + content push the element to whatever fits. Using a fixed height + instead, will help us subtract the value we assign from the body, + adding up to a perfect 100%. This means we don't need to set + overflow: hidden, which makes it hard to debug. border-box + simplifies by all of this by removing padding from the equation. */ box-sizing: border-box; - height: $menu-bar-height; + height: $menu-bar-height; - /* - @todo: This adds ~20px in Chrome, when scrolling to the right, + /* + @todo: This adds ~20px in Chrome, when scrolling to the right, but fixes [FFx + Safari] [resize window down + scroll to the right] bug. - width: 100%; + width: 100%; */ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: .75rem; + font-weight: bold; background-color: $motion-primary; color: white; } @@ -33,18 +35,13 @@ flex-direction: row; justify-content: flex-start; flex-wrap: nowrap; -} - -/* - Using a wrapper so when logo is hovered, it transforms via its center point - @todo: not sure if this is needed -*/ -.logo-wrapper { + align-items: center; } .scratch-logo { - height: $menu-bar-height; + height: $menu-bar-height; padding: 0.8rem 0; + vertical-align: middle; } .scratch-logo:hover { @@ -53,43 +50,101 @@ transition: all .075s ease-in; } -.title { - display: inline-block; - margin: 0 2rem; -} - .menu-item { display: block; - padding: 0 1.5rem; - line-height: $menu-bar-height; + padding: 0 0.25rem; cursor: pointer; text-decoration: none; - color: $motion-tertiary; + color: #fff; user-select: none; + align-self: center; } -.feedback-button-wrapper { - align-self: center; - padding: 0 .5rem; - line-height: $menu-bar-height; +.file-group { + display: flex; + flex-direction: row; + align-items: center; +} + +.file-group .menu-item { + padding: 0 0.75rem; } .feedback-button { - border-radius: 5px; - padding: .75rem; background-color: #FFF; - cursor: pointer; - font-size: .75rem; - font-weight: bold; color: $motion-primary; + height: 34px; } -.feedback-button-icon { - vertical-align: middle; +.divider { + border-right: 1px dashed $menubar-gray; + margin: 0 .5rem; + height: 34px; } -.feedback-button-icon { +.title-field { + border: 1px dashed $menubar-gray; + border-radius: .25rem; + width: 12rem; + height: 34px; + background-color: transparent; + padding: .5rem; +} + +.title-field, +.title-field::placeholder { + color: #fff; + font-weight: bold; + font-size: .8rem; +} + +.share-button { + background: #FFAB19; + height: 32px; + box-shadow: 0 0 0 1px rgba(0,0,0,.1); +} + +.community-button { + height: 32px; + box-shadow: 0 0 0 1px rgba(0,0,0,.1); +} + +.community-button-icon { + height: 1.25rem; +} + +.coming-soon >:not(.coming-soon-tooltip) { + opacity: 0.5; +} + +.account-info-wrapper { + display: flex; + flex-direction: row; + padding: 0 .5rem; + align-items: center; +} + +.mystuff-icon { margin-right: .5rem; - width: 1.5rem; - height: 1.5rem; + height: 1rem; +} + +.account-nav-menu, .mystuff-button { + padding: 0 .25rem; + display: flex; + flex-direction: row; + align-items: center; +} + +.profile-icon { + margin-right: .5rem; + width: 2rem; + height: 34px; + border-radius: 0.25rem; +} + +.dropdown-caret-icon { + margin-left: .5rem; + width: 0.5rem; + height: 0.5rem; } diff --git a/src/components/menu-bar/menu-bar.jsx b/src/components/menu-bar/menu-bar.jsx index 580729454..587b51fc4 100644 --- a/src/components/menu-bar/menu-bar.jsx +++ b/src/components/menu-bar/menu-bar.jsx @@ -6,15 +6,18 @@ import React from 'react'; import Box from '../box/box.jsx'; import Button from '../button/button.jsx'; -import LoadButton from '../../containers/load-button.jsx'; -import SaveButton from '../../containers/save-button.jsx'; +import {ComingSoonTooltip} from '../coming-soon/coming-soon.jsx'; import LanguageSelector from '../../containers/language-selector.jsx'; import {openFeedbackForm} from '../../reducers/modals'; import styles from './menu-bar.css'; +import mystuffIcon from './icon--mystuff.png'; +import profileIcon from './icon--profile.png'; import feedbackIcon from './icon--feedback.svg'; +import communityIcon from './icon--see-community.svg'; +import dropdownCaret from '../language-selector/dropdown-caret.svg'; import scratchLogo from './scratch-logo.svg'; const MenuBar = props => ( @@ -24,37 +27,142 @@ const MenuBar = props => ( })} >
-
- Scratch +
+
+ Scratch +
+
+ + + +
+
+ +
File
+
+
+
+ +
Edit
+
+
+
+
+
+ + + +
+
+ + + +
+
+ + +
- - -
-
+
+
+ +
+ +
+
+ +
+ + scratch-cat + +
+
+
); diff --git a/src/components/sprite-selector-item/sprite-selector-item.css b/src/components/sprite-selector-item/sprite-selector-item.css index 60f839356..a0880a518 100644 --- a/src/components/sprite-selector-item/sprite-selector-item.css +++ b/src/components/sprite-selector-item/sprite-selector-item.css @@ -56,3 +56,11 @@ right: 0.125rem; z-index: 2; } + +.number { + position: absolute; + top: 0.125rem; + left: 0.125rem; + font-size: 0.625rem; + z-index: 2; +} diff --git a/src/components/sprite-selector-item/sprite-selector-item.jsx b/src/components/sprite-selector-item/sprite-selector-item.jsx index abb47bb54..35eeebe00 100644 --- a/src/components/sprite-selector-item/sprite-selector-item.jsx +++ b/src/components/sprite-selector-item/sprite-selector-item.jsx @@ -31,6 +31,9 @@ const SpriteSelectorItem = props => ( onClick={props.onDeleteButtonClick} /> ) : null } + {typeof props.number === 'undefined' ? null : ( +
{props.number}
+ )} {props.costumeURL ? ( { }, { title: intl.formatMessage(messages.addBackdropFromSurprise), img: surpriseIcon, - onClick: onSurpriseBackdropClick // TODO NEED REAL FUNCTION + onClick: onSurpriseBackdropClick }, { title: intl.formatMessage(messages.addBackdropFromPaint), img: paintIcon, - onClick: onEmptyBackdropClick // TODO NEED REAL FUNCTION + onClick: onEmptyBackdropClick } ]} title={intl.formatMessage(messages.addBackdropFromLibrary)} diff --git a/src/components/target-pane/target-pane.jsx b/src/components/target-pane/target-pane.jsx index 1044073a1..7cc7a1ed2 100644 --- a/src/components/target-pane/target-pane.jsx +++ b/src/components/target-pane/target-pane.jsx @@ -4,7 +4,6 @@ import React from 'react'; import VM from 'scratch-vm'; import SpriteLibrary from '../../containers/sprite-library.jsx'; -import BackdropLibrary from '../../containers/backdrop-library.jsx'; import SpriteSelectorComponent from '../sprite-selector/sprite-selector.jsx'; import StageSelector from '../../containers/stage-selector.jsx'; @@ -17,7 +16,6 @@ import styles from './target-pane.css'; * @returns {React.Component} rendered component */ const TargetPane = ({ - backdropLibraryVisible, editingTarget, hoveredTarget, spriteLibraryVisible, @@ -33,7 +31,6 @@ const TargetPane = ({ onSurpriseSpriteClick, onPaintSpriteClick, onRequestCloseSpriteLibrary, - onRequestCloseBackdropLibrary, onSelectSprite, raiseSprites, stage, @@ -83,12 +80,6 @@ const TargetPane = ({ onRequestClose={onRequestCloseSpriteLibrary} /> ) : null} - {backdropLibraryVisible ? ( - - ) : null}
@@ -113,7 +104,6 @@ const spriteShape = PropTypes.shape({ }); TargetPane.propTypes = { - backdropLibraryVisible: PropTypes.bool, editingTarget: PropTypes.string, extensionLibraryVisible: PropTypes.bool, hoveredTarget: PropTypes.shape({ @@ -130,7 +120,6 @@ TargetPane.propTypes = { onDuplicateSprite: PropTypes.func, onNewSpriteClick: PropTypes.func, onPaintSpriteClick: PropTypes.func, - onRequestCloseBackdropLibrary: PropTypes.func, onRequestCloseExtensionLibrary: PropTypes.func, onRequestCloseSpriteLibrary: PropTypes.func, onSelectSprite: PropTypes.func, diff --git a/src/containers/backdrop-library.jsx b/src/containers/backdrop-library.jsx index e6b7d4d39..8f796225e 100644 --- a/src/containers/backdrop-library.jsx +++ b/src/containers/backdrop-library.jsx @@ -23,11 +23,7 @@ class BackdropLibrary extends React.Component { bitmapResolution: item.info.length > 2 ? item.info[2] : 1, skinId: null }; - this.props.vm.addBackdrop(item.md5, vmBackdrop).then(() => { - if (this.props.onNewBackdrop) { - this.props.onNewBackdrop(); - } - }); + this.props.vm.addBackdrop(item.md5, vmBackdrop); analytics.event({ category: 'library', action: 'Select Backdrop', @@ -47,7 +43,6 @@ class BackdropLibrary extends React.Component { } BackdropLibrary.propTypes = { - onNewBackdrop: PropTypes.func, onRequestClose: PropTypes.func, vm: PropTypes.instanceOf(VM).isRequired }; diff --git a/src/containers/blocks.jsx b/src/containers/blocks.jsx index bedbbdbad..504291036 100644 --- a/src/containers/blocks.jsx +++ b/src/containers/blocks.jsx @@ -95,9 +95,11 @@ class Blocks extends React.Component { } if (prevProps.toolboxXML !== this.props.toolboxXML) { - const selectedCategoryName = this.workspace.toolbox_.getSelectedItem().name_; + const categoryName = this.workspace.toolbox_.getSelectedCategoryName(); + const offset = this.workspace.toolbox_.getCategoryScrollOffset(); this.workspace.updateToolbox(this.props.toolboxXML); - this.workspace.toolbox_.setSelectedCategoryByName(selectedCategoryName); + const currentCategoryPos = this.workspace.toolbox_.getCategoryPositionByName(categoryName); + this.workspace.toolbox_.setFlyoutScrollPos(currentCategoryPos + offset); } if (this.props.isVisible === prevProps.isVisible) { return; diff --git a/src/containers/costume-library.jsx b/src/containers/costume-library.jsx index 2e631bd95..afdd56a5d 100644 --- a/src/containers/costume-library.jsx +++ b/src/containers/costume-library.jsx @@ -23,9 +23,7 @@ class CostumeLibrary extends React.PureComponent { bitmapResolution: item.info.length > 2 ? item.info[2] : 1, skinId: null }; - this.props.vm.addCostume(item.md5, vmCostume).then(() => { - this.props.onNewCostume(); - }); + this.props.vm.addCostume(item.md5, vmCostume); analytics.event({ category: 'library', action: 'Select Costume', @@ -45,7 +43,6 @@ class CostumeLibrary extends React.PureComponent { } CostumeLibrary.propTypes = { - onNewCostume: PropTypes.func.isRequired, onRequestClose: PropTypes.func, vm: PropTypes.instanceOf(VM).isRequired }; diff --git a/src/containers/costume-tab.jsx b/src/containers/costume-tab.jsx index 2e897c451..37fd8a12e 100644 --- a/src/containers/costume-tab.jsx +++ b/src/containers/costume-tab.jsx @@ -7,10 +7,12 @@ import VM from 'scratch-vm'; import AssetPanel from '../components/asset-panel/asset-panel.jsx'; import PaintEditorWrapper from './paint-editor-wrapper.jsx'; import CostumeLibrary from './costume-library.jsx'; +import BackdropLibrary from './backdrop-library.jsx'; import {connect} from 'react-redux'; import { closeCostumeLibrary, + closeBackdropLibrary, openCostumeLibrary, openBackdropLibrary } from '../reducers/modals'; @@ -65,7 +67,6 @@ class CostumeTab extends React.Component { 'handleSelectCostume', 'handleDeleteCostume', 'handleDuplicateCostume', - 'handleNewCostume', 'handleNewBlankCostume', 'handleSurpriseCostume', 'handleSurpriseBackdrop' @@ -90,8 +91,24 @@ class CostumeTab extends React.Component { } = nextProps; const target = editingTarget && sprites[editingTarget] ? sprites[editingTarget] : stage; - if (target && target.costumes && this.state.selectedCostumeIndex > target.costumes.length - 1) { - this.setState({selectedCostumeIndex: target.costumes.length - 1}); + if (!target || !target.costumes) { + return; + } + + if (this.props.editingTarget === editingTarget) { + // If costumes have been added or removed, change costumes to the editing target's + // current costume. + const oldTarget = this.props.sprites[editingTarget] ? + this.props.sprites[editingTarget] : this.props.stage; + // @todo: Find and switch to the index of the costume that is new. This is blocked by + // https://github.com/LLK/scratch-vm/issues/967 + // Right now, you can land on the wrong costume if a costume changing script is running. + if (oldTarget.costumeCount !== target.costumeCount) { + this.setState({selectedCostumeIndex: target.currentCostume}); + } + } else { + // If switching editing targets, update the costume index + this.setState({selectedCostumeIndex: target.currentCostume}); } } handleSelectCostume (costumeIndex) { @@ -102,14 +119,7 @@ class CostumeTab extends React.Component { this.props.vm.deleteCostume(costumeIndex); } handleDuplicateCostume (costumeIndex) { - this.props.vm.duplicateCostume(costumeIndex).then(() => { - this.setState({selectedCostumeIndex: costumeIndex + 1}); - }); - } - handleNewCostume () { - if (!this.props.vm.editingTarget) return; - const costumes = this.props.vm.editingTarget.getCostumes() || []; - this.setState({selectedCostumeIndex: Math.max(costumes.length - 1, 0)}); + this.props.vm.duplicateCostume(costumeIndex); } handleNewBlankCostume () { const emptyItem = costumeLibraryContent.find(item => ( @@ -124,9 +134,7 @@ class CostumeTab extends React.Component { skinId: null }; - this.props.vm.addCostume(emptyItem.md5, vmCostume).then(() => { - this.handleNewCostume(); - }); + this.props.vm.addCostume(emptyItem.md5, vmCostume); } handleSurpriseCostume () { const item = costumeLibraryContent[Math.floor(Math.random() * costumeLibraryContent.length)]; @@ -137,9 +145,7 @@ class CostumeTab extends React.Component { bitmapResolution: item.info.length > 2 ? item.info[2] : 1, skinId: null }; - this.props.vm.addCostume(item.md5, vmCostume).then(() => { - this.handleNewCostume(); - }); + this.props.vm.addCostume(item.md5, vmCostume); } handleSurpriseBackdrop () { const item = backdropLibraryContent[Math.floor(Math.random() * backdropLibraryContent.length)]; @@ -150,27 +156,22 @@ class CostumeTab extends React.Component { bitmapResolution: item.info.length > 2 ? item.info[2] : 1, skinId: null }; - this.props.vm.addCostume(item.md5, vmCostume).then(() => { - this.handleNewCostume(); - }); + this.props.vm.addCostume(item.md5, vmCostume); } render () { - // For paint wrapper const { intl, onNewLibraryBackdropClick, onNewLibraryCostumeClick, + backdropLibraryVisible, costumeLibraryVisible, + onRequestCloseBackdropLibrary, onRequestCloseCostumeLibrary, - ...props - } = this.props; - - const { editingTarget, sprites, stage, vm - } = props; + } = this.props; const target = editingTarget && sprites[editingTarget] ? sprites[editingTarget] : stage; @@ -218,7 +219,6 @@ class CostumeTab extends React.Component { > {target.costumes ? : null @@ -226,10 +226,15 @@ class CostumeTab extends React.Component { {costumeLibraryVisible ? ( ) : null} + {backdropLibraryVisible ? ( + + ) : null} ); } @@ -278,6 +283,9 @@ const mapDispatchToProps = dispatch => ({ e.preventDefault(); dispatch(openCostumeLibrary()); }, + onRequestCloseBackdropLibrary: () => { + dispatch(closeBackdropLibrary()); + }, onRequestCloseCostumeLibrary: () => { dispatch(closeCostumeLibrary()); } diff --git a/src/containers/error-boundary.jsx b/src/containers/error-boundary.jsx index d7f558c16..4c473a737 100644 --- a/src/containers/error-boundary.jsx +++ b/src/containers/error-boundary.jsx @@ -30,12 +30,17 @@ class ErrorBoundary extends React.Component { } handleReload () { - window.location.replace(window.location.origin); + window.location.replace(window.location.origin + window.location.pathname); } render () { if (this.state.hasError) { - if (platform.name === 'IE') { + // don't use array.includes because that's something that causes IE to crash. + if ( + platform.name === 'IE' || + platform.name === 'Opera' || + platform.name === 'Opera Mini' || + platform.name === 'Silk') { return ; } return ; diff --git a/src/containers/gui.jsx b/src/containers/gui.jsx index 320eedcdb..85b3a7acd 100644 --- a/src/containers/gui.jsx +++ b/src/containers/gui.jsx @@ -20,25 +20,39 @@ class GUI extends React.Component { constructor (props) { super(props); this.state = { - loading: true + loading: true, + loadingError: false, + errorMessage: '' }; } componentDidMount () { this.audioEngine = new AudioEngine(); this.props.vm.attachAudioEngine(this.audioEngine); - this.props.vm.loadProject(this.props.projectData).then(() => { - this.setState({loading: false}, () => { - this.props.vm.setCompatibilityMode(true); - this.props.vm.start(); + this.props.vm.loadProject(this.props.projectData) + .then(() => { + this.setState({loading: false}, () => { + this.props.vm.setCompatibilityMode(true); + this.props.vm.start(); + }); + }) + .catch(e => { + // Need to catch this error and update component state so that + // error page gets rendered if project failed to load + this.setState({loadingError: true, errorMessage: e}); }); - }); } componentWillReceiveProps (nextProps) { if (this.props.projectData !== nextProps.projectData) { this.setState({loading: true}, () => { - this.props.vm.loadProject(nextProps.projectData).then(() => { - this.setState({loading: false}); - }); + this.props.vm.loadProject(nextProps.projectData) + .then(() => { + this.setState({loading: false}); + }) + .catch(e => { + // Need to catch this error and update component state so that + // error page gets rendered if project failed to load + this.setState({loadingError: true, errorMessage: e}); + }); }); } } @@ -46,16 +60,18 @@ class GUI extends React.Component { this.props.vm.stopAll(); } render () { + if (this.state.loadingError) throw new Error(`Failed to load project: ${this.state.errorMessage}`); const { children, fetchingProject, + loadingStateVisible, projectData, // eslint-disable-line no-unused-vars vm, ...componentProps } = this.props; return ( @@ -70,6 +86,7 @@ GUI.propTypes = { feedbackFormVisible: PropTypes.bool, fetchingProject: PropTypes.bool, importInfoVisible: PropTypes.bool, + loadingStateVisible: PropTypes.bool, previewInfoVisible: PropTypes.bool, projectData: PropTypes.string, vm: PropTypes.instanceOf(VM) @@ -83,6 +100,7 @@ const mapStateToProps = state => ({ costumesTabVisible: state.editorTab.activeTabIndex === COSTUMES_TAB_INDEX, feedbackFormVisible: state.modals.feedbackForm, importInfoVisible: state.modals.importInfo, + loadingStateVisible: state.modals.loadingProject, previewInfoVisible: state.modals.previewInfo, soundsTabVisible: state.editorTab.activeTabIndex === SOUNDS_TAB_INDEX }); diff --git a/src/containers/import-modal.jsx b/src/containers/import-modal.jsx index 1c2ddbefb..0731098f1 100644 --- a/src/containers/import-modal.jsx +++ b/src/containers/import-modal.jsx @@ -51,7 +51,7 @@ class ImportModal extends React.Component { this.setState({inputValue: e.target.value, hasValidationError: false}); } validate (input) { - const urlMatches = input.match(/^(?:https:\/\/)?scratch\.mit\.edu\/projects\/(\d+)/); + const urlMatches = input.match(/^(?:https?:\/\/)?scratch\.mit\.edu\/projects\/(\d+)/); if (urlMatches && urlMatches.length > 0) { return urlMatches[1]; } diff --git a/src/containers/load-button.jsx b/src/containers/load-button.jsx index a7b78a638..e52a6735d 100644 --- a/src/containers/load-button.jsx +++ b/src/containers/load-button.jsx @@ -5,6 +5,11 @@ import {connect} from 'react-redux'; import LoadButtonComponent from '../components/load-button/load-button.jsx'; +import { + openLoadingProject, + closeLoadingProject +} from '../reducers/modals'; + class LoadButton extends React.Component { constructor (props) { super(props); @@ -13,11 +18,24 @@ class LoadButton extends React.Component { 'handleChange', 'handleClick' ]); + this.state = { + loadingError: false, + errorMessage: '' + }; } handleChange (e) { + this.props.openLoadingState(); + // Remove the hash if any (without triggering a hash change event or a reload) + history.replaceState({}, document.title, '.'); const reader = new FileReader(); - reader.onload = () => this.props.loadProject(reader.result); - reader.readAsText(e.target.files[0]); + reader.onload = () => this.props.vm.loadProject(reader.result) + .then(() => { + this.props.closeLoadingState(); + }) + .catch(error => { + this.setState({loadingError: true, errorMessage: error}); + }); + reader.readAsArrayBuffer(e.target.files[0]); } handleClick () { this.fileInput.click(); @@ -26,8 +44,11 @@ class LoadButton extends React.Component { this.fileInput = input; } render () { + if (this.state.loadingError) throw new Error(`Failed to load project: ${this.state.errorMessage}`); const { - loadProject, // eslint-disable-line no-unused-vars + closeLoadingState, // eslint-disable-line no-unused-vars + openLoadingState, // eslint-disable-line no-unused-vars + vm, // eslint-disable-line no-unused-vars ...props } = this.props; return ( @@ -42,14 +63,23 @@ class LoadButton extends React.Component { } LoadButton.propTypes = { - loadProject: PropTypes.func.isRequired + closeLoadingState: PropTypes.func, + openLoadingState: PropTypes.func, + vm: PropTypes.shape({ + loadProject: PropTypes.func + }) }; const mapStateToProps = state => ({ - loadProject: state.vm.fromJSON.bind(state.vm) + vm: state.vm +}); + +const mapDispatchToProps = dispatch => ({ + closeLoadingState: () => dispatch(closeLoadingProject()), + openLoadingState: () => dispatch(openLoadingProject()) }); export default connect( mapStateToProps, - () => ({}) // omit dispatch prop + mapDispatchToProps )(LoadButton); diff --git a/src/containers/paint-editor-wrapper.jsx b/src/containers/paint-editor-wrapper.jsx index 38cb3aa4a..52bb5708f 100644 --- a/src/containers/paint-editor-wrapper.jsx +++ b/src/containers/paint-editor-wrapper.jsx @@ -59,7 +59,8 @@ const mapStateToProps = (state, {selectedCostumeIndex}) => { name: costume && costume.name, rotationCenterX: costume && costume.rotationCenterX, rotationCenterY: costume && costume.rotationCenterY, - svgId: editingTarget && `${editingTarget}${costume.skinId}` + svgId: editingTarget && `${editingTarget}${costume.skinId}`, + vm: state.vm }; }; diff --git a/src/containers/preview-modal.jsx b/src/containers/preview-modal.jsx index 1209f251f..3fa6c8f03 100644 --- a/src/containers/preview-modal.jsx +++ b/src/containers/preview-modal.jsx @@ -36,10 +36,7 @@ class PreviewModal extends React.Component { this.props.onViewProject(); } supportedBrowser () { - if (platform.name === 'IE') { - return false; - } - return true; + return !['IE', 'Opera', 'Opera Mini', 'Silk', 'Vivaldi'].includes(platform.name); } render () { return (this.supportedBrowser() ? diff --git a/src/containers/record-modal.jsx b/src/containers/record-modal.jsx index 8e4b9d249..9c4311539 100644 --- a/src/containers/record-modal.jsx +++ b/src/containers/record-modal.jsx @@ -73,22 +73,24 @@ class RecordModal extends React.Component { sampleRate: this.state.sampleRate, channelData: [clippedSamples] }).then(wavBuffer => { - const md5 = String(Math.floor(100000 * Math.random())); const vmSound = { format: '', - md5: `${md5}.wav`, + dataFormat: 'wav', name: `recording ${this.props.vm.editingTarget.sprite.sounds.length}` }; - // Load the encoded .wav into the storage cache + // Load the encoded .wav into the storage cache and get resulting + // md5 from storage const storage = this.props.vm.runtime.storage; - storage.builtinHelper.cache( + const md5 = storage.builtinHelper.cache( storage.AssetType.Sound, storage.DataFormat.WAV, new Uint8Array(wavBuffer), - md5 ); + // update vmSound object with md5 property + vmSound.md5 = `${md5}.${vmSound.dataFormat}`; + this.props.vm.addSound(vmSound).then(() => { this.props.onClose(); this.props.onNewSound(); diff --git a/src/containers/save-button.jsx b/src/containers/save-button.jsx index f2ae497de..fc8bcf40a 100644 --- a/src/containers/save-button.jsx +++ b/src/containers/save-button.jsx @@ -15,28 +15,28 @@ class SaveButton extends React.Component { ]); } handleClick () { - const json = this.props.saveProjectSb3(); - - // Download project data into a file - create link element, - // simulate click on it, and then remove it. const saveLink = document.createElement('a'); document.body.appendChild(saveLink); - const data = new Blob([json], {type: 'text'}); - const url = window.URL.createObjectURL(data); - saveLink.href = url; + this.props.vm.saveProjectSb3().then(content => { + const url = window.URL.createObjectURL(content); - // File name: project-DATE-TIME - const date = new Date(); - const timestamp = `${date.toLocaleDateString()}-${date.toLocaleTimeString()}`; - saveLink.download = `project-${timestamp}.json`; - saveLink.click(); - window.URL.revokeObjectURL(url); - document.body.removeChild(saveLink); + saveLink.href = url; + + // TODO user-friendly project name + // File name: project-DATE-TIME + const date = new Date(); + const timestamp = `${date.toLocaleDateString()}-${date.toLocaleTimeString()}`; + // TODO change extension to sb3 + saveLink.download = `untitled-project-${timestamp}.sb3`; + saveLink.click(); + window.URL.revokeObjectURL(url); + document.body.removeChild(saveLink); + }); } render () { const { - saveProjectSb3, // eslint-disable-line no-unused-vars + vm, // eslint-disable-line no-unused-vars ...props } = this.props; return ( @@ -57,11 +57,13 @@ class SaveButton extends React.Component { } SaveButton.propTypes = { - saveProjectSb3: PropTypes.func.isRequired + vm: PropTypes.shape({ + saveProjectSb3: PropTypes.func + }) }; const mapStateToProps = state => ({ - saveProjectSb3: state.vm.saveProjectSb3.bind(state.vm) + vm: state.vm }); export default connect( diff --git a/src/containers/sound-editor.jsx b/src/containers/sound-editor.jsx index bbe032c0b..de10ebc2c 100644 --- a/src/containers/sound-editor.jsx +++ b/src/containers/sound-editor.jsx @@ -89,7 +89,7 @@ class SoundEditor extends React.Component { } this.resetState(samples, sampleRate); - this.props.onUpdateSoundBuffer( + this.props.vm.updateSoundBuffer( this.props.soundIndex, this.audioBufferPlayer.buffer, wavBuffer ? new Uint8Array(wavBuffer) : new Uint8Array()); @@ -112,7 +112,7 @@ class SoundEditor extends React.Component { this.setState({playhead}); } handleChangeName (name) { - this.props.onRenameSound(this.props.soundIndex, name); + this.props.vm.renameSound(this.props.soundIndex, name); } handleActivateTrim () { if (this.state.trimStart === null && this.state.trimEnd === null) { @@ -200,12 +200,14 @@ class SoundEditor extends React.Component { SoundEditor.propTypes = { name: PropTypes.string.isRequired, - onRenameSound: PropTypes.func.isRequired, - onUpdateSoundBuffer: PropTypes.func.isRequired, sampleRate: PropTypes.number, samples: PropTypes.instanceOf(Float32Array), soundId: PropTypes.string, - soundIndex: PropTypes.number + soundIndex: PropTypes.number, + vm: PropTypes.shape({ + updateSoundBuffer: PropTypes.func, + renameSound: PropTypes.func + }) }; const mapStateToProps = (state, {soundIndex}) => { @@ -219,8 +221,7 @@ const mapStateToProps = (state, {soundIndex}) => { sampleRate: audioBuffer.sampleRate, samples: audioBuffer.getChannelData(0), name: sound.name, - onRenameSound: state.vm.renameSound.bind(state.vm), - onUpdateSoundBuffer: state.vm.updateSoundBuffer.bind(state.vm) + vm: state.vm }; }; diff --git a/src/containers/sound-tab.jsx b/src/containers/sound-tab.jsx index 471ebe99e..b0b4e3d22 100644 --- a/src/containers/sound-tab.jsx +++ b/src/containers/sound-tab.jsx @@ -46,8 +46,14 @@ class SoundTab extends React.Component { } = nextProps; const target = editingTarget && sprites[editingTarget] ? sprites[editingTarget] : stage; + if (!target || !target.sounds) { + return; + } - if (target && target.sounds && this.state.selectedSoundIndex > target.sounds.length - 1) { + // If switching editing targets, reset the sound index + if (this.props.editingTarget !== editingTarget) { + this.setState({selectedSoundIndex: 0}); + } else if (this.state.selectedSoundIndex > target.sounds.length - 1) { this.setState({selectedSoundIndex: Math.max(target.sounds.length - 1, 0)}); } } diff --git a/src/containers/stage-selector.jsx b/src/containers/stage-selector.jsx index 27075cef8..bf749c12a 100644 --- a/src/containers/stage-selector.jsx +++ b/src/containers/stage-selector.jsx @@ -38,7 +38,9 @@ class StageSelector extends React.Component { handleSurpriseBackdrop () { // @todo should this not add a backdrop you already have? const item = backdropLibraryContent[Math.floor(Math.random() * backdropLibraryContent.length)]; - this.addBackdropFromLibraryItem(item); + this.addBackdropFromLibraryItem(item).then(() => { + this.props.onActivateTab(COSTUMES_TAB_INDEX); + }); } handleEmptyBackdrop () { // @todo this is brittle, will need to be refactored for localized libraries @@ -83,6 +85,7 @@ const mapStateToProps = (state, {assetId}) => ({ const mapDispatchToProps = dispatch => ({ onNewBackdropClick: e => { e.preventDefault(); + dispatch(activateTab(COSTUMES_TAB_INDEX)); dispatch(openBackdropLibrary()); }, onActivateTab: tabIndex => { diff --git a/src/containers/stage.jsx b/src/containers/stage.jsx index c386112b3..cf10d5001 100644 --- a/src/containers/stage.jsx +++ b/src/containers/stage.jsx @@ -186,18 +186,18 @@ class Stage extends React.Component { mouseDown: false, mouseDownPosition: null }); + const data = { + isDown: false, + x: x - this.rect.left, + y: y - this.rect.top, + canvasWidth: this.rect.width, + canvasHeight: this.rect.height, + wasDragged: this.state.isDragging + }; if (this.state.isDragging) { this.onStopDrag(); - } else { - const data = { - isDown: false, - x: x - this.rect.left, - y: y - this.rect.top, - canvasWidth: this.rect.width, - canvasHeight: this.rect.height - }; - this.props.vm.postIOData('mouse', data); } + this.props.vm.postIOData('mouse', data); } onMouseDown (e) { this.updateRect(); diff --git a/src/containers/target-pane.jsx b/src/containers/target-pane.jsx index 889d84185..97d6dffaa 100644 --- a/src/containers/target-pane.jsx +++ b/src/containers/target-pane.jsx @@ -5,7 +5,6 @@ import {connect} from 'react-redux'; import { openSpriteLibrary, - closeBackdropLibrary, closeSpriteLibrary } from '../reducers/modals'; @@ -75,7 +74,9 @@ class TargetPane extends React.Component { const emptyItem = spriteLibraryContent.find(item => item.name === 'Empty'); if (emptyItem) { this.props.vm.addSprite2(JSON.stringify(emptyItem.json)).then(() => { - this.props.onActivateTab(COSTUMES_TAB_INDEX); + setTimeout(() => { // Wait for targets update to propagate before tab switching + this.props.onActivateTab(COSTUMES_TAB_INDEX); + }); }); } } @@ -88,6 +89,7 @@ class TargetPane extends React.Component { render () { const { onActivateTab, // eslint-disable-line no-unused-vars + onReceivedBlocks, // eslint-disable-line no-unused-vars ...componentProps } = this.props; return ( @@ -132,8 +134,7 @@ const mapStateToProps = state => ({ }, {}), stage: state.targets.stage, raiseSprites: state.blockDrag, - spriteLibraryVisible: state.modals.spriteLibrary, - backdropLibraryVisible: state.modals.backdropLibrary + spriteLibraryVisible: state.modals.spriteLibrary }); const mapDispatchToProps = dispatch => ({ onNewSpriteClick: e => { @@ -143,9 +144,6 @@ const mapDispatchToProps = dispatch => ({ onRequestCloseSpriteLibrary: () => { dispatch(closeSpriteLibrary()); }, - onRequestCloseBackdropLibrary: () => { - dispatch(closeBackdropLibrary()); - }, onActivateTab: tabIndex => { dispatch(activateTab(tabIndex)); }, diff --git a/src/css/colors.css b/src/css/colors.css index be8a9a0db..b46bc50bf 100644 --- a/src/css/colors.css +++ b/src/css/colors.css @@ -4,6 +4,8 @@ $ui-background-blue: #e8edf1; $text-primary: #575e75; +$menubar-gray: rgba(0,0,0,0.25); + $motion-primary: #4C97FF; $motion-tertiary: #3373CC; $motion-transparent: hsla(215, 100%, 65%, 0.20); @@ -11,6 +13,8 @@ $motion-transparent: hsla(215, 100%, 65%, 0.20); $red-primary: #FF661A; $red-tertiary: #E64D00; +$share-orange: #FFAB19; + $sound-primary: #CF63CF; $sound-tertiary: #A63FA6; diff --git a/src/lib/default-project/project.json b/src/lib/default-project/project.json index c1ae335d8..fc3e21a9c 100755 --- a/src/lib/default-project/project.json +++ b/src/lib/default-project/project.json @@ -1 +1,128 @@ -{"targets":[{"id":"`jEk@4|i[#Fk?(8x)AV.","name":"Stage","isStage":true,"x":0,"y":0,"size":100,"direction":90,"draggable":false,"currentCostume":0,"costume":{"name":"backdrop1","bitmapResolution":1,"rotationCenterX":240,"rotationCenterY":180,"skinId":2,"dataFormat":"png","assetId":"739b5e2a2435f6e1ec2993791b423146"},"costumeCount":1,"visible":true,"rotationStyle":"all around","blocks":{},"variables":{"`jEk@4|i[#Fk?(8x)AV.-my variable":{"id":"`jEk@4|i[#Fk?(8x)AV.-my variable","name":"my variable","type":"","isCloud":false,"value":0}},"lists":{},"costumes":[{"name":"backdrop1","bitmapResolution":1,"rotationCenterX":240,"rotationCenterY":180,"skinId":2,"dataFormat":"png","assetId":"739b5e2a2435f6e1ec2993791b423146"}],"sounds":[{"name":"pop","format":"","rate":11025,"sampleCount":258,"soundID":1,"md5":"83a9787d4cb6f3b7632b4ddfebf74367.wav","data":null,"dataFormat":"wav","assetId":"83a9787d4cb6f3b7632b4ddfebf74367","soundId":"p=i?*Zt*I]@]x_*V`mut"}]},{"id":"9xJ@2eKXvx:/*Q^3Rib#","name":"Sprite1","isStage":false,"x":0,"y":0,"size":100,"direction":90,"draggable":false,"currentCostume":0,"costume":{"name":"costume1","bitmapResolution":1,"rotationCenterX":47,"rotationCenterY":55,"skinId":0,"dataFormat":"svg","assetId":"09dc888b0b7df19f70d81588ae73420e"},"costumeCount":2,"visible":true,"rotationStyle":"all around","blocks":{},"variables":{},"lists":{},"costumes":[{"name":"costume1","bitmapResolution":1,"rotationCenterX":47,"rotationCenterY":55,"skinId":0,"dataFormat":"svg","assetId":"09dc888b0b7df19f70d81588ae73420e"},{"name":"costume2","bitmapResolution":1,"rotationCenterX":47,"rotationCenterY":55,"skinId":1,"dataFormat":"svg","assetId":"3696356a03a8d938318876a593572843"}],"sounds":[{"name":"Meow","format":"","rate":22050,"sampleCount":18688,"soundID":0,"md5":"83c36d806dc92327b9e7049a565c6bff.wav","data":null,"dataFormat":"wav","assetId":"83c36d806dc92327b9e7049a565c6bff","soundId":"]z6@jLeJ2W%gr/eA1HB+"}]}],"meta":{"semver":"3.0.0","vm":"0.1.0","agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36"}} \ No newline at end of file +{ + "targets": [ + { + "id": "`jEk@4|i[#Fk?(8x)AV.", + "name": "Stage", + "isStage": true, + "x": 0, + "y": 0, + "size": 100, + "direction": 90, + "draggable": false, + "currentCostume": 0, + "costume": { + "name": "backdrop1", + "bitmapResolution": 1, + "rotationCenterX": 240, + "rotationCenterY": 180, + "skinId": 2, + "dataFormat": "png", + "assetId": "739b5e2a2435f6e1ec2993791b423146" + }, + "costumeCount": 1, + "visible": true, + "rotationStyle": "all around", + "blocks": {}, + "variables": { + "`jEk@4|i[#Fk?(8x)AV.-my variable": { + "id": "`jEk@4|i[#Fk?(8x)AV.-my variable", + "name": "my variable", + "type": "", + "isCloud": false, + "value": 0 + } + }, + "lists": {}, + "costumes": [ + { + "name": "backdrop1", + "bitmapResolution": 1, + "rotationCenterX": 240, + "rotationCenterY": 180, + "skinId": 2, + "dataFormat": "png", + "assetId": "739b5e2a2435f6e1ec2993791b423146" + } + ], + "sounds": [ + { + "name": "pop", + "format": "", + "rate": 11025, + "sampleCount": 258, + "soundID": 1, + "md5ext": "83a9787d4cb6f3b7632b4ddfebf74367.wav", + "data": null, + "dataFormat": "wav", + "assetId": "83a9787d4cb6f3b7632b4ddfebf74367", + "soundId": "p=i?*Zt*I]@]x_*V`mut" + } + ] + }, + { + "id": "9xJ@2eKXvx:/*Q^3Rib#", + "name": "Sprite1", + "isStage": false, + "x": 0, + "y": 0, + "size": 100, + "direction": 90, + "draggable": false, + "currentCostume": 0, + "costume": { + "name": "costume1", + "bitmapResolution": 1, + "rotationCenterX": 47, + "rotationCenterY": 55, + "skinId": 0, + "dataFormat": "svg", + "assetId": "09dc888b0b7df19f70d81588ae73420e" + }, + "costumeCount": 2, + "visible": true, + "rotationStyle": "all around", + "blocks": {}, + "variables": {}, + "lists": {}, + "costumes": [ + { + "name": "costume1", + "bitmapResolution": 1, + "rotationCenterX": 47, + "rotationCenterY": 55, + "skinId": 0, + "dataFormat": "svg", + "assetId": "09dc888b0b7df19f70d81588ae73420e" + }, + { + "name": "costume2", + "bitmapResolution": 1, + "rotationCenterX": 47, + "rotationCenterY": 55, + "skinId": 1, + "dataFormat": "svg", + "assetId": "3696356a03a8d938318876a593572843" + } + ], + "sounds": [ + { + "name": "Meow", + "format": "", + "rate": 22050, + "sampleCount": 18688, + "soundID": 0, + "md5ext": "83c36d806dc92327b9e7049a565c6bff.wav", + "data": null, + "dataFormat": "wav", + "assetId": "83c36d806dc92327b9e7049a565c6bff", + "soundId": "]z6@jLeJ2W%gr/eA1HB+" + } + ] + } + ], + "meta": { + "semver": "3.0.0", + "vm": "0.1.0", + "agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36" + } +} diff --git a/src/lib/libraries/costumes.json b/src/lib/libraries/costumes.json index 30c305182..1ac8e24d9 100644 --- a/src/lib/libraries/costumes.json +++ b/src/lib/libraries/costumes.json @@ -615,6 +615,17 @@ 1 ] }, + { + "name": "Cat1", + "md5": "09dc888b0b7df19f70d81588ae73420e.svg", + "type": "costume", + "tags": [], + "info": [ + 47, + 55, + 1 + ] + }, { "name": "Cat1 Flying-a", "md5": "1e81725d2d2c7de4a2dd4a145198a43c.svg", @@ -637,6 +648,17 @@ 1 ] }, + { + "name": "Cat2", + "md5": "3696356a03a8d938318876a593572843.svg", + "type": "costume", + "tags": [], + "info": [ + 47, + 55, + 1 + ] + }, { "name": "Cloud-a", "md5": "c7d7de8e29179407f03b054fa640f4d0.svg", @@ -681,28 +703,6 @@ 1 ] }, - { - "name": "Costume1", - "md5": "09dc888b0b7df19f70d81588ae73420e.svg", - "type": "costume", - "tags": [], - "info": [ - 47, - 55, - 1 - ] - }, - { - "name": "Costume2", - "md5": "3696356a03a8d938318876a593572843.svg", - "type": "costume", - "tags": [], - "info": [ - 47, - 55, - 1 - ] - }, { "name": "Crab-a", "md5": "114249a5660f7948663d95de575cfd8d.svg", @@ -1132,17 +1132,6 @@ 1 ] }, - { - "name": "Drum-c", - "md5": "50cf928f06481ff823e18e70c01df807.svg", - "type": "costume", - "tags": [], - "info": [ - 43, - 60, - 1 - ] - }, { "name": "Drum-cymbal-a", "md5": "d6d41862fda966df1455d2dbff5e1988.svg", @@ -1165,17 +1154,6 @@ 1 ] }, - { - "name": "Drum-cymbal-c", - "md5": "0e85f690e8eaf153ae20cbbbb88ca60c.svg", - "type": "costume", - "tags": [], - "info": [ - 30, - 74, - 1 - ] - }, { "name": "Drum-highhat-a", "md5": "81fb79151a63cb096258607451cc2cf5.svg", @@ -1198,17 +1176,6 @@ 1 ] }, - { - "name": "Drum-highhat-c", - "md5": "c6f2467a5226f7d372d15669c5099a41.svg", - "type": "costume", - "tags": [], - "info": [ - 33, - 73, - 1 - ] - }, { "name": "Drum-kit", "md5": "131d040d86ecea62ccd175a8709c7866.svg", @@ -1231,25 +1198,14 @@ 1 ] }, - { - "name": "Drum-kit-c", - "md5": "4ca5c6eee4d907b98fb62340bd4a2cde.svg", - "type": "costume", - "tags": [], - "info": [ - 58, - 78, - 1 - ] - }, { "name": "Drum-snare-a", - "md5": "31a81bb560abf30cbc44bcdd581d5ccc.svg", + "md5": "b0255be93e3c8be6ac687f4199a25c4b.svg", "type": "costume", "tags": [], "info": [ - 40, - 69, + 23, + 50, 1 ] }, @@ -1264,17 +1220,6 @@ 1 ] }, - { - "name": "Drum-snare-c", - "md5": "878ea646cbbd2a9e24fdeb8d98c3e24f.svg", - "type": "costume", - "tags": [], - "info": [ - 40, - 69, - 1 - ] - }, { "name": "Duck", "md5": "c3baf7eedfbdac8cd1e4f1f1f779dc0c.svg", @@ -1649,17 +1594,6 @@ 1 ] }, - { - "name": "Guitar-c", - "md5": "5cfe9faacb98d35a702cfa633967a5b5.svg", - "type": "costume", - "tags": [], - "info": [ - 47, - 83, - 1 - ] - }, { "name": "Guitar-electric1-a", "md5": "b2b469b9d11fd23bdd671eab94dc58ff.svg", @@ -1682,17 +1616,6 @@ 1 ] }, - { - "name": "Guitar-electric1-c", - "md5": "4cb069d2a290c0a153f196de725d357c.svg", - "type": "costume", - "tags": [], - "info": [ - 42, - 85, - 1 - ] - }, { "name": "Guitar-electric2-a", "md5": "1fc433b89038f9e16092c9f4d7514cca.svg", @@ -1715,17 +1638,6 @@ 1 ] }, - { - "name": "Guitar-electric2-c", - "md5": "7f5f8b6d772657566e3dee24c9a9c538.svg", - "type": "costume", - "tags": [], - "info": [ - 38, - 94, - 1 - ] - }, { "name": "Hedgehog-a", "md5": "32416e6b2ef8e45fb5fd10778c1b9a9f.svg", @@ -1913,61 +1825,6 @@ 1 ] }, - { - "name": "Jez-b", - "md5": "f1e74f3c02333e9e2068e8baf4e77aa0.svg", - "type": "costume", - "tags": [], - "info": [ - 67, - 95, - 1 - ] - }, - { - "name": "Jez-c", - "md5": "e2482cf509c312935f08be0e2e2c9d84.svg", - "type": "costume", - "tags": [], - "info": [ - 67, - 95, - 1 - ] - }, - { - "name": "Jez-d", - "md5": "569e736b519199efddfbae2572f7e92b.svg", - "type": "costume", - "tags": [], - "info": [ - 67, - 95, - 1 - ] - }, - { - "name": "Jez-e", - "md5": "2261bed0f2cc819def17969158297b4f.svg", - "type": "costume", - "tags": [], - "info": [ - 77, - 95, - 1 - ] - }, - { - "name": "Jez-f", - "md5": "d7f44adb3dc7906b9dfb3599a028e0d6.svg", - "type": "costume", - "tags": [], - "info": [ - 62, - 94, - 1 - ] - }, { "name": "Jordyn-a", "md5": "8dd2a2abbb8e639da8576b6e72ef9e59.svg", @@ -2034,17 +1891,6 @@ 1 ] }, - { - "name": "Keyboard-c", - "md5": "4ee5e7c6d0463d9b50e0c593e70e1e31.svg", - "type": "costume", - "tags": [], - "info": [ - 72, - 68, - 1 - ] - }, { "name": "Kiran-a", "md5": "9de23c4a7a7fbb67136b539241346854.svg", @@ -2056,6 +1902,61 @@ 1 ] }, + { + "name": "Kiran-b", + "md5": "f1e74f3c02333e9e2068e8baf4e77aa0.svg", + "type": "costume", + "tags": [], + "info": [ + 67, + 95, + 1 + ] + }, + { + "name": "Kiran-c", + "md5": "e2482cf509c312935f08be0e2e2c9d84.svg", + "type": "costume", + "tags": [], + "info": [ + 67, + 95, + 1 + ] + }, + { + "name": "Kiran-d", + "md5": "569e736b519199efddfbae2572f7e92b.svg", + "type": "costume", + "tags": [], + "info": [ + 67, + 95, + 1 + ] + }, + { + "name": "Kiran-e", + "md5": "2261bed0f2cc819def17969158297b4f.svg", + "type": "costume", + "tags": [], + "info": [ + 77, + 95, + 1 + ] + }, + { + "name": "Kiran-f", + "md5": "d7f44adb3dc7906b9dfb3599a028e0d6.svg", + "type": "costume", + "tags": [], + "info": [ + 62, + 94, + 1 + ] + }, { "name": "Knight", "md5": "f2c5e8bc24d001b81566879dbf2f1a13.svg", @@ -2199,17 +2100,6 @@ 1 ] }, - { - "name": "Microphone-c", - "md5": "021d6da78b7bda374da4bdb50d4cbc4f.svg", - "type": "costume", - "tags": [], - "info": [ - 40, - 88, - 1 - ] - }, { "name": "Milk-a", "md5": "e6a7964bc4ea38c79a5a31d6ddfb5ba9.svg", @@ -2541,7 +2431,7 @@ ] }, { - "name": "Penguin1", + "name": "Penguin1-a", "md5": "c17d9e4bdb59c574e0c34aa70af516da.svg", "type": "costume", "tags": [], @@ -2551,6 +2441,61 @@ 1 ] }, + { + "name": "Penguin1-b", + "md5": "35fec7aa5f60cca945fe0615413f1f08.svg", + "type": "costume", + "tags": [], + "info": [ + 48, + 62, + 1 + ] + }, + { + "name": "Penguin1-c", + "md5": "18fa51a64ebd5518f0c5c465525346e5.svg", + "type": "costume", + "tags": [], + "info": [ + 48, + 61, + 1 + ] + }, + { + "name": "Penguin2-a", + "md5": "eaaaa7068e78a51d73ba437f1ec5763e.svg", + "type": "costume", + "tags": [], + "info": [ + 49, + 79, + 1 + ] + }, + { + "name": "Penguin2-b", + "md5": "5a80f4b2fd20d43e4f7cb4189c08d99c.svg", + "type": "costume", + "tags": [], + "info": [ + 45, + 79, + 1 + ] + }, + { + "name": "Penguin2-c", + "md5": "394e79f5f9a462064ece2a9a6606a07d.svg", + "type": "costume", + "tags": [], + "info": [ + 50, + 78, + 1 + ] + }, { "name": "Pico-a", "md5": "0579fe60bb3717c49dfd7743caa84ada.svg", @@ -2926,13 +2871,35 @@ ] }, { - "name": "Saxophone-c", - "md5": "29751826f768bc1568092f267525ae8b.svg", + "name": "Shark-a ", + "md5": "7c0a907eae79462f69f8e2af8e7df828.svg", "type": "costume", "tags": [], "info": [ - 47, - 80, + 75, + 75, + 1 + ] + }, + { + "name": "Shark-b ", + "md5": "cff9ae87a93294693a0650b38a7a33d2.svg", + "type": "costume", + "tags": [], + "info": [ + 75, + 75, + 1 + ] + }, + { + "name": "Shark-c ", + "md5": "afeae3f998598424f7c50918507f6ce6.svg", + "type": "costume", + "tags": [], + "info": [ + 77, + 37, 1 ] }, @@ -3057,6 +3024,28 @@ 1 ] }, + { + "name": "Starfish-a", + "md5": "3d1101bbc24ae292a36356af325f660c.svg", + "type": "costume", + "tags": [], + "info": [ + 75, + 75, + 1 + ] + }, + { + "name": "Starfish-b ", + "md5": "ad8007f4e63693984d4adc466ffa3ad2.svg", + "type": "costume", + "tags": [], + "info": [ + 53, + 60, + 1 + ] + }, { "name": "Strawberry-a", "md5": "734556fb8e14740f2cbc971238b71d47.svg", @@ -3266,17 +3255,6 @@ 1 ] }, - { - "name": "Trumpet-c", - "md5": "680dd004c24690af1e95c4beb22cd615.svg", - "type": "costume", - "tags": [], - "info": [ - 37, - 73, - 1 - ] - }, { "name": "Unicorn", "md5": "a04def38351e7fd805226345cac4fbfe.svg", diff --git a/src/lib/libraries/sounds.json b/src/lib/libraries/sounds.json index 4f146abdc..aafa6f01e 100644 --- a/src/lib/libraries/sounds.json +++ b/src/lib/libraries/sounds.json @@ -470,10 +470,10 @@ }, { "name": "Cheer", - "md5": "4b36eebf22be4667fc2f15b78c805b4c.wav", - "sampleCount": 62528, + "md5": "170e05c29d50918ae0b482c2955768c0.wav", + "sampleCount": 108864, "rate": 22050, - "format": "" + "format": "adpcm" }, { "name": "Chirp", @@ -553,19 +553,12 @@ "format": "" }, { - "name": "Computer Beeps1", + "name": "Computer Beep2", "md5": "1da43f6d52d0615da8a250e28100a80d.wav", "sampleCount": 19200, "rate": 11025, "format": "" }, - { - "name": "Computer Beeps2", - "md5": "28c76b6bebd04be1383fe9ba4933d263.wav", - "sampleCount": 9536, - "rate": 11025, - "format": "" - }, { "name": "Connect", "md5": "9aad12085708ccd279297d4bea9c5ae0.wav", @@ -1842,10 +1835,10 @@ }, { "name": "Screech", - "md5": "3676f478e5e3496034cf100d239160df.wav", - "sampleCount": 28928, - "rate": 11025, - "format": "" + "md5": "10644c5cc83a9a2dd3ab466deb0eb03d.wav", + "sampleCount": 12907, + "rate": 22050, + "format": "adpcm" }, { "name": "Seagulls", @@ -2269,8 +2262,8 @@ }, { "name": "Water Drop", - "md5": "aa488de9e2c871e9d4faecd246ed737a.wav", - "sampleCount": 8136, + "md5": "e133e625fd367d269e76964d4b722fc2.wav", + "sampleCount": 15131, "rate": 22050, "format": "adpcm" }, diff --git a/src/lib/libraries/sprites.json b/src/lib/libraries/sprites.json index 349bc3064..2c12e02cd 100644 --- a/src/lib/libraries/sprites.json +++ b/src/lib/libraries/sprites.json @@ -62,7 +62,7 @@ "direction": 90, "rotationStyle": "normal", "isDraggable": false, - "indexInLibrary": 10, + "indexInLibrary": 11, "visible": true, "spriteInfo": {} } @@ -456,7 +456,7 @@ } }, { - "name": "Bat3", + "name": "Bat2", "md5": "647d4bd53163f94a7dabf623ccab7fd3.svg", "type": "sprite", "tags": [], @@ -466,7 +466,7 @@ 1 ], "json": { - "objName": "Bat3", + "objName": "Bat2", "sounds": [ { "soundName": "pop", @@ -502,7 +502,7 @@ "direction": 90, "rotationStyle": "normal", "isDraggable": false, - "indexInLibrary": 12, + "indexInLibrary": 10, "visible": true, "spriteInfo": {} } @@ -546,7 +546,7 @@ "direction": 90, "rotationStyle": "normal", "isDraggable": false, - "indexInLibrary": 11, + "indexInLibrary": 12, "visible": true, "spriteInfo": {} } @@ -1169,7 +1169,7 @@ "objName": "Cake", "sounds": [ { - "soundName": "birthday song", + "soundName": "birthday", "soundID": -1, "md5": "89691587a169d935a58c48c3d4e78534.wav", "sampleCount": 161408, @@ -1299,7 +1299,7 @@ ], "costumes": [ { - "costumeName": "costume1", + "costumeName": "cat1", "baseLayerID": -1, "baseLayerMD5": "09dc888b0b7df19f70d81588ae73420e.svg", "bitmapResolution": 1, @@ -1307,7 +1307,7 @@ "rotationCenterY": 55 }, { - "costumeName": "costume2", + "costumeName": "cat2", "baseLayerID": -1, "baseLayerMD5": "3696356a03a8d938318876a593572843.svg", "bitmapResolution": 1, @@ -1315,7 +1315,7 @@ "rotationCenterY": 55 } ], - "currentCostumeIndex": 0, + "currentCostumeIndex": 1, "scratchX": 0, "scratchY": 0, "scale": 1, @@ -2166,7 +2166,7 @@ "tags": [], "info": [ 0, - 3, + 2, 2 ], "json": { @@ -2205,14 +2205,6 @@ "bitmapResolution": 1, "rotationCenterX": 43, "rotationCenterY": 60 - }, - { - "costumeName": "drum-c", - "baseLayerID": -1, - "baseLayerMD5": "50cf928f06481ff823e18e70c01df807.svg", - "bitmapResolution": 1, - "rotationCenterX": 43, - "rotationCenterY": 60 } ], "currentCostumeIndex": 0, @@ -2234,7 +2226,7 @@ "tags": [], "info": [ 0, - 3, + 2, 5 ], "json": { @@ -2297,14 +2289,6 @@ "bitmapResolution": 1, "rotationCenterX": 58, "rotationCenterY": 78 - }, - { - "costumeName": "drum-kit-c", - "baseLayerID": -1, - "baseLayerMD5": "4ca5c6eee4d907b98fb62340bd4a2cde.svg", - "bitmapResolution": 1, - "rotationCenterX": 58, - "rotationCenterY": 78 } ], "currentCostumeIndex": 0, @@ -2326,7 +2310,7 @@ "tags": [], "info": [ 0, - 3, + 2, 4 ], "json": { @@ -2381,14 +2365,6 @@ "bitmapResolution": 1, "rotationCenterX": 30, "rotationCenterY": 74 - }, - { - "costumeName": "drum-cymbal-c", - "baseLayerID": -1, - "baseLayerMD5": "0e85f690e8eaf153ae20cbbbb88ca60c.svg", - "bitmapResolution": 1, - "rotationCenterX": 30, - "rotationCenterY": 74 } ], "currentCostumeIndex": 0, @@ -2410,7 +2386,7 @@ "tags": [], "info": [ 0, - 3, + 2, 1 ], "json": { @@ -2441,14 +2417,6 @@ "bitmapResolution": 1, "rotationCenterX": 33, "rotationCenterY": 73 - }, - { - "costumeName": "drum-highhat-c", - "baseLayerID": -1, - "baseLayerMD5": "c6f2467a5226f7d372d15669c5099a41.svg", - "bitmapResolution": 1, - "rotationCenterX": 33, - "rotationCenterY": 73 } ], "currentCostumeIndex": 0, @@ -2465,12 +2433,12 @@ }, { "name": "Drum-snare", - "md5": "31a81bb560abf30cbc44bcdd581d5ccc.svg", + "md5": "b0255be93e3c8be6ac687f4199a25c4b.svg", "type": "sprite", "tags": [], "info": [ 0, - 3, + 2, 3 ], "json": { @@ -2505,29 +2473,21 @@ { "costumeName": "drum-snare-a", "baseLayerID": -1, - "baseLayerMD5": "31a81bb560abf30cbc44bcdd581d5ccc.svg", + "baseLayerMD5": "b0255be93e3c8be6ac687f4199a25c4b.svg", "bitmapResolution": 1, - "rotationCenterX": 40, - "rotationCenterY": 69 + "rotationCenterX": 23, + "rotationCenterY": 50 }, { "costumeName": "drum-snare-b", - "baseLayerID": 0, + "baseLayerID": -1, "baseLayerMD5": "f6d2f2a6e1055dab6262336625ddf652.svg", "bitmapResolution": 1, "rotationCenterX": 36, "rotationCenterY": 66 - }, - { - "costumeName": "drum-snare-c", - "baseLayerID": -1, - "baseLayerMD5": "878ea646cbbd2a9e24fdeb8d98c3e24f.svg", - "bitmapResolution": 1, - "rotationCenterX": 40, - "rotationCenterY": 69 } ], - "currentCostumeIndex": 1, + "currentCostumeIndex": 0, "scratchX": 45, "scratchY": -33, "scale": 1, @@ -3234,7 +3194,7 @@ "tags": [], "info": [ 0, - 3, + 2, 8 ], "json": { @@ -3321,14 +3281,6 @@ "bitmapResolution": 1, "rotationCenterX": 47, "rotationCenterY": 83 - }, - { - "costumeName": "guitar-c", - "baseLayerID": -1, - "baseLayerMD5": "5cfe9faacb98d35a702cfa633967a5b5.svg", - "bitmapResolution": 1, - "rotationCenterX": 47, - "rotationCenterY": 83 } ], "currentCostumeIndex": 0, @@ -3350,7 +3302,7 @@ "tags": [], "info": [ 0, - 3, + 2, 8 ], "json": { @@ -3437,14 +3389,6 @@ "bitmapResolution": 1, "rotationCenterX": 38, "rotationCenterY": 94 - }, - { - "costumeName": "guitar-electric2-c", - "baseLayerID": -1, - "baseLayerMD5": "7f5f8b6d772657566e3dee24c9a9c538.svg", - "bitmapResolution": 1, - "rotationCenterX": 38, - "rotationCenterY": 94 } ], "currentCostumeIndex": 0, @@ -3466,7 +3410,7 @@ "tags": [], "info": [ 0, - 3, + 2, 8 ], "json": { @@ -3553,14 +3497,6 @@ "bitmapResolution": 1, "rotationCenterX": 42, "rotationCenterY": 85 - }, - { - "costumeName": "guitar-electric1-c", - "baseLayerID": -1, - "baseLayerMD5": "4cb069d2a290c0a153f196de725d357c.svg", - "bitmapResolution": 1, - "rotationCenterX": 42, - "rotationCenterY": 85 } ], "currentCostumeIndex": 0, @@ -3711,17 +3647,25 @@ "info": [ 0, 2, - 1 + 2 ], "json": { "objName": "Horse1", "sounds": [ { - "soundName": "meow", + "soundName": "horse", "soundID": -1, - "md5": "83c36d806dc92327b9e7049a565c6bff.wav", - "sampleCount": 18688, - "rate": 22050, + "md5": "45ffcf97ee2edca0199ff5aa71a5b72e.wav", + "sampleCount": 14464, + "rate": 11025, + "format": "" + }, + { + "soundName": "horse gallop", + "soundID": -1, + "md5": "058a34b5fb8b57178b5322d994b6b8c8.wav", + "sampleCount": 38336, + "rate": 11025, "format": "" } ], @@ -3891,90 +3835,6 @@ "spriteInfo": {} } }, - { - "name": "Jez", - "md5": "9de23c4a7a7fbb67136b539241346854.svg", - "type": "sprite", - "tags": [], - "info": [ - 0, - 6, - 1 - ], - "json": { - "objName": "jez", - "sounds": [ - { - "soundName": "pop", - "soundID": -1, - "md5": "83a9787d4cb6f3b7632b4ddfebf74367.wav", - "sampleCount": 258, - "rate": 11025, - "format": "" - } - ], - "costumes": [ - { - "costumeName": "kiran-a", - "baseLayerID": -1, - "baseLayerMD5": "9de23c4a7a7fbb67136b539241346854.svg", - "bitmapResolution": 1, - "rotationCenterX": 67, - "rotationCenterY": 95 - }, - { - "costumeName": "jez-b", - "baseLayerID": -1, - "baseLayerMD5": "f1e74f3c02333e9e2068e8baf4e77aa0.svg", - "bitmapResolution": 1, - "rotationCenterX": 67, - "rotationCenterY": 95 - }, - { - "costumeName": "jez-c", - "baseLayerID": -1, - "baseLayerMD5": "e2482cf509c312935f08be0e2e2c9d84.svg", - "bitmapResolution": 1, - "rotationCenterX": 67, - "rotationCenterY": 95 - }, - { - "costumeName": "jez-d", - "baseLayerID": -1, - "baseLayerMD5": "569e736b519199efddfbae2572f7e92b.svg", - "bitmapResolution": 1, - "rotationCenterX": 67, - "rotationCenterY": 95 - }, - { - "costumeName": "jez-e", - "baseLayerID": -1, - "baseLayerMD5": "2261bed0f2cc819def17969158297b4f.svg", - "bitmapResolution": 1, - "rotationCenterX": 77, - "rotationCenterY": 95 - }, - { - "costumeName": "jez-f", - "baseLayerID": -1, - "baseLayerMD5": "d7f44adb3dc7906b9dfb3599a028e0d6.svg", - "bitmapResolution": 1, - "rotationCenterX": 62, - "rotationCenterY": 94 - } - ], - "currentCostumeIndex": 0, - "scratchX": 57, - "scratchY": -42, - "scale": 0.8, - "direction": 90, - "rotationStyle": "normal", - "isDraggable": false, - "indexInLibrary": 3, - "visible": true, - "spriteInfo": {} - } - }, { "name": "Jordyn", "md5": "8dd2a2abbb8e639da8576b6e72ef9e59.svg", @@ -4050,7 +3910,7 @@ "tags": [], "info": [ 0, - 3, + 2, 8 ], "json": { @@ -4137,14 +3997,6 @@ "bitmapResolution": 1, "rotationCenterX": 72, "rotationCenterY": 68 - }, - { - "costumeName": "keyboard-c", - "baseLayerID": -1, - "baseLayerMD5": "4ee5e7c6d0463d9b50e0c593e70e1e31.svg", - "bitmapResolution": 1, - "rotationCenterX": 72, - "rotationCenterY": 68 } ], "currentCostumeIndex": 0, @@ -4159,6 +4011,90 @@ "spriteInfo": {} } }, + { + "name": "Kiran", + "md5": "9de23c4a7a7fbb67136b539241346854.svg", + "type": "sprite", + "tags": [], + "info": [ + 0, + 6, + 1 + ], + "json": { + "objName": "kiran", + "sounds": [ + { + "soundName": "pop", + "soundID": -1, + "md5": "83a9787d4cb6f3b7632b4ddfebf74367.wav", + "sampleCount": 258, + "rate": 11025, + "format": "" + } + ], + "costumes": [ + { + "costumeName": "kiran-a", + "baseLayerID": -1, + "baseLayerMD5": "9de23c4a7a7fbb67136b539241346854.svg", + "bitmapResolution": 1, + "rotationCenterX": 67, + "rotationCenterY": 95 + }, + { + "costumeName": "kiran-b", + "baseLayerID": -1, + "baseLayerMD5": "f1e74f3c02333e9e2068e8baf4e77aa0.svg", + "bitmapResolution": 1, + "rotationCenterX": 67, + "rotationCenterY": 95 + }, + { + "costumeName": "kiran-c", + "baseLayerID": -1, + "baseLayerMD5": "e2482cf509c312935f08be0e2e2c9d84.svg", + "bitmapResolution": 1, + "rotationCenterX": 67, + "rotationCenterY": 95 + }, + { + "costumeName": "kiran-d", + "baseLayerID": -1, + "baseLayerMD5": "569e736b519199efddfbae2572f7e92b.svg", + "bitmapResolution": 1, + "rotationCenterX": 67, + "rotationCenterY": 95 + }, + { + "costumeName": "kiran-e", + "baseLayerID": -1, + "baseLayerMD5": "2261bed0f2cc819def17969158297b4f.svg", + "bitmapResolution": 1, + "rotationCenterX": 77, + "rotationCenterY": 95 + }, + { + "costumeName": "kiran-f", + "baseLayerID": -1, + "baseLayerMD5": "d7f44adb3dc7906b9dfb3599a028e0d6.svg", + "bitmapResolution": 1, + "rotationCenterX": 62, + "rotationCenterY": 94 + } + ], + "currentCostumeIndex": 0, + "scratchX": 57, + "scratchY": -42, + "scale": 0.8, + "direction": 90, + "rotationStyle": "normal", + "isDraggable": false, + "indexInLibrary": 3, + "visible": true, + "spriteInfo": {} + } + }, { "name": "Knight", "md5": "f2c5e8bc24d001b81566879dbf2f1a13.svg", @@ -4434,7 +4370,7 @@ "tags": [], "info": [ 0, - 3, + 2, 9 ], "json": { @@ -4529,14 +4465,6 @@ "bitmapResolution": 1, "rotationCenterX": 40, "rotationCenterY": 88 - }, - { - "costumeName": "microphone-c", - "baseLayerID": -1, - "baseLayerMD5": "021d6da78b7bda374da4bdb50d4cbc4f.svg", - "bitmapResolution": 1, - "rotationCenterX": 40, - "rotationCenterY": 88 } ], "currentCostumeIndex": 0, @@ -5086,7 +5014,7 @@ "tags": [], "info": [ 0, - 1, + 3, 1 ], "json": { @@ -5103,12 +5031,28 @@ ], "costumes": [ { - "costumeName": "penguin1", + "costumeName": "penguin1-a", "baseLayerID": -1, "baseLayerMD5": "c17d9e4bdb59c574e0c34aa70af516da.svg", "bitmapResolution": 1, "rotationCenterX": 54, "rotationCenterY": 61 + }, + { + "costumeName": "penguin1-b", + "baseLayerID": -1, + "baseLayerMD5": "35fec7aa5f60cca945fe0615413f1f08.svg", + "bitmapResolution": 1, + "rotationCenterX": 48, + "rotationCenterY": 62 + }, + { + "costumeName": "penguin1-c", + "baseLayerID": -1, + "baseLayerMD5": "18fa51a64ebd5518f0c5c465525346e5.svg", + "bitmapResolution": 1, + "rotationCenterX": 48, + "rotationCenterY": 61 } ], "currentCostumeIndex": 0, @@ -5123,6 +5067,66 @@ "spriteInfo": {} } }, + { + "name": "Penguin2", + "md5": "eaaaa7068e78a51d73ba437f1ec5763e.svg", + "type": "sprite", + "tags": [], + "info": [ + 0, + 3, + 1 + ], + "json": { + "objName": "Penguin2", + "sounds": [ + { + "soundName": "pop", + "soundID": -1, + "md5": "83a9787d4cb6f3b7632b4ddfebf74367.wav", + "sampleCount": 258, + "rate": 11025, + "format": "" + } + ], + "costumes": [ + { + "costumeName": "penguin2-a", + "baseLayerID": -1, + "baseLayerMD5": "eaaaa7068e78a51d73ba437f1ec5763e.svg", + "bitmapResolution": 1, + "rotationCenterX": 49, + "rotationCenterY": 79 + }, + { + "costumeName": "penguin2-b", + "baseLayerID": -1, + "baseLayerMD5": "5a80f4b2fd20d43e4f7cb4189c08d99c.svg", + "bitmapResolution": 1, + "rotationCenterX": 45, + "rotationCenterY": 79 + }, + { + "costumeName": "penguin2-c", + "baseLayerID": -1, + "baseLayerMD5": "394e79f5f9a462064ece2a9a6606a07d.svg", + "bitmapResolution": 1, + "rotationCenterX": 50, + "rotationCenterY": 78 + } + ], + "currentCostumeIndex": 0, + "scratchX": 95, + "scratchY": 44, + "scale": 1, + "direction": 90, + "rotationStyle": "normal", + "isDraggable": false, + "indexInLibrary": 62, + "visible": true, + "spriteInfo": {} + } + }, { "name": "Pico", "md5": "0579fe60bb3717c49dfd7743caa84ada.svg", @@ -5682,7 +5686,7 @@ "tags": [], "info": [ 0, - 3, + 2, 8 ], "json": { @@ -5769,14 +5773,6 @@ "bitmapResolution": 1, "rotationCenterX": 47, "rotationCenterY": 80 - }, - { - "costumeName": "saxophone-c", - "baseLayerID": -1, - "baseLayerMD5": "29751826f768bc1568092f267525ae8b.svg", - "bitmapResolution": 1, - "rotationCenterX": 47, - "rotationCenterY": 80 } ], "currentCostumeIndex": 0, @@ -5791,6 +5787,66 @@ "spriteInfo": {} } }, + { + "name": "Shark", + "md5": "7c0a907eae79462f69f8e2af8e7df828.svg", + "type": "sprite", + "tags": [], + "info": [ + 0, + 3, + 1 + ], + "json": { + "objName": "Shark", + "sounds": [ + { + "soundName": "pop", + "soundID": -1, + "md5": "83a9787d4cb6f3b7632b4ddfebf74367.wav", + "sampleCount": 258, + "rate": 11025, + "format": "" + } + ], + "costumes": [ + { + "costumeName": "shark-a ", + "baseLayerID": -1, + "baseLayerMD5": "7c0a907eae79462f69f8e2af8e7df828.svg", + "bitmapResolution": 1, + "rotationCenterX": 75, + "rotationCenterY": 75 + }, + { + "costumeName": "shark-b ", + "baseLayerID": -1, + "baseLayerMD5": "cff9ae87a93294693a0650b38a7a33d2.svg", + "bitmapResolution": 1, + "rotationCenterX": 75, + "rotationCenterY": 75 + }, + { + "costumeName": "shark-c ", + "baseLayerID": -1, + "baseLayerMD5": "afeae3f998598424f7c50918507f6ce6.svg", + "bitmapResolution": 1, + "rotationCenterX": 77, + "rotationCenterY": 37 + } + ], + "currentCostumeIndex": 0, + "scratchX": 41, + "scratchY": -15, + "scale": 1, + "direction": 90, + "rotationStyle": "normal", + "isDraggable": false, + "indexInLibrary": 63, + "visible": true, + "spriteInfo": {} + } + }, { "name": "Snake", "md5": "4d06e12d90479461303d828f0970f2d4.svg", @@ -6359,6 +6415,58 @@ "spriteInfo": {} } }, + { + "name": "Starfish", + "md5": "3d1101bbc24ae292a36356af325f660c.svg", + "type": "sprite", + "tags": [], + "info": [ + 0, + 2, + 1 + ], + "json": { + "objName": "Starfish", + "sounds": [ + { + "soundName": "pop", + "soundID": -1, + "md5": "83a9787d4cb6f3b7632b4ddfebf74367.wav", + "sampleCount": 258, + "rate": 11025, + "format": "" + } + ], + "costumes": [ + { + "costumeName": "starfish-a", + "baseLayerID": -1, + "baseLayerMD5": "3d1101bbc24ae292a36356af325f660c.svg", + "bitmapResolution": 1, + "rotationCenterX": 75, + "rotationCenterY": 75 + }, + { + "costumeName": "starfish-b ", + "baseLayerID": -1, + "baseLayerMD5": "ad8007f4e63693984d4adc466ffa3ad2.svg", + "bitmapResolution": 1, + "rotationCenterX": 53, + "rotationCenterY": 60 + } + ], + "currentCostumeIndex": 1, + "scratchX": -37, + "scratchY": 38, + "scale": 1, + "direction": 90, + "rotationStyle": "normal", + "isDraggable": false, + "indexInLibrary": 64, + "visible": true, + "spriteInfo": {} + } + }, { "name": "Strawberry", "md5": "734556fb8e14740f2cbc971238b71d47.svg", @@ -6646,7 +6754,7 @@ "tags": [], "info": [ 0, - 3, + 2, 8 ], "json": { @@ -6733,14 +6841,6 @@ "bitmapResolution": 1, "rotationCenterX": 37, "rotationCenterY": 73 - }, - { - "costumeName": "trumpet-c", - "baseLayerID": -1, - "baseLayerMD5": "680dd004c24690af1e95c4beb22cd615.svg", - "bitmapResolution": 1, - "rotationCenterX": 37, - "rotationCenterY": 73 } ], "currentCostumeIndex": 0, diff --git a/src/lib/make-toolbox-xml.js b/src/lib/make-toolbox-xml.js index 306c5b548..4bfb8b2e5 100644 --- a/src/lib/make-toolbox-xml.js +++ b/src/lib/make-toolbox-xml.js @@ -234,10 +234,10 @@ const looks = function (isStage, targetId) { ${blockSeparator} - - - ${blockSeparator} ${isStage ? '' : ` + + + ${blockSeparator} @@ -246,7 +246,6 @@ const looks = function (isStage, targetId) { - ${blockSeparator} `} ${isStage ? ` @@ -311,13 +310,17 @@ const sound = function () { `; }; -const events = function () { +const events = function (isStage) { return ` - + ${isStage ? ` + + ` : ` + + `} ${blockSeparator} @@ -431,7 +434,11 @@ const sensing = function (isStage) { ${blockSeparator} - + + + + + diff --git a/src/lib/vm-listener-hoc.jsx b/src/lib/vm-listener-hoc.jsx index 8e7a0ca17..3f1453b54 100644 --- a/src/lib/vm-listener-hoc.jsx +++ b/src/lib/vm-listener-hoc.jsx @@ -52,11 +52,6 @@ const vmListenerHOC = function (WrappedComponent) { keyCode: e.keyCode, isDown: true }); - - // Don't stop browser keyboard shortcuts - if (e.metaKey || e.altKey || e.ctrlKey) return; - - e.preventDefault(); } handleKeyUp (e) { // Always capture up events, @@ -99,9 +94,7 @@ const vmListenerHOC = function (WrappedComponent) { attachKeyboardEvents: true }; const mapStateToProps = state => ({ - vm: state.vm, - hoveredSprite: state.hoveredTarget.sprite, - editingTarget: state.targets.editingTarget + vm: state.vm }); const mapDispatchToProps = dispatch => ({ onTargetsUpdate: data => { diff --git a/src/reducers/modals.js b/src/reducers/modals.js index ba038ff34..2a3772e59 100644 --- a/src/reducers/modals.js +++ b/src/reducers/modals.js @@ -8,6 +8,7 @@ const MODAL_COSTUME_LIBRARY = 'costumeLibrary'; const MODAL_EXTENSION_LIBRARY = 'extensionLibrary'; const MODAL_FEEDBACK_FORM = 'feedbackForm'; const MODAL_IMPORT_INFO = 'importInfo'; +const MODAL_LOADING_PROJECT = 'loadingProject'; const MODAL_PREVIEW_INFO = 'previewInfo'; const MODAL_SOUND_LIBRARY = 'soundLibrary'; const MODAL_SPRITE_LIBRARY = 'spriteLibrary'; @@ -20,6 +21,7 @@ const initialState = { [MODAL_EXTENSION_LIBRARY]: false, [MODAL_FEEDBACK_FORM]: false, [MODAL_IMPORT_INFO]: false, + [MODAL_LOADING_PROJECT]: false, [MODAL_PREVIEW_INFO]: true, [MODAL_SOUND_LIBRARY]: false, [MODAL_SPRITE_LIBRARY]: false, @@ -73,6 +75,10 @@ const openImportInfo = function () { analytics.pageview('modals/import'); return openModal(MODAL_IMPORT_INFO); }; +const openLoadingProject = function () { + analytics.pageview('modals/loading'); + return openModal(MODAL_LOADING_PROJECT); +}; const openPreviewInfo = function () { analytics.pageview('/modals/preview'); return openModal(MODAL_PREVIEW_INFO); @@ -104,6 +110,9 @@ const closeFeedbackForm = function () { const closeImportInfo = function () { return closeModal(MODAL_IMPORT_INFO); }; +const closeLoadingProject = function () { + return closeModal(MODAL_LOADING_PROJECT); +}; const closePreviewInfo = function () { return closeModal(MODAL_PREVIEW_INFO); }; @@ -123,6 +132,7 @@ export { openExtensionLibrary, openFeedbackForm, openImportInfo, + openLoadingProject, openPreviewInfo, openSoundLibrary, openSpriteLibrary, @@ -132,6 +142,7 @@ export { closeExtensionLibrary, closeFeedbackForm, closeImportInfo, + closeLoadingProject, closePreviewInfo, closeSpriteLibrary, closeSoundLibrary, diff --git a/test/helpers/selenium-helper.js b/test/helpers/selenium-helper.js index b63ea2bf6..b18f35651 100644 --- a/test/helpers/selenium-helper.js +++ b/test/helpers/selenium-helper.js @@ -34,8 +34,11 @@ class SeleniumHelper { } getDriver () { + const chromeCapabilities = webdriver.Capabilities.chrome(); + chromeCapabilities.set('chromeOptions', {args: ['--headless']}); this.driver = new webdriver.Builder() .forBrowser('chrome') + .withCapabilities(chromeCapabilities) .build(); return this.driver; } diff --git a/test/unit/components/__snapshots__/button.test.jsx.snap b/test/unit/components/__snapshots__/button.test.jsx.snap index 5b928b4c4..a46334204 100644 --- a/test/unit/components/__snapshots__/button.test.jsx.snap +++ b/test/unit/components/__snapshots__/button.test.jsx.snap @@ -5,5 +5,9 @@ exports[`ButtonComponent matches snapshot 1`] = ` className="" onClick={[Function]} role="button" -/> +> +
+ `; diff --git a/test/unit/components/__snapshots__/sprite-selector-item.test.jsx.snap b/test/unit/components/__snapshots__/sprite-selector-item.test.jsx.snap index 4ecf19565..eaca8d936 100644 --- a/test/unit/components/__snapshots__/sprite-selector-item.test.jsx.snap +++ b/test/unit/components/__snapshots__/sprite-selector-item.test.jsx.snap @@ -1,5 +1,84 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`SpriteSelectorItemComponent matches snapshot when given a number to show 1`] = ` +
+
+ +
+
+ 5 +
+ +
+ Pony sprite +
+ +
+`; + exports[`SpriteSelectorItemComponent matches snapshot when selected 1`] = `
{ let onClick; let onDeleteButtonClick; let selected; + let number; // Wrap this in a function so it gets test specific states and can be reused. const getComponent = function () { @@ -19,6 +20,7 @@ describe('SpriteSelectorItemComponent', () => { className={className} costumeURL={costumeURL} name={name} + number={number} selected={selected} onClick={onClick} onDeleteButtonClick={onDeleteButtonClick} @@ -33,6 +35,8 @@ describe('SpriteSelectorItemComponent', () => { onClick = jest.fn(); onDeleteButtonClick = jest.fn(); selected = true; + // Reset number to undefined since it is an optional prop + number = undefined; // eslint-disable-line no-undefined }); test('matches snapshot when selected', () => { @@ -40,6 +44,12 @@ describe('SpriteSelectorItemComponent', () => { expect(component.toJSON()).toMatchSnapshot(); }); + test('matches snapshot when given a number to show', () => { + number = 5; + const component = componentWithIntl(getComponent()); + expect(component.toJSON()).toMatchSnapshot(); + }); + test('does not have a close box when not selected', () => { selected = false; const wrapper = shallowWithIntl(getComponent());