diff --git a/.travis.yml b/.travis.yml index 991c69bba5924ed25b6b7da191bdddaeb0893757..59e1d15ba92569c05cd41357603636935a74fff3 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 b8649d8c1e8dd0e430e9eae5c1534f69007606bf..e9f49338f375f2d58271245051815e26bf79b328 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 f2ab58cae0dc4c084cc525ffc50252eaad777b8e..ff818415a44851d1049cfd674e40f40c4ca6987b 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 c410f7f62d26e359a300e2bb80072e8ed9bcf248..9052d5f57075796500df46e8f172cca3405e0f8f 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}) => ( <p> { /* eslint-disable max-len */ } <FormattedMessage - defaultMessage="We're very sorry, but Scratch 3.0 does not support Internet Explorer. We recommend trying a newer browser such as Google Chrome, Mozilla Firefox, or Microsoft Edge." + defaultMessage="We're very sorry, but Scratch 3.0 does not support Internet Explorer, Opera or Silk. We recommend trying a newer browser such as Google Chrome, Mozilla Firefox, or Microsoft Edge." description="Unsupported browser description" id="gui.unsupportedBrowser.description" /> diff --git a/src/components/button/button.css b/src/components/button/button.css index 44243650f970d16aa787b8a44da3763e595802ce..5e69aabd477b6507ef774c9249586cdeb29b8198 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 59b39668fe9952a6425850fbe222266fc94639bb..1d348ba24d7bfc3ce60a02b244e36e2f9f2b3373 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 && ( + <img + className={classNames(iconClassName, styles.icon)} + draggable={false} + src={iconSrc} + /> + ); + return ( <span className={classNames( - styles.button, + styles.outlinedButton, className )} role="button" onClick={onClick} {...props} > - {children} + {icon} + <div className={styles.content}>{children}</div> </span> ); }; @@ -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 5363ef081b3f99808f63513e5cf1b9c65a79e270..6ccf4fdd49eb64e5537957436d0e6597a1216258 100644 --- a/src/components/filter/filter.jsx +++ b/src/components/filter/filter.jsx @@ -25,7 +25,6 @@ const FilterComponent = props => { src={filterIcon} /> <input - autoFocus className={styles.filterInput} placeholder={placeholderText} type="text" diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx index b7f2a0acd1d73abf9f683dd8c7d3505394c8a441..2ec53402bb78ec00bc83a373de6fdfc7c36bcbaf 100644 --- a/src/components/gui/gui.jsx +++ b/src/components/gui/gui.jsx @@ -34,6 +34,10 @@ const messages = defineMessages({ } }); +// Cache this value to only retreive it once the first time. +// Assume that it doesn't change for a session. +let isRendererSupported = null; + const GUIComponent = props => { 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 ( <Box diff --git a/src/components/language-selector/dropdown-caret.svg b/src/components/language-selector/dropdown-caret.svg new file mode 100644 index 0000000000000000000000000000000000000000..9ed9f8a5499f221d2ec0a7999fc3b4f6305d5243 Binary files /dev/null and b/src/components/language-selector/dropdown-caret.svg differ diff --git a/src/components/language-selector/language-icon.svg b/src/components/language-selector/language-icon.svg index 42bd409ab18e8ed0e737e6547c7ecc89ea57df0d..e3a712ffb3d7cf6cab15c7ea03751733a6852082 100644 Binary files a/src/components/language-selector/language-icon.svg and b/src/components/language-selector/language-icon.svg differ diff --git a/src/components/language-selector/language-selector.css b/src/components/language-selector/language-selector.css index 0ae9e0a1028ec95ccd9e9896b57721e439e57e06..bae30cdd3c7ca8eef1d9cad1ab9b1f03e00a918c 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 db846fef5953fa54c3f4847808b21735b60f2807..e4779d8c6b561d3df41575af07979fdc805d8fbb 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 }) => ( <Box {...props}> - <ComingSoonTooltip - place="bottom" - tooltipId="language-selector" - > - <div className={styles.group}> - <img - className={styles.languageIcon} - src={languageIcon} - /> + <div className={styles.group}> + {open ? ( <select disabled aria-label="language selector" @@ -38,15 +33,27 @@ const LanguageSelector = ({ </option> ))} </select> - </div> - </ComingSoonTooltip> + ) : ( + <React.Fragment> + <img + className={classNames(styles.languageIcon, styles.disabled)} + src={languageIcon} + /> + <img + className={classNames(styles.dropdownCaret, styles.disabled)} + src={dropdownCaret} + /> + </React.Fragment> + )} + </div> </Box> ); 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 0f580278cdf7659ba819c1e345d7b220ceed013b..d437a9c0265a71fd6d5303e3ca62ef3a647c3eb0 100644 --- a/src/components/load-button/load-button.jsx +++ b/src/components/load-button/load-button.jsx @@ -26,6 +26,7 @@ const LoadButtonComponent = ({ </ButtonComponent> <input disabled + accept=".sb2,.sb3" className={styles.fileInput} ref={inputRef} type="file" diff --git a/src/components/loader/loader.css b/src/components/loader/loader.css index 68f8e5f4f08f852f5f1ac3c968d30e5c33729a8e..8ebbdb5cfe9d47e7a9eb880ee66e38272c8d86d4 100644 --- a/src/components/loader/loader.css +++ b/src/components/loader/loader.css @@ -29,19 +29,18 @@ margin-top: -4px; } -.topBlock { +.top-block { animation: top-slide-in 1.5s ease infinite; } -.middleBlock { +.middle-block { animation: middle-slide-in 1.5s ease infinite; } -.bottomBlock { +.bottom-block { animation: bottom-slide-in 1.5s ease infinite; } - @keyframes top-slide-in { 0% { transform: translateY(50px); @@ -87,3 +86,17 @@ opacity: 1; } } + +.message-container-outer { + height: 30px; + overflow: hidden; +} + +.message-container-inner { + transition: transform 0.5s; +} + +.message { + height: 20px; + margin: 5px 0; +} diff --git a/src/components/loader/loader.jsx b/src/components/loader/loader.jsx index 8a93e5c36abd385665c9fcdd1374f30096d9c0a8..9da2eb3838e4ef8f98db2c64304ae016d1359dcd 100644 --- a/src/components/loader/loader.jsx +++ b/src/components/loader/loader.jsx @@ -5,140 +5,174 @@ import styles from './loader.css'; import topBlock from './top-block.svg'; import middleBlock from './middle-block.svg'; import bottomBlock from './bottom-block.svg'; +const messages = [ + { + message: ( + <FormattedMessage + defaultMessage="Creating blocks …" + description="One of the loading messages" + id="gui.loader.message1" + /> + ), + weight: 50 + }, + { + message: ( + <FormattedMessage + defaultMessage="Loading sprites …" + description="One of the loading messages" + id="gui.loader.message2" + /> + ), + weight: 50 + }, + { + message: ( + <FormattedMessage + defaultMessage="Loading sounds …" + description="One of the loading messages" + id="gui.loader.message3" + /> + ), + weight: 50 + }, + { + message: ( + <FormattedMessage + defaultMessage="Loading extensions …" + description="One of the loading messages" + id="gui.loader.message4" + /> + ), + weight: 50 + }, + { + message: ( + <FormattedMessage + defaultMessage="Creating blocks …" + description="One of the loading messages" + id="gui.loader.message1" + /> + ), + weight: 20 + }, + { + message: ( + <FormattedMessage + defaultMessage="Herding cats …" + description="One of the loading messages" + id="gui.loader.message5" + /> + ), + weight: 1 + }, + { + message: ( + <FormattedMessage + defaultMessage="Transmitting nanos …" + description="One of the loading messages" + id="gui.loader.message6" + /> + ), + weight: 1 + }, + { + message: ( + <FormattedMessage + defaultMessage="Inflating gobos …" + description="One of the loading messages" + id="gui.loader.message7" + /> + ), + weight: 1 + }, + { + message: ( + <FormattedMessage + defaultMessage="Preparing emojis …" + description="One of the loading messages" + id="gui.loader.message8" + /> + ), + weight: 1 + } +]; -const LoaderComponent = () => { - const messages = [ - { - message: ( - <FormattedMessage - defaultMessage="Creating blocks …" - description="One of the loading messages" - id="gui.loader.message1" - /> - ), - weight: 50 - }, - { - message: ( - <FormattedMessage - defaultMessage="Loading sprites …" - description="One of the loading messages" - id="gui.loader.message2" - /> - ), - weight: 50 - }, - { - message: ( - <FormattedMessage - defaultMessage="Loading sounds …" - description="One of the loading messages" - id="gui.loader.message3" - /> - ), - weight: 50 - }, - { - message: ( - <FormattedMessage - defaultMessage="Loading extensions …" - description="One of the loading messages" - id="gui.loader.message4" - /> - ), - weight: 50 - }, - { - message: ( - <FormattedMessage - defaultMessage="Creating blocks …" - description="One of the loading messages" - id="gui.loader.message1" - /> - ), - weight: 20 - }, - { - message: ( - <FormattedMessage - defaultMessage="Herding cats …" - description="One of the loading messages" - id="gui.loader.message5" - /> - ), - weight: 1 - }, - { - message: ( - <FormattedMessage - defaultMessage="Transmitting nanos …" - description="One of the loading messages" - id="gui.loader.message6" - /> - ), - weight: 1 - }, - { - message: ( - <FormattedMessage - defaultMessage="Inflating gobos …" - description="One of the loading messages" - id="gui.loader.message7" - /> - ), - weight: 1 - }, - { - message: ( - <FormattedMessage - defaultMessage="Preparing emojiis …" - description="One of the loading messages" - id="gui.loader.message8" - /> - ), - weight: 1 - } - ]; +class LoaderComponent extends React.Component { + constructor (props) { + super(props); + this.state = { + messageNumber: 0 + }; + } + componentDidMount () { + this.chooseRandomMessage(); - 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; + // 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}); } - - return ( - <div className={styles.background}> - <div className={styles.container}> - <div className={styles.blockAnimation}> - <img - className={styles.topBlock} - src={topBlock} - /> - <img - className={styles.middleBlock} - src={middleBlock} - /> - <img - className={styles.bottomBlock} - src={bottomBlock} - /> + render () { + return ( + <div className={styles.background}> + <div className={styles.container}> + <div className={styles.blockAnimation}> + <img + className={styles.topBlock} + src={topBlock} + /> + <img + className={styles.middleBlock} + src={middleBlock} + /> + <img + className={styles.bottomBlock} + src={bottomBlock} + /> + </div> + <h1 className={styles.title}> + <FormattedMessage + defaultMessage="Loading Project" + description="Main loading message" + id="gui.loader.headline" + /> + </h1> + <div className={styles.messageContainerOuter}> + <div + className={styles.messageContainerInner} + style={{transform: `translate(0, -${this.state.messageNumber * 25}px)`}} + > + {messages.map((m, i) => ( + <div + className={styles.message} + key={i} + > + {m.message} + </div> + ))} + </div> + </div> </div> - <h1 className={styles.title}> - <FormattedMessage - defaultMessage="Loading Project" - description="Main loading message" - id="gui.loader.headline" - /> - </h1> - <p>{message}</p> </div> - </div> - ); -}; + ); + } +} 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 0000000000000000000000000000000000000000..79bbcb7f9da39c0a73db5a142082b9c39c2825bc 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 0000000000000000000000000000000000000000..41e62b6140efe34784c1c5fa697f1bf73c236b85 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 0000000000000000000000000000000000000000..1f5b6ee6e58a29ba0ddf03372fff213c0e2948b5 Binary files /dev/null and b/src/components/menu-bar/icon--see-community.svg differ diff --git a/src/components/menu-bar/menu-bar.css b/src/components/menu-bar/menu-bar.css index d1f4c3185d4b6b8200b46167eab8ea153b34c1cc..7e59dd4ef1de43d6a2cd966990ad6dcff41d8bcf 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; +} + +.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; } -.feedback-button-icon { +.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 5807294541a08fcf03983f3542e77a0416bf3d17..587b51fc4b049fe4ab480b895f62b2a766491925 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 => ( })} > <div className={styles.mainMenu}> - <div className={classNames(styles.logoWrapper, styles.menuItem)}> - <img - alt="Scratch" - className={styles.scratchLogo} - draggable={false} - src={scratchLogo} - /> + <div className={styles.fileGroup}> + <div className={classNames(styles.logoWrapper, styles.menuItem)}> + <img + alt="Scratch" + className={styles.scratchLogo} + draggable={false} + src={scratchLogo} + /> + </div> + <div className={classNames(styles.menuItem)}> + <ComingSoonTooltip + className={styles.comingSoon} + place="bottom" + tooltipClassName={styles.comingSoonTooltip} + tooltipId="menubar-selector" + > + <LanguageSelector /> + </ComingSoonTooltip> + </div> + <div className={classNames(styles.menuItem)}> + <ComingSoonTooltip + className={styles.comingSoon} + place="bottom" + tooltipClassName={styles.comingSoonTooltip} + tooltipId="file-menu" + > + <div className={classNames(styles.fileMenu)}>File</div> + </ComingSoonTooltip> + </div> + <div className={classNames(styles.menuItem)}> + <ComingSoonTooltip + className={styles.comingSoon} + place="bottom" + tooltipClassName={styles.comingSoonTooltip} + tooltipId="edit-menu" + > + <div className={classNames(styles.editMenu)}>Edit</div> + </ComingSoonTooltip> + </div> + </div> + <div className={classNames(styles.divider)} /> + <div className={classNames(styles.menuItem)}> + <ComingSoonTooltip + className={styles.comingSoon} + place="bottom" + tooltipClassName={styles.comingSoonTooltip} + tooltipId="title-field" + > + <input + disabled + className={classNames(styles.titleField)} + placeholder="Untitled-1" + /> + </ComingSoonTooltip> + </div> + <div className={classNames(styles.menuItem)}> + <ComingSoonTooltip + className={styles.comingSoon} + place="bottom" + tooltipClassName={styles.comingSoonTooltip} + tooltipId="share-button" + > + <Button className={classNames(styles.shareButton)}> + <FormattedMessage + defaultMessage="Share" + description="Label for project share button" + id="gui.menuBar.share" + /> + </Button> + </ComingSoonTooltip> + </div> + <div className={classNames(styles.menuItem, styles.communityButtonWrapper)}> + <ComingSoonTooltip + className={styles.comingSoon} + place="bottom" + tooltipClassName={styles.comingSoonTooltip} + tooltipId="community-button" + > + <Button + className={classNames(styles.communityButton)} + iconClassName={styles.communityButtonIcon} + iconSrc={communityIcon} + > + <FormattedMessage + defaultMessage="See Community" + description="Label for see community button" + id="gui.menuBar.seeCommunity" + /> + </Button> + </ComingSoonTooltip> </div> - <SaveButton className={styles.menuItem} /> - <LoadButton className={styles.menuItem} /> - <LanguageSelector className={styles.menuItem} /> </div> - <div className={styles.feedbackButtonWrapper}> + <div className={classNames(styles.menuItem, styles.feedbackButtonWrapper)}> <Button className={styles.feedbackButton} + iconSrc={feedbackIcon} onClick={props.onGiveFeedback} > - <img - className={styles.feedbackButtonIcon} - draggable={false} - src={feedbackIcon} + <FormattedMessage + defaultMessage="Give Feedback" + description="Label for feedback form modal button" + id="gui.menuBar.giveFeedback" /> - <span className={styles.feedbackText}> - <FormattedMessage - defaultMessage="Give Feedback" - description="Label for feedback form modal button" - id="gui.menuBar.giveFeedback" - /> - </span> </Button> </div> + <div className={styles.accountInfoWrapper}> + <ComingSoonTooltip + className={styles.comingSoon} + place="bottom" + tooltipId="mystuff" + > + <div className={styles.mystuffButton}> + <img + className={styles.mystuffIcon} + src={mystuffIcon} + /> + </div> + </ComingSoonTooltip> + <ComingSoonTooltip + className={styles.comingSoon} + place="left" + tooltipId="account-nav" + > + <div className={styles.accountNavMenu}> + <img + className={styles.profileIcon} + src={profileIcon} + /> + <span>scratch-cat</span> + <img + className={styles.dropdownCaretIcon} + src={dropdownCaret} + /> + </div> + </ComingSoonTooltip> + </div> </Box> ); diff --git a/src/components/sprite-selector-item/sprite-selector-item.css b/src/components/sprite-selector-item/sprite-selector-item.css index 60f8393565b8d2e4068a005dd5bc65f4e00dbef4..a0880a518705b1ecff5d15ea61ea1940324d2c03 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 abb47bb5478bcb572726ac2a838f88efe8476e06..35eeebe005cba166743ed763dbd518f1deafc43a 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 : ( + <div className={styles.number}>{props.number}</div> + )} {props.costumeURL ? ( <CostumeCanvas className={styles.spriteImage} @@ -69,6 +72,7 @@ SpriteSelectorItem.propTypes = { className: PropTypes.string, costumeURL: PropTypes.string, name: PropTypes.string.isRequired, + number: PropTypes.number, onClick: PropTypes.func, onDeleteButtonClick: PropTypes.func, onDuplicateButtonClick: PropTypes.func, diff --git a/src/components/stage-selector/stage-selector.jsx b/src/components/stage-selector/stage-selector.jsx index 073ca257c15bfe735bafb67ce25966e69a3dfb40..8016caecab1691294f6f6b47842bf113216a567a 100644 --- a/src/components/stage-selector/stage-selector.jsx +++ b/src/components/stage-selector/stage-selector.jsx @@ -94,12 +94,12 @@ const StageSelector = props => { }, { 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 1044073a1cc18b438633e487019d03f2d10065e4..7cc7a1ed25963029ea60a6fd3decfb69c52722df 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 ? ( - <BackdropLibrary - vm={vm} - onRequestClose={onRequestCloseBackdropLibrary} - /> - ) : null} </div> </div> </div> @@ -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 e6b7d4d39f6577bf7e2bc967065a030008956848..8f796225e81a8e8740b4100b408269fbbc22b2b2 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 bedbbdbade90c98fe17572495521ca28478cdef9..504291036f35d8f99d61c062b53c31d7e0315dbe 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 2e631bd95e4339344eac40cbfc3f691f4c2cd87b..afdd56a5d292a714fa81d954f6ee27fede66ab29 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 2e897c451031eae029dad95609c484f2ef645d72..37fd8a12e1e490933ec5f843eb12e667c2316b10 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 ? <PaintEditorWrapper - {...props} selectedCostumeIndex={this.state.selectedCostumeIndex} /> : null @@ -226,10 +226,15 @@ class CostumeTab extends React.Component { {costumeLibraryVisible ? ( <CostumeLibrary vm={vm} - onNewCostume={this.handleNewCostume} onRequestClose={onRequestCloseCostumeLibrary} /> ) : null} + {backdropLibraryVisible ? ( + <BackdropLibrary + vm={vm} + onRequestClose={onRequestCloseBackdropLibrary} + /> + ) : null} </AssetPanel> ); } @@ -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 d7f558c166f3da16eb00b4a88423add555a994e1..4c473a7374a05e14b4f99772a499e72a1f8f8eb3 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 <BrowserModalComponent onBack={this.handleBack} />; } return <CrashMessageComponent onReload={this.handleReload} />; diff --git a/src/containers/gui.jsx b/src/containers/gui.jsx index 320eedcdb6661a076d39bea33892c9e4a0deefa4..85b3a7acd224798d8ddd94bb42ca9bc1ea95e156 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 ( <GUIComponent - loading={fetchingProject || this.state.loading} + loading={fetchingProject || this.state.loading || loadingStateVisible} vm={vm} {...componentProps} > @@ -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 1c2ddbefbb4639cec62cbf37fc7ceae33a45dbc8..0731098f1563c2fda54258c807babc0872ee4638 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 a7b78a638dcf87871ced71752a668beebe153f87..e52a6735da91bb32005c531ddd35e267ea58e541 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 38cb3aa4a818b92ccc0ca2fa5a7946f0ade90a86..52bb5708f9c13e928be7fc6fd909fd0970c9b722 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 1209f251f0445da6a5aa0d65f8fa6d343ce5654e..3fa6c8f03f97eea04d8e1ed3b69c50ca2e070b0a 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 8e4b9d249690391dd4da4c80633728b5a525d811..9c4311539ae204c1583add7afe0a59497944de25 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 f2ae497def0837fcf6eb0b888f3b84933d95039c..fc8bcf40a635b0a3caafae8f6ec27f9ede8704c2 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); + + saveLink.href = url; - // 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); + // 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 bbe032c0b67bf897fb2ecdb6edf26cdc04b5c41f..de10ebc2cd066dc3729ea2afebeb1c116970389b 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 471ebe99e65a37301979f431f304096e392c1a27..b0b4e3d2209f27ec4725ee33639c6c926e14f26d 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 27075cef8afb92f04a664ee0ae3c1e8d43930b61..bf749c12a08fa7d997e484aa19b98d252e2a9d30 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 c386112b3858cbfbc8bc4040c84bff2ec88ac88b..cf10d50019342bdf9942d36dfb580d7a6f6ff9f0 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 889d84185ad94b7debf94c9c429b51194ab5af82..97d6dffaa31e4c89e9f09a02d0e7acda5bb8fe76 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 be8a9a0dbe1ef45a5a3214f1d8d00b92f4b74d42..b46bc50bf79c6abe91c77aa1011b6eba17812ec3 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 c1ae335d848b25a005b0fc353766ce6a5be05042..fc3e21a9ce07b5022470ee61da2b6672f2abd953 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 30c305182273eba19e884bc99ce2f82c4e20cc57..1ac8e24d90ab1e9c34e9bd9e6ac65490a308c6fa 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", @@ -1914,145 +1826,134 @@ ] }, { - "name": "Jez-b", - "md5": "f1e74f3c02333e9e2068e8baf4e77aa0.svg", - "type": "costume", - "tags": [], - "info": [ - 67, - 95, - 1 - ] - }, - { - "name": "Jez-c", - "md5": "e2482cf509c312935f08be0e2e2c9d84.svg", + "name": "Jordyn-a", + "md5": "8dd2a2abbb8e639da8576b6e72ef9e59.svg", "type": "costume", "tags": [], "info": [ - 67, - 95, + 76, + 68, 1 ] }, { - "name": "Jez-d", - "md5": "569e736b519199efddfbae2572f7e92b.svg", + "name": "Jordyn-b", + "md5": "9665f543147961551d8dc6f612d9cc41.svg", "type": "costume", "tags": [], "info": [ - 67, - 95, + 76, + 68, 1 ] }, { - "name": "Jez-e", - "md5": "2261bed0f2cc819def17969158297b4f.svg", + "name": "Jordyn-c", + "md5": "ec8c2286070c77ebd9dd40c96eaae3fc.svg", "type": "costume", "tags": [], "info": [ - 77, - 95, + 76, + 68, 1 ] }, { - "name": "Jez-f", - "md5": "d7f44adb3dc7906b9dfb3599a028e0d6.svg", + "name": "Jordyn-d", + "md5": "1f9ed7f29800f31ce2ee53196143a3c8.svg", "type": "costume", "tags": [], "info": [ - 62, - 94, + 76, + 68, 1 ] }, { - "name": "Jordyn-a", - "md5": "8dd2a2abbb8e639da8576b6e72ef9e59.svg", + "name": "Keyboard-a", + "md5": "c67d180e964926b6393ac14781541b39.svg", "type": "costume", "tags": [], "info": [ - 76, + 72, 68, 1 ] }, { - "name": "Jordyn-b", - "md5": "9665f543147961551d8dc6f612d9cc41.svg", + "name": "Keyboard-b", + "md5": "dbaf62b33de45093c3c7d13b5d49d637.svg", "type": "costume", "tags": [], "info": [ - 76, + 72, 68, 1 ] }, { - "name": "Jordyn-c", - "md5": "ec8c2286070c77ebd9dd40c96eaae3fc.svg", + "name": "Kiran-a", + "md5": "9de23c4a7a7fbb67136b539241346854.svg", "type": "costume", "tags": [], "info": [ - 76, - 68, + 67, + 95, 1 ] }, { - "name": "Jordyn-d", - "md5": "1f9ed7f29800f31ce2ee53196143a3c8.svg", + "name": "Kiran-b", + "md5": "f1e74f3c02333e9e2068e8baf4e77aa0.svg", "type": "costume", "tags": [], "info": [ - 76, - 68, + 67, + 95, 1 ] }, { - "name": "Keyboard-a", - "md5": "c67d180e964926b6393ac14781541b39.svg", + "name": "Kiran-c", + "md5": "e2482cf509c312935f08be0e2e2c9d84.svg", "type": "costume", "tags": [], "info": [ - 72, - 68, + 67, + 95, 1 ] }, { - "name": "Keyboard-b", - "md5": "dbaf62b33de45093c3c7d13b5d49d637.svg", + "name": "Kiran-d", + "md5": "569e736b519199efddfbae2572f7e92b.svg", "type": "costume", "tags": [], "info": [ - 72, - 68, + 67, + 95, 1 ] }, { - "name": "Keyboard-c", - "md5": "4ee5e7c6d0463d9b50e0c593e70e1e31.svg", + "name": "Kiran-e", + "md5": "2261bed0f2cc819def17969158297b4f.svg", "type": "costume", "tags": [], "info": [ - 72, - 68, + 77, + 95, 1 ] }, { - "name": "Kiran-a", - "md5": "9de23c4a7a7fbb67136b539241346854.svg", + "name": "Kiran-f", + "md5": "d7f44adb3dc7906b9dfb3599a028e0d6.svg", "type": "costume", "tags": [], "info": [ - 67, - 95, + 62, + 94, 1 ] }, @@ -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 4f146abdce33fd2040952ed61dd4f8ac58b88791..aafa6f01e60bb262750ed6be475fa0fffaab1ed9 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 349bc3064d2fd97e65f3716042efae8aff4a5b29..2c12e02cd3463e9dabb8c0b12c3236a4f7e6f897 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,24 +5773,76 @@ "bitmapResolution": 1, "rotationCenterX": 47, "rotationCenterY": 80 + } + ], + "currentCostumeIndex": 0, + "scratchX": 137, + "scratchY": -13, + "scale": 1, + "direction": 90, + "rotationStyle": "normal", + "isDraggable": false, + "indexInLibrary": 9, + "visible": true, + "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": "saxophone-c", + "costumeName": "shark-b ", "baseLayerID": -1, - "baseLayerMD5": "29751826f768bc1568092f267525ae8b.svg", + "baseLayerMD5": "cff9ae87a93294693a0650b38a7a33d2.svg", "bitmapResolution": 1, - "rotationCenterX": 47, - "rotationCenterY": 80 + "rotationCenterX": 75, + "rotationCenterY": 75 + }, + { + "costumeName": "shark-c ", + "baseLayerID": -1, + "baseLayerMD5": "afeae3f998598424f7c50918507f6ce6.svg", + "bitmapResolution": 1, + "rotationCenterX": 77, + "rotationCenterY": 37 } ], "currentCostumeIndex": 0, - "scratchX": 137, - "scratchY": -13, + "scratchX": 41, + "scratchY": -15, "scale": 1, "direction": 90, "rotationStyle": "normal", "isDraggable": false, - "indexInLibrary": 9, + "indexInLibrary": 63, "visible": true, "spriteInfo": {} } @@ -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 306c5b548bb537f20681d353100ac0ac6a3a9a65..4bfb8b2e5f93e287bfd35c9fbe63a7ec8a328b06 100644 --- a/src/lib/make-toolbox-xml.js +++ b/src/lib/make-toolbox-xml.js @@ -234,10 +234,10 @@ const looks = function (isStage, targetId) { </block> <block type="looks_cleargraphiceffects"/> ${blockSeparator} - <block type="looks_show"/> - <block type="looks_hide"/> - ${blockSeparator} ${isStage ? '' : ` + <block type="looks_show"/> + <block type="looks_hide"/> + ${blockSeparator} <block type="looks_gotofrontback"/> <block type="looks_goforwardbackwardlayers"> <value name="NUM"> @@ -246,7 +246,6 @@ const looks = function (isStage, targetId) { </shadow> </value> </block> - ${blockSeparator} `} ${isStage ? ` <block id="backdropnumbername" type="looks_backdropnumbername"/> @@ -311,13 +310,17 @@ const sound = function () { `; }; -const events = function () { +const events = function (isStage) { return ` <category name="Events" colour="#FFD500" secondaryColour="#CC9900"> <block type="event_whenflagclicked"/> <block type="event_whenkeypressed"> </block> - <block type="event_whenthisspriteclicked"/> + ${isStage ? ` + <block type="event_whenstageclicked"/> + ` : ` + <block type="event_whenthisspriteclicked"/> + `} <block type="event_whenbackdropswitchesto"> </block> ${blockSeparator} @@ -431,7 +434,11 @@ const sensing = function (isStage) { </block> <block id="answer" type="sensing_answer"/> ${blockSeparator} - <block type="sensing_keypressed"/> + <block type="sensing_keypressed"> + <value name="KEY_OPTION"> + <shadow type="sensing_keyoptions"/> + </value> + </block> <block type="sensing_mousedown"/> <block type="sensing_mousex"/> <block type="sensing_mousey"/> diff --git a/src/lib/vm-listener-hoc.jsx b/src/lib/vm-listener-hoc.jsx index 8e7a0ca17d1062bb41491e2bbac5cb1b63dffeab..3f1453b548daf4aa77493a0330fbf349d1397c28 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 ba038ff3464e2d3c33373e5fb54f9d92fe1544f3..2a3772e59935d359a8345c8a422cf6a666b20ec1 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 b63ea2bf66ac321ae9dc68f79906af68c21d9d16..b18f356518c56c8154bc63b0acc9d856507e15f1 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 5b928b4c4fedbcafec4e60f941f6497f565cb5ac..a4633420483bde81f9626c5b08220ee37d09890e 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" -/> +> + <div + className={undefined} + /> +</span> `; 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 4ecf19565c1de4f56bef8dfb883fda7e6d199f56..eaca8d9366004e9eccf9bf21fa69da48b877e662 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`] = ` +<div + className="react-contextmenu-wrapper ponies undefined" + onClick={[Function]} + onContextMenu={[Function]} + onMouseDown={[Function]} + onMouseEnter={undefined} + onMouseLeave={undefined} + onMouseOut={[Function]} + onMouseUp={[Function]} + onTouchEnd={[Function]} + onTouchStart={[Function]} +> + <div + aria-label="Close" + className="" + onClick={[Function]} + role="button" + tabIndex="0" + > + <img + className={undefined} + src="test-file-stub" + /> + </div> + <div + className={undefined} + > + 5 + </div> + <canvas + className={undefined} + height={32} + style={ + Object { + "height": "32px", + "width": "32px", + } + } + width={32} + /> + <div + className={undefined} + > + Pony sprite + </div> + <nav + className="react-contextmenu" + onContextMenu={[Function]} + onMouseLeave={[Function]} + role="menu" + style={ + Object { + "opacity": 0, + "pointerEvents": "none", + "position": "fixed", + } + } + tabIndex="-1" + > + <div + aria-disabled="false" + aria-orientation={null} + className="react-contextmenu-item" + onClick={[Function]} + onMouseLeave={[Function]} + onMouseMove={[Function]} + onTouchEnd={[Function]} + role="menuitem" + tabIndex="-1" + > + <span> + delete + </span> + </div> + </nav> +</div> +`; + exports[`SpriteSelectorItemComponent matches snapshot when selected 1`] = ` <div className="react-contextmenu-wrapper ponies undefined" diff --git a/test/unit/components/sprite-selector-item.test.jsx b/test/unit/components/sprite-selector-item.test.jsx index e1d7ac7bb73ae23fb10ff018035a097ef0137e06..5848e0ca951ffea8f1a893c2cc20c7d51ef93954 100644 --- a/test/unit/components/sprite-selector-item.test.jsx +++ b/test/unit/components/sprite-selector-item.test.jsx @@ -11,6 +11,7 @@ describe('SpriteSelectorItemComponent', () => { 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());