diff --git a/src/components/sprite-selector/sprite-selector.css b/src/components/sprite-selector/sprite-selector.css
index 781da192d..368a3451c 100644
--- a/src/components/sprite-selector/sprite-selector.css
+++ b/src/components/sprite-selector/sprite-selector.css
@@ -112,3 +112,34 @@
75% {transform: rotate(-2deg) scale(1.05);}
100% {transform: rotate(0deg) scale(1.05);}
}
+
+.receivedBlocks {
+ -webkit-animation: glowing 250ms;
+ -moz-animation: glowing 250ms;
+ -o-animation: glowing 250ms;
+ animation: glowing 250ms;
+}
+
+@-webkit-keyframes glowing {
+ 10% { -webkit-box-shadow: 0 0 10px #7fff1e; }
+ 90% { -webkit-box-shadow: 0 0 10px #7fff1e; }
+ 100% { -webkit-box-shadow: none; }
+}
+
+@-moz-keyframes glowing {
+ 10% { -moz-box-shadow: 0 0 10px #7fff1e; }
+ 90% { -moz-box-shadow: 0 0 10px #7fff1e; }
+ 100% { -moz-box-shadow: none; }
+}
+
+@-o-keyframes glowing {
+ 10% { box-shadow: 0 0 10px #7fff1e; }
+ 90% { box-shadow: 0 0 10px #7fff1e; }
+ 100% { box-shadow: none; }
+}
+
+@keyframes glowing {
+ 10% { box-shadow: 0 0 10px #7fff1e; }
+ 90% { box-shadow: 0 0 10px #7fff1e; }
+ 100% { box-shadow: none; }
+}
\ No newline at end of file
diff --git a/src/components/sprite-selector/sprite-selector.jsx b/src/components/sprite-selector/sprite-selector.jsx
index a8cd3fc3f..906479d71 100644
--- a/src/components/sprite-selector/sprite-selector.jsx
+++ b/src/components/sprite-selector/sprite-selector.jsx
@@ -21,6 +21,8 @@ const messages = defineMessages({
const SpriteSelectorComponent = function (props) {
const {
+ editingTarget,
+ hoveredTarget,
intl,
onChangeSpriteDirection,
onChangeSpriteName,
@@ -30,8 +32,6 @@ const SpriteSelectorComponent = function (props) {
onChangeSpriteY,
onDeleteSprite,
onDuplicateSprite,
- onMouseOutSprite,
- onMouseOverSprite,
onNewSpriteClick,
onSelectSprite,
raised,
@@ -76,10 +76,12 @@ const SpriteSelectorComponent = function (props) {
.map(sprite => (
))
}
@@ -105,6 +105,11 @@ const SpriteSelectorComponent = function (props) {
};
SpriteSelectorComponent.propTypes = {
+ editingTarget: PropTypes.string,
+ hoveredTarget: PropTypes.shape({
+ hoveredSprite: PropTypes.string,
+ receivedBlocks: PropTypes.bool
+ }),
intl: intlShape.isRequired,
onChangeSpriteDirection: PropTypes.func,
onChangeSpriteName: PropTypes.func,
@@ -114,8 +119,6 @@ SpriteSelectorComponent.propTypes = {
onChangeSpriteY: PropTypes.func,
onDeleteSprite: PropTypes.func,
onDuplicateSprite: PropTypes.func,
- onMouseOutSprite: PropTypes.func,
- onMouseOverSprite: PropTypes.func,
onNewSpriteClick: PropTypes.func,
onSelectSprite: PropTypes.func,
raised: PropTypes.bool,
diff --git a/src/components/target-pane/target-pane.jsx b/src/components/target-pane/target-pane.jsx
index 31ae88485..014ed6498 100644
--- a/src/components/target-pane/target-pane.jsx
+++ b/src/components/target-pane/target-pane.jsx
@@ -19,6 +19,7 @@ import styles from './target-pane.css';
const TargetPane = ({
backdropLibraryVisible,
editingTarget,
+ hoveredTarget,
spriteLibraryVisible,
onChangeSpriteDirection,
onChangeSpriteName,
@@ -28,8 +29,6 @@ const TargetPane = ({
onChangeSpriteY,
onDeleteSprite,
onDuplicateSprite,
- onMouseOutSprite,
- onMouseOverSprite,
onNewSpriteClick,
onRequestCloseSpriteLibrary,
onRequestCloseBackdropLibrary,
@@ -46,6 +45,8 @@ const TargetPane = ({
>
@@ -113,6 +112,10 @@ TargetPane.propTypes = {
backdropLibraryVisible: PropTypes.bool,
editingTarget: PropTypes.string,
extensionLibraryVisible: PropTypes.bool,
+ hoveredTarget: PropTypes.shape({
+ hoveredSprite: PropTypes.string,
+ receivedBlocks: PropTypes.bool
+ }),
onChangeSpriteDirection: PropTypes.func,
onChangeSpriteName: PropTypes.func,
onChangeSpriteSize: PropTypes.func,
@@ -121,8 +124,6 @@ TargetPane.propTypes = {
onChangeSpriteY: PropTypes.func,
onDeleteSprite: PropTypes.func,
onDuplicateSprite: PropTypes.func,
- onMouseOutSprite: PropTypes.func,
- onMouseOverSprite: PropTypes.func,
onNewSpriteClick: PropTypes.func,
onRequestCloseBackdropLibrary: PropTypes.func,
onRequestCloseExtensionLibrary: PropTypes.func,
diff --git a/src/containers/sprite-selector-item.jsx b/src/containers/sprite-selector-item.jsx
index 87e7db36b..4b81507a0 100644
--- a/src/containers/sprite-selector-item.jsx
+++ b/src/containers/sprite-selector-item.jsx
@@ -1,9 +1,10 @@
import bindAll from 'lodash.bindall';
import PropTypes from 'prop-types';
import React from 'react';
-
import {connect} from 'react-redux';
+import {setHoveredSprite} from '../reducers/hovered-target';
+
import SpriteSelectorItemComponent from '../components/sprite-selector-item/sprite-selector-item.jsx';
class SpriteSelectorItem extends React.Component {
@@ -33,10 +34,10 @@ class SpriteSelectorItem extends React.Component {
this.props.onDuplicateButtonClick(this.props.id);
}
handleMouseOut () {
- this.props.onMouseOut(this.props.id);
+ this.props.dispatchSetHoveredSprite(null);
}
handleMouseOver () {
- this.props.onMouseOver(this.props.id);
+ this.props.dispatchSetHoveredSprite(this.props.id);
}
render () {
const {
@@ -46,8 +47,7 @@ class SpriteSelectorItem extends React.Component {
onClick,
onDeleteButtonClick,
onDuplicateButtonClick,
- onMouseOut,
- onMouseOver,
+ receivedBlocks,
/* eslint-enable no-unused-vars */
...props
} = this.props;
@@ -56,8 +56,8 @@ class SpriteSelectorItem extends React.Component {
onClick={this.handleClick}
onDeleteButtonClick={onDeleteButtonClick ? this.handleDelete : null}
onDuplicateButtonClick={onDuplicateButtonClick ? this.handleDuplicate : null}
- onMouseOut={onMouseOut ? this.handleMouseOut : null}
- onMouseOver={onMouseOver ? this.handleMouseOver : null}
+ onMouseOut={this.handleMouseOut}
+ onMouseOver={this.handleMouseOver}
{...props}
/>
);
@@ -67,20 +67,28 @@ class SpriteSelectorItem extends React.Component {
SpriteSelectorItem.propTypes = {
assetId: PropTypes.string,
costumeURL: PropTypes.string,
+ dispatchSetHoveredSprite: PropTypes.func.isRequired,
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
name: PropTypes.string,
onClick: PropTypes.func,
onDeleteButtonClick: PropTypes.func,
onDuplicateButtonClick: PropTypes.func,
- onMouseOut: PropTypes.func,
- onMouseOver: PropTypes.func,
+ receivedBlocks: PropTypes.bool,
selected: PropTypes.bool
};
-const mapStateToProps = (state, {assetId, costumeURL}) => ({
- costumeURL: costumeURL || (assetId && state.vm.runtime.storage.get(assetId).encodeDataURI())
+const mapStateToProps = (state, {assetId, costumeURL, id}) => ({
+ costumeURL: costumeURL || (assetId && state.vm.runtime.storage.get(assetId).encodeDataURI()),
+ receivedBlocks: state.hoveredTarget.receivedBlocks &&
+ state.hoveredTarget.sprite === id
+});
+const mapDispatchToProps = dispatch => ({
+ dispatchSetHoveredSprite: spriteId => {
+ dispatch(setHoveredSprite(spriteId));
+ }
});
export default connect(
- mapStateToProps
+ mapStateToProps,
+ mapDispatchToProps
)(SpriteSelectorItem);
diff --git a/src/containers/target-pane.jsx b/src/containers/target-pane.jsx
index 5cc156a87..7dc6adb25 100644
--- a/src/containers/target-pane.jsx
+++ b/src/containers/target-pane.jsx
@@ -9,8 +9,6 @@ import {
closeSpriteLibrary
} from '../reducers/modals';
-import {setHoveredSprite} from '../reducers/hovered-target-sprite';
-
import TargetPaneComponent from '../components/target-pane/target-pane.jsx';
class TargetPane extends React.Component {
@@ -25,8 +23,6 @@ class TargetPane extends React.Component {
'handleChangeSpriteY',
'handleDeleteSprite',
'handleDuplicateSprite',
- 'handleMouseOutSprite',
- 'handleMouseOverSprite',
'handleSelectSprite'
]);
}
@@ -54,12 +50,6 @@ class TargetPane extends React.Component {
handleDuplicateSprite (id) {
this.props.vm.duplicateSprite(id);
}
- handleMouseOutSprite () {
- this.props.setHoveredSprite(null);
- }
- handleMouseOverSprite (id) {
- this.props.setHoveredSprite(id);
- }
handleSelectSprite (id) {
this.props.vm.setEditingTarget(id);
}
@@ -75,8 +65,6 @@ class TargetPane extends React.Component {
onChangeSpriteY={this.handleChangeSpriteY}
onDeleteSprite={this.handleDeleteSprite}
onDuplicateSprite={this.handleDuplicateSprite}
- onMouseOutSprite={this.handleMouseOutSprite}
- onMouseOverSprite={this.handleMouseOverSprite}
onSelectSprite={this.handleSelectSprite}
/>
);
@@ -94,6 +82,7 @@ TargetPane.propTypes = {
const mapStateToProps = state => ({
editingTarget: state.targets.editingTarget,
+ hoveredTarget: state.hoveredTarget,
sprites: Object.keys(state.targets.sprites).reduce((sprites, k) => {
let {direction, size, x, y, ...sprite} = state.targets.sprites[k];
if (typeof direction !== 'undefined') direction = Math.round(direction);
@@ -118,9 +107,6 @@ const mapDispatchToProps = dispatch => ({
},
onRequestCloseBackdropLibrary: () => {
dispatch(closeBackdropLibrary());
- },
- setHoveredSprite: spriteId => {
- dispatch(setHoveredSprite(spriteId));
}
});
diff --git a/src/lib/vm-listener-hoc.jsx b/src/lib/vm-listener-hoc.jsx
index bf541b81b..3ae81fdbb 100644
--- a/src/lib/vm-listener-hoc.jsx
+++ b/src/lib/vm-listener-hoc.jsx
@@ -8,6 +8,7 @@ import {connect} from 'react-redux';
import {updateEditingTarget, updateTargets} from '../reducers/targets';
import {updateBlockDrag} from '../reducers/block-drag';
import {updateMonitors} from '../reducers/monitors';
+import {setReceivedBlocks} from '../reducers/hovered-target';
/*
* Higher Order Component to manage events emitted by the VM
@@ -51,8 +52,9 @@ const vmListenerHOC = function (WrappedComponent) {
}
}
handleBlockDragEnd (blocks) {
- if (this.props.hoveredTargetSprite && this.props.hoveredTargetSprite !== this.props.editingTarget) {
- this.props.vm.shareBlocksToTarget(blocks, this.props.hoveredTargetSprite);
+ if (this.props.hoveredSprite && this.props.hoveredSprite !== this.props.editingTarget) {
+ this.props.vm.shareBlocksToTarget(blocks, this.props.hoveredSprite);
+ this.props.onReceivedBlocks(true);
}
}
handleKeyDown (e) {
@@ -86,10 +88,13 @@ const vmListenerHOC = function (WrappedComponent) {
const {
/* eslint-disable no-unused-vars */
attachKeyboardEvents,
+ editingTarget,
+ hoveredSprite,
onBlockDragUpdate,
onKeyDown,
onKeyUp,
onMonitorsUpdate,
+ onReceivedBlocks,
onTargetsUpdate,
/* eslint-enable no-unused-vars */
...props
@@ -100,11 +105,12 @@ const vmListenerHOC = function (WrappedComponent) {
VMListener.propTypes = {
attachKeyboardEvents: PropTypes.bool,
editingTarget: PropTypes.string,
- hoveredTargetSprite: PropTypes.string,
+ hoveredSprite: PropTypes.string,
onBlockDragUpdate: PropTypes.func.isRequired,
onKeyDown: PropTypes.func,
onKeyUp: PropTypes.func,
onMonitorsUpdate: PropTypes.func.isRequired,
+ onReceivedBlocks: PropTypes.func.isRequired,
onTargetsUpdate: PropTypes.func.isRequired,
vm: PropTypes.instanceOf(VM).isRequired
};
@@ -113,7 +119,7 @@ const vmListenerHOC = function (WrappedComponent) {
};
const mapStateToProps = state => ({
vm: state.vm,
- hoveredTargetSprite: state.hoveredTargetSprite,
+ hoveredSprite: state.hoveredTarget.sprite,
editingTarget: state.targets.editingTarget
});
const mapDispatchToProps = dispatch => ({
@@ -126,6 +132,9 @@ const vmListenerHOC = function (WrappedComponent) {
},
onBlockDragUpdate: areBlocksOverGui => {
dispatch(updateBlockDrag(areBlocksOverGui));
+ },
+ onReceivedBlocks: receivedBlocks => {
+ dispatch(setReceivedBlocks(receivedBlocks));
}
});
return connect(
diff --git a/src/reducers/gui.js b/src/reducers/gui.js
index 61ffa5501..49b9a0120 100644
--- a/src/reducers/gui.js
+++ b/src/reducers/gui.js
@@ -2,7 +2,7 @@ import {combineReducers} from 'redux';
import colorPickerReducer from './color-picker';
import customProceduresReducer from './custom-procedures';
import blockDragReducer from './block-drag';
-import hoveredTargetSpriteReducer from './hovered-target-sprite';
+import hoveredTargetReducer from './hovered-target';
import intlReducer from './intl';
import modalReducer from './modals';
import monitorReducer from './monitors';
@@ -17,7 +17,7 @@ export default combineReducers({
blockDrag: blockDragReducer,
colorPicker: colorPickerReducer,
customProcedures: customProceduresReducer,
- hoveredTargetSprite: hoveredTargetSpriteReducer,
+ hoveredTarget: hoveredTargetReducer,
intl: intlReducer,
stageSize: stageSizeReducer,
modals: modalReducer,
diff --git a/src/reducers/hovered-target-sprite.js b/src/reducers/hovered-target-sprite.js
deleted file mode 100644
index 38da69ca1..000000000
--- a/src/reducers/hovered-target-sprite.js
+++ /dev/null
@@ -1,28 +0,0 @@
-const SET_HOVERED_SPRITE = 'scratch-gui/hovered-target-sprite/SET_HOVERED_SPRITE';
-
-const initialState = null;
-
-const reducer = function (state, action) {
- if (typeof state === 'undefined') state = initialState;
- switch (action.type) {
- case SET_HOVERED_SPRITE:
- return action.spriteId;
- default:
- return state;
- }
-};
-
-const setHoveredSprite = function (spriteId) {
- return {
- type: SET_HOVERED_SPRITE,
- spriteId: spriteId,
- meta: {
- throttle: 30
- }
- };
-};
-
-export {
- reducer as default,
- setHoveredSprite
-};
diff --git a/src/reducers/hovered-target.js b/src/reducers/hovered-target.js
new file mode 100644
index 000000000..43bf87ac0
--- /dev/null
+++ b/src/reducers/hovered-target.js
@@ -0,0 +1,48 @@
+const SET_HOVERED_SPRITE = 'scratch-gui/hovered-target/SET_HOVERED_SPRITE';
+const SET_RECEIVED_BLOCKS = 'scratch-gui/hovered-target/SET_RECEIVED_BLOCKS';
+
+const initialState = {
+ sprite: null,
+ receivedBlocks: false
+};
+
+const reducer = function (state, action) {
+ if (typeof state === 'undefined') state = initialState;
+ switch (action.type) {
+ case SET_HOVERED_SPRITE:
+ return {
+ sprite: action.spriteId,
+ receivedBlocks: false
+ };
+ case SET_RECEIVED_BLOCKS:
+ return {
+ sprite: state.sprite,
+ receivedBlocks: action.receivedBlocks
+ };
+ default:
+ return state;
+ }
+};
+
+const setHoveredSprite = function (spriteId) {
+ return {
+ type: SET_HOVERED_SPRITE,
+ spriteId: spriteId,
+ meta: {
+ throttle: 30
+ }
+ };
+};
+
+const setReceivedBlocks = function (receivedBlocks) {
+ return {
+ type: SET_RECEIVED_BLOCKS,
+ receivedBlocks: receivedBlocks
+ };
+};
+
+export {
+ reducer as default,
+ setHoveredSprite,
+ setReceivedBlocks
+};