Add sprite target highlighter on sprite-tile click
This commit is contained in:
1 parent
b18b428ace
commit
523f2aa619
5 files changed
+122
-3
No files matched your search
@@ -89,7 +89,7 @@ to adjust for the border using a different method */
|
||||
padding-bottom: calc($stage-full-screen-stage-padding + $stage-full-screen-border-width);
|
||||
}
|
||||
|
||||
.monitor-wrapper, .color-picker-wrapper {
|
||||
.monitor-wrapper, .color-picker-wrapper, .frame-wrapper {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
@@ -127,3 +127,18 @@ to adjust for the border using a different method */
|
||||
.question-wrapper {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.frame {
|
||||
box-sizing: content-box !important;
|
||||
background: $motion-transparent;
|
||||
border: 2px solid $motion-primary;
|
||||
border-radius: 0.5rem;
|
||||
animation-name: flash;
|
||||
animation-duration: 0.75s;
|
||||
animation-fill-mode: forwards; /* Leave at 0 opacity after animation */
|
||||
}
|
||||
|
||||
@keyframes flash {
|
||||
0% { opacity: 1; }
|
||||
100% { opacity: 0; }
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import Box from '../box/box.jsx';
|
||||
import DOMElementRenderer from '../../containers/dom-element-renderer.jsx';
|
||||
import Loupe from '../loupe/loupe.jsx';
|
||||
import MonitorList from '../../containers/monitor-list.jsx';
|
||||
import TargetHighlight from '../../containers/target-highlight.jsx';
|
||||
import Question from '../../containers/question.jsx';
|
||||
import MicIndicator from '../mic-indicator/mic-indicator.jsx';
|
||||
import {STAGE_DISPLAY_SIZES} from '../../lib/layout-constants.js';
|
||||
@@ -63,6 +64,9 @@ const StageComponent = props => {
|
||||
stageSize={stageDimensions}
|
||||
/>
|
||||
</Box>
|
||||
<Box className={styles.frameWrapper}>
|
||||
<TargetHighlight className={styles.frame} />
|
||||
</Box>
|
||||
{isColorPicking && colorInfo ? (
|
||||
<Box className={styles.colorPickerWrapper}>
|
||||
<Loupe colorInfo={colorInfo} />
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import bindAll from 'lodash.bindall';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
import VM from 'scratch-vm';
|
||||
|
||||
class TargetHighlight extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'getPageCoords'
|
||||
]);
|
||||
}
|
||||
|
||||
// Transform scratch coordinates into page coordinates
|
||||
getPageCoords (x, y) {
|
||||
const nativeSize = this.props.vm.renderer.getNativeSize();
|
||||
const rect = this.props.vm.renderer.canvas.getBoundingClientRect();
|
||||
return [
|
||||
((rect.width / nativeSize[0]) * x) + (rect.width / 2),
|
||||
-((rect.height / nativeSize[1]) * y) + (rect.height / 2)
|
||||
];
|
||||
}
|
||||
|
||||
render () {
|
||||
const {
|
||||
className,
|
||||
highlightedTargetId,
|
||||
highlightedTargetTime,
|
||||
vm
|
||||
} = this.props;
|
||||
|
||||
if (!(highlightedTargetId && vm && vm.renderer)) return null;
|
||||
|
||||
const target = vm.runtime.getTargetById(highlightedTargetId);
|
||||
const bounds = vm.renderer.getBounds(target.drawableID);
|
||||
const [left, top] = this.getPageCoords(bounds.left, bounds.top);
|
||||
const [right, bottom] = this.getPageCoords(bounds.right, bounds.bottom);
|
||||
|
||||
const pad = 2; // px
|
||||
|
||||
return (
|
||||
<div
|
||||
className={className}
|
||||
// Ensure new DOM element each update to restart animation
|
||||
key={highlightedTargetTime}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: `${top - pad}px`,
|
||||
left: `${left - pad}px`,
|
||||
width: `${(right - left) + (2 * pad)}px`,
|
||||
height: `${(bottom - top) + (2 * pad)}px`
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TargetHighlight.propTypes = {
|
||||
className: PropTypes.string,
|
||||
highlightedTargetId: PropTypes.string,
|
||||
highlightedTargetTime: PropTypes.number,
|
||||
vm: PropTypes.instanceOf(VM)
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
highlightedTargetTime: state.scratchGui.targets.highlightedTargetTime,
|
||||
highlightedTargetId: state.scratchGui.targets.highlightedTargetId,
|
||||
vm: state.scratchGui.vm
|
||||
});
|
||||
|
||||
const mapDispatchToProps = () => ({});
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(TargetHighlight);
|
||||
@@ -18,6 +18,7 @@ import spriteLibraryContent from '../lib/libraries/sprites.json';
|
||||
import {handleFileUpload, spriteUpload} from '../lib/file-uploader.js';
|
||||
import sharedMessages from '../lib/shared-messages';
|
||||
import {emptySprite} from '../lib/empty-assets';
|
||||
import {highlightTarget} from '../reducers/targets';
|
||||
|
||||
class TargetPane extends React.Component {
|
||||
constructor (props) {
|
||||
@@ -109,6 +110,7 @@ class TargetPane extends React.Component {
|
||||
}
|
||||
handleSelectSprite (id) {
|
||||
this.props.vm.setEditingTarget(id);
|
||||
this.props.onHighlightTarget(id, Date.now());
|
||||
}
|
||||
handleSurpriseSpriteClick () {
|
||||
const item = spriteLibraryContent[Math.floor(Math.random() * spriteLibraryContent.length)];
|
||||
@@ -195,6 +197,7 @@ class TargetPane extends React.Component {
|
||||
const {
|
||||
onActivateTab, // eslint-disable-line no-unused-vars
|
||||
onReceivedBlocks, // eslint-disable-line no-unused-vars
|
||||
onHighlightTarget, // eslint-disable-line no-unused-vars
|
||||
dispatchUpdateRestore, // eslint-disable-line no-unused-vars
|
||||
...componentProps
|
||||
} = this.props;
|
||||
@@ -265,6 +268,9 @@ const mapDispatchToProps = dispatch => ({
|
||||
},
|
||||
dispatchUpdateRestore: restoreState => {
|
||||
dispatch(setRestore(restoreState));
|
||||
},
|
||||
onHighlightTarget: (id, time) => {
|
||||
dispatch(highlightTarget(id, time));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+18
-2
@@ -1,8 +1,11 @@
|
||||
const UPDATE_TARGET_LIST = 'scratch-gui/targets/UPDATE_TARGET_LIST';
|
||||
const HIGHLIGHT_TARGET = 'scratch-gui/targets/HIGHLIGHT_TARGET';
|
||||
|
||||
const initialState = {
|
||||
sprites: {},
|
||||
stage: {}
|
||||
stage: {},
|
||||
highlightedTargetId: null,
|
||||
highlightedTargetTime: null
|
||||
};
|
||||
|
||||
const reducer = function (state, action) {
|
||||
@@ -23,6 +26,11 @@ const reducer = function (state, action) {
|
||||
.filter(target => target.isStage)[0] || {},
|
||||
editingTarget: action.editingTarget
|
||||
});
|
||||
case HIGHLIGHT_TARGET:
|
||||
return Object.assign({}, state, {
|
||||
highlightedTargetId: action.targetId,
|
||||
highlightedTargetTime: action.updateTime
|
||||
});
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
@@ -37,8 +45,16 @@ const updateTargets = function (targetList, editingTarget) {
|
||||
}
|
||||
};
|
||||
};
|
||||
const highlightTarget = function (targetId, updateTime) {
|
||||
return {
|
||||
type: HIGHLIGHT_TARGET,
|
||||
targetId: targetId,
|
||||
updateTime: updateTime
|
||||
};
|
||||
};
|
||||
export {
|
||||
reducer as default,
|
||||
initialState as targetsInitialState,
|
||||
updateTargets
|
||||
updateTargets,
|
||||
highlightTarget
|
||||
};
|
||||
Reference in new issue
Block a user