diff --git a/src/components/action-menu/action-menu.css b/src/components/action-menu/action-menu.css
new file mode 100644
index 000000000..c222216b1
--- /dev/null
+++ b/src/components/action-menu/action-menu.css
@@ -0,0 +1,174 @@
+@import "../../css/colors.css";
+
+$main-button-size: 2.75rem;
+$more-button-size: 2.25rem;
+
+.menu-container {
+ display: flex;
+ flex-direction: column-reverse;
+ transition: 0.2s;
+ position: relative;
+}
+
+.button {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ background: $motion-primary;
+ outline: none;
+ border: none;
+ transition: background-color 0.2s;
+}
+
+.button:hover {
+ background: $pen-primary;
+}
+
+.button.coming-soon:hover {
+ background: $data-primary;
+}
+
+.main-button {
+ border-radius: 100%;
+ width: $main-button-size;
+ height: $main-button-size;
+ z-index: 20; /* TODO reorder layout to prevent z-index need */
+}
+
+.main-icon {
+ width: calc($main-button-size - 1rem);
+ height: calc($main-button-size - 1rem);
+}
+
+.more-buttons-outer {
+ /*
+ Need to use two divs to set different overflow x/y
+ which is needed to get animation to look right while
+ allowing the tooltips to be visible.
+ */
+ overflow-y: hidden;
+
+ background: $motion-tertiary;
+ border-top-left-radius: $more-button-size;
+ border-top-right-radius: $more-button-size;
+ width: $more-button-size;
+ margin-left: calc(($main-button-size - $more-button-size) / 2);
+
+ position: absolute;
+ bottom: calc($main-button-size);
+
+ margin-bottom: calc($main-button-size / -2);
+ padding-bottom: calc($main-button-size / 2);
+}
+
+.more-buttons {
+ max-height: 0;
+ transition: max-height 1s;
+ overflow-x: visible;
+ display: flex;
+ flex-direction: column;
+ z-index: 10; /* @todo justify */
+}
+
+.expanded .more-buttons {
+ max-height: 1000px; /* Arbitrary, needs to be a value in order for animation to run */
+}
+
+.force-hidden .more-buttons {
+ display: none; /* This property does not animate */
+}
+
+.more-buttons:first-child { /* Round off top button */
+ border-top-right-radius: $more-button-size;
+ border-top-left-radius: $more-button-size;
+}
+
+.more-button {
+ width: $more-button-size;
+ height: $more-button-size;
+ background: $motion-tertiary;
+}
+
+.more-icon {
+ width: calc($more-button-size - 1rem);
+ height: calc($more-button-size - 1rem);
+}
+
+.coming-soon .more-icon {
+ opacity: 0.5;
+}
+
+/*
+ @todo needs to be refactored with coming soon tooltip overrides.
+ The "!important"s are for the same reason as with coming soon, the library
+ is not very easy to style.
+*/
+.tooltip {
+ background-color: $pen-primary !important;
+ opacity: 1 !important;
+ border: 1px solid hsla(0, 0%, 0%, .1) !important;
+ box-shadow: 0 0 .5rem hsla(0, 0%, 0%, .25) !important;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif !important;
+}
+
+.tooltip:after {
+ background-color: $pen-primary;
+}
+
+.coming-soon-tooltip {
+ background-color: $data-primary !important;
+}
+
+.coming-soon-tooltip:after {
+ background-color: $data-primary !important;
+}
+
+.tooltip {
+ border: 1px solid hsla(0, 0%, 0%, .1) !important;
+ border-radius: .25rem !important;
+ box-shadow: 0 0 .5rem hsla(0, 0%, 0%, .25) !important;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif !important;
+ z-index: 100 !important;
+}
+
+$arrow-size: 0.5rem;
+$arrow-inset: -0.25rem;
+$arrow-rounding: 0.125rem;
+
+.tooltip:after {
+ content: "";
+ border-top: 1px solid hsla(0, 0%, 0%, .1) !important;
+ border-left: 0 !important;
+ border-bottom: 0 !important;
+ border-right: 1px solid hsla(0, 0%, 0%, .1) !important;
+ border-radius: $arrow-rounding;
+ height: $arrow-size !important;
+ width: $arrow-size !important;
+}
+
+.tooltip:global(.place-left):after {
+ margin-top: $arrow-inset !important;
+ right: $arrow-inset !important;
+ transform: rotate(45deg) !important;
+}
+
+.tooltip:global(.place-right):after {
+ margin-top: $arrow-inset !important;
+ left: $arrow-inset !important;
+ transform: rotate(-135deg) !important;
+}
+
+.tooltip:global(.place-top):after {
+ margin-right: $arrow-inset !important;
+ bottom: $arrow-inset !important;
+ transform: rotate(135deg) !important;
+}
+
+.tooltip:global(.place-bottom):after {
+ margin-left: $arrow-inset !important;
+ top: $arrow-inset !important;
+ transform: rotate(-45deg) !important;
+}
diff --git a/src/components/action-menu/action-menu.jsx b/src/components/action-menu/action-menu.jsx
new file mode 100644
index 000000000..750a0ea3e
--- /dev/null
+++ b/src/components/action-menu/action-menu.jsx
@@ -0,0 +1,142 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import classNames from 'classnames';
+import bindAll from 'lodash.bindall';
+import ReactTooltip from 'react-tooltip';
+
+import styles from './action-menu.css';
+
+const CLOSE_DELAY = 300; // ms
+
+class ActionMenu extends React.Component {
+ constructor (props) {
+ super(props);
+ bindAll(this, [
+ 'handleClosePopover',
+ 'handleToggleOpenState',
+ 'clickDelayer'
+ ]);
+ this.state = {
+ isOpen: false,
+ forceHide: false
+ };
+ }
+ handleClosePopover () {
+ this.closeTimeoutId = setTimeout(() => {
+ this.setState({isOpen: false});
+ this.closeTimeoutId = null;
+ }, CLOSE_DELAY);
+ }
+ handleToggleOpenState () {
+ if (this.closeTimeoutId) {
+ clearTimeout(this.closeTimeoutId);
+ this.closeTimeoutId = null;
+ } else {
+ this.setState({
+ isOpen: true,
+ forceHide: false
+ });
+ }
+ }
+ clickDelayer (fn) {
+ // Return a wrapped action that manages the menu closing.
+ // @todo we may be able to use react-transition for this in the future
+ // for now all this work is to ensure the menu closes BEFORE the
+ // (possibly slow) action is started.
+ return event => {
+ this.setState({forceHide: true, isOpen: false}, () => {
+ if (fn) fn(event);
+ setTimeout(() => this.setState({forceHide: false}));
+ });
+ };
+ }
+ render () {
+ const {
+ className,
+ img: mainImg,
+ title: mainTitle,
+ moreButtons,
+ onClick
+ } = this.props;
+
+ const mainTooltipId = `tooltip-${Math.random()}`;
+
+ return (
+
+
+
+
+
+ {(moreButtons || []).map(({img, title, onClick: handleClick}) => {
+ const isComingSoon = !handleClick;
+ const tooltipId = `tooltip-${Math.random()}`;
+ return (
+
+
+
+
+ );
+ })}
+
+
+
+ );
+ }
+}
+
+ActionMenu.propTypes = {
+ className: PropTypes.string,
+ img: PropTypes.string,
+ moreButtons: PropTypes.arrayOf(PropTypes.shape({
+ img: PropTypes.string,
+ title: PropTypes.node.isRequired,
+ onClick: PropTypes.func // Optional, "coming soon" if no callback provided
+ })),
+ onClick: PropTypes.func.isRequired,
+ title: PropTypes.node.isRequired
+};
+
+export default ActionMenu;
diff --git a/src/components/stage-selector/icon--backdrop.svg b/src/components/action-menu/icon--backdrop.svg
similarity index 100%
rename from src/components/stage-selector/icon--backdrop.svg
rename to src/components/action-menu/icon--backdrop.svg
diff --git a/src/components/action-menu/icon--camera.svg b/src/components/action-menu/icon--camera.svg
new file mode 100644
index 000000000..e8c442d8f
--- /dev/null
+++ b/src/components/action-menu/icon--camera.svg
@@ -0,0 +1,12 @@
+
+
\ No newline at end of file
diff --git a/src/components/action-menu/icon--file-upload.svg b/src/components/action-menu/icon--file-upload.svg
new file mode 100644
index 000000000..57337d959
--- /dev/null
+++ b/src/components/action-menu/icon--file-upload.svg
@@ -0,0 +1,12 @@
+
+
\ No newline at end of file
diff --git a/src/components/action-menu/icon--paint.svg b/src/components/action-menu/icon--paint.svg
new file mode 100644
index 000000000..f79d562c3
--- /dev/null
+++ b/src/components/action-menu/icon--paint.svg
@@ -0,0 +1,12 @@
+
+
\ No newline at end of file
diff --git a/src/components/sprite-selector/icon--sprite.svg b/src/components/action-menu/icon--sprite.svg
similarity index 100%
rename from src/components/sprite-selector/icon--sprite.svg
rename to src/components/action-menu/icon--sprite.svg
diff --git a/src/components/action-menu/icon--surprise.svg b/src/components/action-menu/icon--surprise.svg
new file mode 100644
index 000000000..41655999f
--- /dev/null
+++ b/src/components/action-menu/icon--surprise.svg
@@ -0,0 +1,12 @@
+
+
\ No newline at end of file
diff --git a/src/components/asset-button/asset-button.css b/src/components/asset-button/asset-button.css
deleted file mode 100644
index b36fa76b8..000000000
--- a/src/components/asset-button/asset-button.css
+++ /dev/null
@@ -1,21 +0,0 @@
-@import "../../css/colors.css";
-
-.container {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- cursor: pointer;
- font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
- background: $motion-primary;
- border-radius: 100%;
- width: 2.75rem;
- height: 2.75rem;
- outline: none;
- border: none;
-}
-
-.icon {
- width: 1.75rem;
- height: 1.75rem;
-}
diff --git a/src/components/asset-button/asset-button.jsx b/src/components/asset-button/asset-button.jsx
deleted file mode 100644
index 088838990..000000000
--- a/src/components/asset-button/asset-button.jsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import PropTypes from 'prop-types';
-import React from 'react';
-import classNames from 'classnames';
-import styles from './asset-button.css';
-
-const AssetButton = ({
- img,
- className,
- title,
- onClick
-}) => (
-
-);
-
-AssetButton.propTypes = {
- className: PropTypes.string,
- img: PropTypes.string,
- onClick: PropTypes.func.isRequired,
- title: PropTypes.node.isRequired
-};
-
-export default AssetButton;
diff --git a/src/components/asset-panel/selector.css b/src/components/asset-panel/selector.css
index 2a19296d6..24a5c2ec6 100644
--- a/src/components/asset-panel/selector.css
+++ b/src/components/asset-panel/selector.css
@@ -11,13 +11,30 @@
}
.new-buttons {
+ position: absolute;
+ bottom: 0;
+ width: 100%;
+
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-around;
- margin: 0.75rem 0;
+ padding: 0.75rem 0;
color: $motion-primary;
text-align: center;
+ background: none;
+}
+
+$fade-out-distance: 100px;
+
+.new-buttons:before {
+ content: "";
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ background: linear-gradient(rgba(232,237,241, 0),rgba(232,237,241, 1));
+ height: $fade-out-distance;
+ width: 100%;
}
.new-buttons > button + button {
diff --git a/src/components/asset-panel/selector.jsx b/src/components/asset-panel/selector.jsx
index b69707781..51094059f 100644
--- a/src/components/asset-panel/selector.jsx
+++ b/src/components/asset-panel/selector.jsx
@@ -4,7 +4,7 @@ import React from 'react';
import SpriteSelectorItem from '../../containers/sprite-selector-item.jsx';
import Box from '../box/box.jsx';
-import AssetButton from '../asset-button/asset-button.jsx';
+import ActionMenu from '../action-menu/action-menu.jsx';
import styles from './selector.css';
const Selector = props => {
@@ -36,14 +36,12 @@ const Selector = props => {
))}
- {buttons.map(({message, img, onClick}, index) => (
-
- ))}
+
);
diff --git a/src/components/green-flag/green-flag.css b/src/components/green-flag/green-flag.css
index 2c1452838..35ba29726 100644
--- a/src/components/green-flag/green-flag.css
+++ b/src/components/green-flag/green-flag.css
@@ -5,6 +5,7 @@
padding: 0.375rem;
border-radius: 0.25rem;
user-select: none;
+ user-drag: none;
cursor: pointer;
transition: 0.2s ease-out;
}
diff --git a/src/components/gui/gui.css b/src/components/gui/gui.css
index 92b83acc2..5132af685 100644
--- a/src/components/gui/gui.css
+++ b/src/components/gui/gui.css
@@ -140,7 +140,8 @@
For making the sprite-selector a scrollable pane
@todo: Not working in Safari
*/
- overflow: hidden;
+ /* TODO this also breaks the thermometer menu */
+ /* overflow: hidden; */
}
.extension-button-container {
@@ -156,6 +157,19 @@
box-sizing: content-box; /* To match scratch-block vertical toolbox borders */
}
+$fade-out-distance: 15px;
+
+.extension-button-container:before {
+ content: "";
+ position: absolute;
+ top: calc(calc(-1 * $fade-out-distance) - 1px);
+ left: -1px;
+ background: linear-gradient(rgba(0, 0, 0, 0),rgba(0, 0, 0, 0.15));
+ height: $fade-out-distance;
+ width: calc(100% + 0.5px);
+}
+
+
.extension-button {
background: none;
border: none;
diff --git a/src/components/sprite-selector/sprite-selector.css b/src/components/sprite-selector/sprite-selector.css
index 67b0532d3..4cb03ecd5 100644
--- a/src/components/sprite-selector/sprite-selector.css
+++ b/src/components/sprite-selector/sprite-selector.css
@@ -2,7 +2,7 @@
.sprite-selector {
flex-grow: 1;
- position: relative;
+ position: relative;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
margin-right: calc($space / 2);
background-color: #f9f9f9;
@@ -16,14 +16,14 @@
/* In prep for renaming sprite-selector-item to sprite */
.sprite {
- /*
+ /*
Our goal is to fit sprites evenly in a row without leftover space.
- Flexbox's `space between` property gets us close, but doesn't flow
+ Flexbox's `space between` property gets us close, but doesn't flow
well when the # of items per row > 1 and less than the max per row.
- Solving by explicitly calc'ing the width of each sprite. Setting
- `border-box` simplifies things, because content, padding and
- border-width all are included in the width, leaving us only to subtract
+ Solving by explicitly calc'ing the width of each sprite. Setting
+ `border-box` simplifies things, because content, padding and
+ border-width all are included in the width, leaving us only to subtract
the left + right margins.
@todo: make room for the scrollbar
@@ -32,7 +32,7 @@
width: calc((100% / $sprites-per-row ) - $space);
min-width: 4rem;
min-height: 4rem; /* @todo: calc height same as width */
- margin: calc($space / 2);
+ margin: calc($space / 2);
}
@@ -41,11 +41,11 @@
Sets the sprite-selector items as a scrollable pane
@todo: Safari: pane doesn't stretch to fill height;
- @todo: Adding `position: relative` still doesn't fix Safari scrolling pane, and
- also introduces a new bug in Chrome when vertically resizing window down,
- then back up, introduces white space in the outside the page container.
+ @todo: Adding `position: relative` still doesn't fix Safari scrolling pane, and
+ also introduces a new bug in Chrome when vertically resizing window down,
+ then back up, introduces white space in the outside the page container.
*/
- height: calc(100% - $sprite-info-height);
+ height: calc(100% - $sprite-info-height);
overflow-y: scroll;
}
@@ -57,11 +57,12 @@
padding-top: calc($space / 2);
padding-left: calc($space / 2);
padding-right: calc($space / 2);
- padding-bottom: $space;
+ padding-bottom: $space;
}
.add-button {
position: absolute;
bottom: 0.75rem;
right: 1rem;
+ z-index: 1; /* TODO overlaps the stage, this doesn't work, fix! */
}
diff --git a/src/components/sprite-selector/sprite-selector.jsx b/src/components/sprite-selector/sprite-selector.jsx
index 41e51a316..9817386f7 100644
--- a/src/components/sprite-selector/sprite-selector.jsx
+++ b/src/components/sprite-selector/sprite-selector.jsx
@@ -5,16 +5,41 @@ import {defineMessages, injectIntl, intlShape} from 'react-intl';
import Box from '../box/box.jsx';
import SpriteInfo from '../../containers/sprite-info.jsx';
import SpriteSelectorItem from '../../containers/sprite-selector-item.jsx';
-import AssetButton from '../asset-button/asset-button.jsx';
+import ActionMenu from '../action-menu/action-menu.jsx';
import styles from './sprite-selector.css';
-import spriteIcon from './icon--sprite.svg';
+
+import cameraIcon from '../action-menu/icon--camera.svg';
+import fileUploadIcon from '../action-menu/icon--file-upload.svg';
+import paintIcon from '../action-menu/icon--paint.svg';
+import spriteIcon from '../action-menu/icon--sprite.svg';
+import surpriseIcon from '../action-menu/icon--surprise.svg';
const messages = defineMessages({
- addSprite: {
- id: 'gui.spriteSelector.addSprite',
- description: 'Button to add a sprite in the target pane',
- defaultMessage: 'Add Sprite'
+ addSpriteFromLibrary: {
+ id: 'gui.spriteSelector.addSpriteFromLibrary',
+ description: 'Button to add a sprite in the target pane from library',
+ defaultMessage: 'Library'
+ },
+ addSpriteFromPaint: {
+ id: 'gui.spriteSelector.addSpriteFromPaint',
+ description: 'Button to add a sprite in the target pane from paint',
+ defaultMessage: 'Paint'
+ },
+ addSpriteFromSurprise: {
+ id: 'gui.spriteSelector.addSpriteFromSurprise',
+ description: 'Button to add a random sprite in the target pane',
+ defaultMessage: 'Surprise'
+ },
+ addSpriteFromFile: {
+ id: 'gui.spriteSelector.addSpriteFromFile',
+ description: 'Button to add a sprite in the target pane from file',
+ defaultMessage: 'Coming Soon'
+ },
+ addSpriteFromCamera: {
+ id: 'gui.spriteSelector.addSpriteFromCamera',
+ description: 'Button to add a sprite in the target pane from camera',
+ defaultMessage: 'Coming Soon'
}
});
@@ -30,6 +55,8 @@ const SpriteSelectorComponent = function (props) {
onDeleteSprite,
onDuplicateSprite,
onNewSpriteClick,
+ onSurpriseSpriteClick,
+ onPaintSpriteClick,
onSelectSprite,
selectedId,
sprites,
@@ -85,10 +112,27 @@ const SpriteSelectorComponent = function (props) {
}
-
diff --git a/src/components/stage-selector/stage-selector.jsx b/src/components/stage-selector/stage-selector.jsx
index eea811409..bb2771072 100644
--- a/src/components/stage-selector/stage-selector.jsx
+++ b/src/components/stage-selector/stage-selector.jsx
@@ -4,16 +4,41 @@ import React from 'react';
import {defineMessages, intlShape, injectIntl, FormattedMessage} from 'react-intl';
import Box from '../box/box.jsx';
-import AssetButton from '../asset-button/asset-button.jsx';
+import ActionMenu from '../action-menu/action-menu.jsx';
import CostumeCanvas from '../costume-canvas/costume-canvas.jsx';
import styles from './stage-selector.css';
-import backdropIcon from './icon--backdrop.svg';
+
+import backdropIcon from '../action-menu/icon--backdrop.svg';
+import cameraIcon from '../action-menu/icon--camera.svg';
+import fileUploadIcon from '../action-menu/icon--file-upload.svg';
+import paintIcon from '../action-menu/icon--paint.svg';
+import surpriseIcon from '../action-menu/icon--surprise.svg';
const messages = defineMessages({
- addBackdrop: {
- id: 'gui.stageSelector.targetPaneAddBackdrop',
- description: 'Button to add a backdrop in the target pane',
- defaultMessage: 'Add Backdrop'
+ addBackdropFromLibrary: {
+ id: 'gui.spriteSelector.addBackdropFromLibrary',
+ description: 'Button to add a stage in the target pane from library',
+ defaultMessage: 'Library'
+ },
+ addBackdropFromPaint: {
+ id: 'gui.stageSelector.addBackdropFromPaint',
+ description: 'Button to add a stage in the target pane from paint',
+ defaultMessage: 'Paint'
+ },
+ addBackdropFromSurprise: {
+ id: 'gui.stageSelector.addBackdropFromSurprise',
+ description: 'Button to add a random stage in the target pane',
+ defaultMessage: 'Surprise'
+ },
+ addBackdropFromFile: {
+ id: 'gui.stageSelector.addBackdropFromFile',
+ description: 'Button to add a stage in the target pane from file',
+ defaultMessage: 'Coming Soon'
+ },
+ addBackdropFromCamera: {
+ id: 'gui.stageSelector.addBackdropFromCamera',
+ description: 'Button to add a stage in the target pane from camera',
+ defaultMessage: 'Coming Soon'
}
});
@@ -25,6 +50,8 @@ const StageSelector = props => {
url,
onClick,
onNewBackdropClick,
+ onSurpriseBackdropClick,
+ onEmptyBackdropClick,
...componentProps
} = props;
return (
@@ -54,10 +81,28 @@ const StageSelector = props => {
/>
{backdropCount}
-
@@ -72,4 +117,5 @@ StageSelector.propTypes = {
selected: PropTypes.bool.isRequired,
url: PropTypes.string
};
+
export default injectIntl(StageSelector);
diff --git a/src/components/target-pane/target-pane.jsx b/src/components/target-pane/target-pane.jsx
index 30267688f..da19c36fc 100644
--- a/src/components/target-pane/target-pane.jsx
+++ b/src/components/target-pane/target-pane.jsx
@@ -29,6 +29,8 @@ const TargetPane = ({
onDeleteSprite,
onDuplicateSprite,
onNewSpriteClick,
+ onSurpriseSpriteClick,
+ onPaintSpriteClick,
onRequestCloseSpriteLibrary,
onRequestCloseBackdropLibrary,
onSelectSprite,
@@ -54,7 +56,9 @@ const TargetPane = ({
onDeleteSprite={onDeleteSprite}
onDuplicateSprite={onDuplicateSprite}
onNewSpriteClick={onNewSpriteClick}
+ onSurpriseSpriteClick={onSurpriseSpriteClick}
onSelectSprite={onSelectSprite}
+ onPaintSpriteClick={onPaintSpriteClick}
/>
{stage.id &&
2 ? item.info[2] : 1,
+ skinId: null
+ };
+ this.props.vm.addCostume(item.md5, vmCostume).then(() => {
+ this.handleNewCostume();
+ });
+ }
+ handleSurpriseBackdrop () {
+ const item = backdropLibraryContent[Math.floor(Math.random() * backdropLibraryContent.length)];
+ const vmCostume = {
+ name: item.name,
+ rotationCenterX: item.info[0] && item.info[0] / 2,
+ rotationCenterY: item.info[1] && item.info[1] / 2,
+ bitmapResolution: item.info.length > 2 ? item.info[2] : 1,
+ skinId: null
+ };
+ this.props.vm.addCostume(item.md5, vmCostume).then(() => {
+ this.handleNewCostume();
+ });
+ }
render () {
// For paint wrapper
const {
@@ -139,8 +179,7 @@ class CostumeTab extends React.Component {
return null;
}
- const addLibraryMessage = target.isStage ? messages.addLibraryBackdropMsg : messages.addLibraryCostumeMsg;
- const addBlankMessage = target.isStage ? messages.addBlankBackdropMsg : messages.addBlankCostumeMsg;
+ const addSurpriseFunc = target.isStage ? this.handleSurpriseBackdrop : this.handleSurpriseCostume;
const addLibraryFunc = target.isStage ? onNewLibraryBackdropClick : onNewLibraryCostumeClick;
const addLibraryIcon = target.isStage ? addLibraryBackdropIcon : addLibraryCostumeIcon;
@@ -148,14 +187,27 @@ class CostumeTab extends React.Component {
{
+ this.handleNewSound();
+ });
+ }
+
render () {
const {
intl,
@@ -94,13 +114,23 @@ class SoundTab extends React.Component {
)) : [];
const messages = defineMessages({
+ fileUploadSound: {
+ defaultMessage: 'Coming Soon',
+ description: 'Button to upload sound from file in the editor tab',
+ id: 'gui.soundTab.fileUploadSound'
+ },
+ surpriseSound: {
+ defaultMessage: 'Surprise',
+ description: 'Button to get a random sound in the editor tab',
+ id: 'gui.soundTab.surpriseSound'
+ },
recordSound: {
- defaultMessage: 'Record Sound',
+ defaultMessage: 'Record',
description: 'Button to record a sound in the editor tab',
id: 'gui.soundTab.recordSound'
},
addSound: {
- defaultMessage: 'Add Sound',
+ defaultMessage: 'Library',
description: 'Button to add a sound in the editor tab',
id: 'gui.soundTab.addSound'
}
@@ -109,13 +139,20 @@ class SoundTab extends React.Component {
return (
({
url: soundIcon,
diff --git a/src/containers/stage-selector.jsx b/src/containers/stage-selector.jsx
index 83a5a1bb0..3d280bd16 100644
--- a/src/containers/stage-selector.jsx
+++ b/src/containers/stage-selector.jsx
@@ -4,19 +4,51 @@ import React from 'react';
import {connect} from 'react-redux';
import {openBackdropLibrary} from '../reducers/modals';
+import {activateTab, COSTUMES_TAB_INDEX} from '../reducers/editor-tab';
+
import StageSelectorComponent from '../components/stage-selector/stage-selector.jsx';
+import backdropLibraryContent from '../lib/libraries/backdrops.json';
+import costumeLibraryContent from '../lib/libraries/costumes.json';
+
class StageSelector extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
- 'handleClick'
+ 'handleClick',
+ 'handleSurpriseBackdrop',
+ 'handleEmptyBackdrop',
+ 'addBackdropFromLibraryItem'
]);
}
+ addBackdropFromLibraryItem (item) {
+ const vmBackdrop = {
+ name: item.name,
+ rotationCenterX: item.info[0] && item.info[0] / 2,
+ rotationCenterY: item.info[1] && item.info[1] / 2,
+ bitmapResolution: item.info.length > 2 ? item.info[2] : 1,
+ skinId: null
+ };
+ return this.props.vm.addBackdrop(item.md5, vmBackdrop);
+ }
handleClick (e) {
e.preventDefault();
this.props.onSelect(this.props.id);
}
+ handleSurpriseBackdrop () {
+ // @todo should this not add a backdrop you already have?
+ const item = backdropLibraryContent[Math.floor(Math.random() * backdropLibraryContent.length)];
+ this.addBackdropFromLibraryItem(item);
+ }
+ handleEmptyBackdrop () {
+ // @todo this is brittle, will need to be refactored for localized libraries
+ const emptyItem = costumeLibraryContent.find(item => item.name === 'Empty');
+ if (emptyItem) {
+ this.addBackdropFromLibraryItem(emptyItem).then(() => {
+ this.props.onActivateTab(COSTUMES_TAB_INDEX);
+ });
+ }
+ }
render () {
const {
/* eslint-disable no-unused-vars */
@@ -29,6 +61,8 @@ class StageSelector extends React.Component {
return (
);
@@ -41,13 +75,17 @@ StageSelector.propTypes = {
};
const mapStateToProps = (state, {assetId}) => ({
- url: assetId && state.vm.runtime.storage.get(assetId).encodeDataURI()
+ url: assetId && state.vm.runtime.storage.get(assetId).encodeDataURI(),
+ vm: state.vm
});
const mapDispatchToProps = dispatch => ({
onNewBackdropClick: e => {
e.preventDefault();
dispatch(openBackdropLibrary());
+ },
+ onActivateTab: tabIndex => {
+ dispatch(activateTab(tabIndex));
}
});
diff --git a/src/containers/target-pane.jsx b/src/containers/target-pane.jsx
index 32ad448bc..a1665f808 100644
--- a/src/containers/target-pane.jsx
+++ b/src/containers/target-pane.jsx
@@ -9,7 +9,10 @@ import {
closeSpriteLibrary
} from '../reducers/modals';
+import {activateTab, COSTUMES_TAB_INDEX} from '../reducers/editor-tab';
+
import TargetPaneComponent from '../components/target-pane/target-pane.jsx';
+import spriteLibraryContent from '../lib/libraries/sprites.json';
class TargetPane extends React.Component {
constructor (props) {
@@ -23,7 +26,9 @@ class TargetPane extends React.Component {
'handleChangeSpriteY',
'handleDeleteSprite',
'handleDuplicateSprite',
- 'handleSelectSprite'
+ 'handleSelectSprite',
+ 'handleSurpriseSpriteClick',
+ 'handlePaintSpriteClick'
]);
}
handleChangeSpriteDirection (direction) {
@@ -53,6 +58,19 @@ class TargetPane extends React.Component {
handleSelectSprite (id) {
this.props.vm.setEditingTarget(id);
}
+ handleSurpriseSpriteClick () {
+ const item = spriteLibraryContent[Math.floor(Math.random() * spriteLibraryContent.length)];
+ this.props.vm.addSprite2(JSON.stringify(item.json));
+ }
+ handlePaintSpriteClick () {
+ // @todo this is brittle, will need to be refactored for localized libraries
+ 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);
+ });
+ }
+ }
render () {
return (
);
}
@@ -105,6 +125,9 @@ const mapDispatchToProps = dispatch => ({
},
onRequestCloseBackdropLibrary: () => {
dispatch(closeBackdropLibrary());
+ },
+ onActivateTab: tabIndex => {
+ dispatch(activateTab(tabIndex));
}
});
diff --git a/src/css/colors.css b/src/css/colors.css
index e270217f9..f4611c06e 100644
--- a/src/css/colors.css
+++ b/src/css/colors.css
@@ -18,4 +18,6 @@ $control-primary: #FFAB19;
$data-primary: #FF8C1A;
+$pen-primary: #11B581;
+
$form-border: #E9EEF2;