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 {updateAssetDrag} from '../reducers/asset-drag'; import storage from '../lib/storage'; import {getEventXY} from '../lib/touch-utils'; import VM from 'scratch-vm'; import getCostumeUrl from '../lib/get-costume-url'; import SpriteSelectorItemComponent from '../components/sprite-selector-item/sprite-selector-item.jsx'; const dragThreshold = 3; // Same as the block drag threshold class SpriteSelectorItem extends React.Component { constructor (props) { super(props); bindAll(this, [ 'getCostumeData', 'handleClick', 'handleDelete', 'handleDuplicate', 'handleExport', 'handleMouseEnter', 'handleMouseLeave', 'handleMouseDown', 'handleMouseMove', 'handleMouseUp' ]); // Asset ID of the current decoded costume this.decodedAssetId = null; } shouldComponentUpdate (nextProps) { // Ignore dragPayload due to https://github.com/LLK/scratch-gui/issues/3172. // This function should be removed once the issue is fixed. for (const property in nextProps) { if (property !== 'dragPayload' && this.props[property] !== nextProps[property]) { return true; } } return false; } getCostumeData () { if (this.props.costumeURL) return this.props.costumeURL; if (!this.props.asset) return null; return getCostumeUrl(this.props.asset); } handleMouseUp () { this.initialOffset = null; window.removeEventListener('mouseup', this.handleMouseUp); window.removeEventListener('mousemove', this.handleMouseMove); window.removeEventListener('touchend', this.handleMouseUp); window.removeEventListener('touchmove', this.handleMouseMove); if (this.props.dragging) { this.props.onDrag({ img: null, currentOffset: null, dragging: false, dragType: null, index: null }); } setTimeout(() => { this.noClick = false; }); } handleMouseMove (e) { const currentOffset = getEventXY(e); const dx = currentOffset.x - this.initialOffset.x; const dy = currentOffset.y - this.initialOffset.y; if (Math.sqrt((dx * dx) + (dy * dy)) > dragThreshold) { this.props.onDrag({ img: this.getCostumeData(), currentOffset: currentOffset, dragging: true, dragType: this.props.dragType, index: this.props.index, payload: this.props.dragPayload }); this.noClick = true; } e.preventDefault(); } handleMouseDown (e) { this.initialOffset = getEventXY(e); window.addEventListener('mouseup', this.handleMouseUp); window.addEventListener('mousemove', this.handleMouseMove); window.addEventListener('touchend', this.handleMouseUp); window.addEventListener('touchmove', this.handleMouseMove); } handleClick (e) { e.preventDefault(); if (!this.noClick) { this.props.onClick(this.props.id); } } handleDelete (e) { e.stopPropagation(); // To prevent from bubbling back to handleClick this.props.onDeleteButtonClick(this.props.id); } handleDuplicate (e) { e.stopPropagation(); // To prevent from bubbling back to handleClick this.props.onDuplicateButtonClick(this.props.id); } handleExport (e) { e.stopPropagation(); this.props.onExportButtonClick(this.props.id); } handleMouseLeave () { this.props.dispatchSetHoveredSprite(null); } handleMouseEnter () { this.props.dispatchSetHoveredSprite(this.props.id); } render () { const { /* eslint-disable no-unused-vars */ asset, id, index, onClick, onDeleteButtonClick, onDuplicateButtonClick, onExportButtonClick, dragPayload, receivedBlocks, costumeURL, vm, /* eslint-enable no-unused-vars */ ...props } = this.props; return ( <SpriteSelectorItemComponent costumeURL={this.getCostumeData()} onClick={this.handleClick} onDeleteButtonClick={onDeleteButtonClick ? this.handleDelete : null} onDuplicateButtonClick={onDuplicateButtonClick ? this.handleDuplicate : null} onExportButtonClick={onExportButtonClick ? this.handleExport : null} onMouseDown={this.handleMouseDown} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} {...props} /> ); } } SpriteSelectorItem.propTypes = { asset: PropTypes.instanceOf(storage.Asset), costumeURL: PropTypes.string, dispatchSetHoveredSprite: PropTypes.func.isRequired, dragPayload: PropTypes.shape({ name: PropTypes.string, body: PropTypes.string }), dragType: PropTypes.string, dragging: PropTypes.bool, id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), index: PropTypes.number, name: PropTypes.string, onClick: PropTypes.func, onDeleteButtonClick: PropTypes.func, onDrag: PropTypes.func.isRequired, onDuplicateButtonClick: PropTypes.func, onExportButtonClick: PropTypes.func, receivedBlocks: PropTypes.bool.isRequired, selected: PropTypes.bool, vm: PropTypes.instanceOf(VM).isRequired }; const mapStateToProps = (state, {id}) => ({ dragging: state.scratchGui.assetDrag.dragging, receivedBlocks: state.scratchGui.hoveredTarget.receivedBlocks && state.scratchGui.hoveredTarget.sprite === id, vm: state.scratchGui.vm }); const mapDispatchToProps = dispatch => ({ dispatchSetHoveredSprite: spriteId => { dispatch(setHoveredSprite(spriteId)); }, onDrag: data => dispatch(updateAssetDrag(data)) }); const ConnectedComponent = connect( mapStateToProps, mapDispatchToProps )(SpriteSelectorItem); export default ConnectedComponent;