mirror of
https://github.com/scratchfoundation/scratch-paint.git
synced 2025-01-08 13:42:00 -05:00
Break paint-editor.jsx code out into HOCs (#633)
This commit is contained in:
parent
c6458ddebc
commit
20a98db397
10 changed files with 555 additions and 255 deletions
|
@ -24,7 +24,7 @@ import FillMode from '../../containers/fill-mode.jsx';
|
||||||
import InputGroup from '../input-group/input-group.jsx';
|
import InputGroup from '../input-group/input-group.jsx';
|
||||||
import LineMode from '../../containers/line-mode.jsx';
|
import LineMode from '../../containers/line-mode.jsx';
|
||||||
import Loupe from '../loupe/loupe.jsx';
|
import Loupe from '../loupe/loupe.jsx';
|
||||||
import FixedToolsComponent from '../fixed-tools/fixed-tools.jsx';
|
import FixedToolsContainer from '../../containers/fixed-tools.jsx';
|
||||||
import ModeToolsContainer from '../../containers/mode-tools.jsx';
|
import ModeToolsContainer from '../../containers/mode-tools.jsx';
|
||||||
import OvalMode from '../../containers/oval-mode.jsx';
|
import OvalMode from '../../containers/oval-mode.jsx';
|
||||||
import RectMode from '../../containers/rect-mode.jsx';
|
import RectMode from '../../containers/rect-mode.jsx';
|
||||||
|
@ -65,18 +65,12 @@ const PaintEditorComponent = props => (
|
||||||
<div className={styles.editorContainerTop}>
|
<div className={styles.editorContainerTop}>
|
||||||
{/* First row */}
|
{/* First row */}
|
||||||
<div className={styles.row}>
|
<div className={styles.row}>
|
||||||
<FixedToolsComponent
|
<FixedToolsContainer
|
||||||
canRedo={props.canRedo}
|
canRedo={props.canRedo}
|
||||||
canUndo={props.canUndo}
|
canUndo={props.canUndo}
|
||||||
name={props.name}
|
name={props.name}
|
||||||
onGroup={props.onGroup}
|
|
||||||
onRedo={props.onRedo}
|
onRedo={props.onRedo}
|
||||||
onSendBackward={props.onSendBackward}
|
|
||||||
onSendForward={props.onSendForward}
|
|
||||||
onSendToBack={props.onSendToBack}
|
|
||||||
onSendToFront={props.onSendToFront}
|
|
||||||
onUndo={props.onUndo}
|
onUndo={props.onUndo}
|
||||||
onUngroup={props.onUngroup}
|
|
||||||
onUpdateImage={props.onUpdateImage}
|
onUpdateImage={props.onUpdateImage}
|
||||||
onUpdateName={props.onUpdateName}
|
onUpdateName={props.onUpdateName}
|
||||||
/>
|
/>
|
||||||
|
@ -324,16 +318,10 @@ PaintEditorComponent.propTypes = {
|
||||||
intl: intlShape,
|
intl: intlShape,
|
||||||
isEyeDropping: PropTypes.bool,
|
isEyeDropping: PropTypes.bool,
|
||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
onGroup: PropTypes.func.isRequired,
|
|
||||||
onRedo: PropTypes.func.isRequired,
|
onRedo: PropTypes.func.isRequired,
|
||||||
onSendBackward: PropTypes.func.isRequired,
|
|
||||||
onSendForward: PropTypes.func.isRequired,
|
|
||||||
onSendToBack: PropTypes.func.isRequired,
|
|
||||||
onSendToFront: PropTypes.func.isRequired,
|
|
||||||
onSwitchToBitmap: PropTypes.func.isRequired,
|
onSwitchToBitmap: PropTypes.func.isRequired,
|
||||||
onSwitchToVector: PropTypes.func.isRequired,
|
onSwitchToVector: PropTypes.func.isRequired,
|
||||||
onUndo: PropTypes.func.isRequired,
|
onUndo: PropTypes.func.isRequired,
|
||||||
onUngroup: PropTypes.func.isRequired,
|
|
||||||
onUpdateImage: PropTypes.func.isRequired,
|
onUpdateImage: PropTypes.func.isRequired,
|
||||||
onUpdateName: PropTypes.func.isRequired,
|
onUpdateName: PropTypes.func.isRequired,
|
||||||
onZoomIn: PropTypes.func.isRequired,
|
onZoomIn: PropTypes.func.isRequired,
|
||||||
|
|
131
src/containers/fixed-tools.jsx
Normal file
131
src/containers/fixed-tools.jsx
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import {connect} from 'react-redux';
|
||||||
|
|
||||||
|
import FixedToolsComponent from '../components/fixed-tools/fixed-tools.jsx';
|
||||||
|
|
||||||
|
import {changeMode} from '../reducers/modes';
|
||||||
|
import {changeFormat} from '../reducers/format';
|
||||||
|
import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items';
|
||||||
|
import {deactivateEyeDropper} from '../reducers/eye-dropper';
|
||||||
|
import {setTextEditTarget} from '../reducers/text-edit-target';
|
||||||
|
import {setLayout} from '../reducers/layout';
|
||||||
|
|
||||||
|
import {getSelectedLeafItems} from '../helper/selection';
|
||||||
|
import {bringToFront, sendBackward, sendToBack, bringForward} from '../helper/order';
|
||||||
|
import {groupSelection, ungroupSelection} from '../helper/group';
|
||||||
|
|
||||||
|
import Formats from '../lib/format';
|
||||||
|
import {isBitmap} from '../lib/format';
|
||||||
|
import bindAll from 'lodash.bindall';
|
||||||
|
|
||||||
|
class FixedTools extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props);
|
||||||
|
bindAll(this, [
|
||||||
|
'handleSendBackward',
|
||||||
|
'handleSendForward',
|
||||||
|
'handleSendToBack',
|
||||||
|
'handleSendToFront',
|
||||||
|
'handleSetSelectedItems',
|
||||||
|
'handleGroup',
|
||||||
|
'handleUngroup'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
handleGroup () {
|
||||||
|
groupSelection(this.props.clearSelectedItems, this.handleSetSelectedItems, this.props.onUpdateImage);
|
||||||
|
}
|
||||||
|
handleUngroup () {
|
||||||
|
ungroupSelection(this.props.clearSelectedItems, this.handleSetSelectedItems, this.props.onUpdateImage);
|
||||||
|
}
|
||||||
|
handleSendBackward () {
|
||||||
|
sendBackward(this.props.onUpdateImage);
|
||||||
|
}
|
||||||
|
handleSendForward () {
|
||||||
|
bringForward(this.props.onUpdateImage);
|
||||||
|
}
|
||||||
|
handleSendToBack () {
|
||||||
|
sendToBack(this.props.onUpdateImage);
|
||||||
|
}
|
||||||
|
handleSendToFront () {
|
||||||
|
bringToFront(this.props.onUpdateImage);
|
||||||
|
}
|
||||||
|
handleSetSelectedItems () {
|
||||||
|
this.props.setSelectedItems(this.props.format);
|
||||||
|
}
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<FixedToolsComponent
|
||||||
|
canRedo={this.props.canRedo}
|
||||||
|
canUndo={this.props.canUndo}
|
||||||
|
name={this.props.name}
|
||||||
|
onGroup={this.handleGroup}
|
||||||
|
onRedo={this.props.onRedo}
|
||||||
|
onSendBackward={this.handleSendBackward}
|
||||||
|
onSendForward={this.handleSendForward}
|
||||||
|
onSendToBack={this.handleSendToBack}
|
||||||
|
onSendToFront={this.handleSendToFront}
|
||||||
|
onUndo={this.props.onUndo}
|
||||||
|
onUngroup={this.handleUngroup}
|
||||||
|
onUpdateImage={this.props.onUpdateImage}
|
||||||
|
onUpdateName={this.props.onUpdateName}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FixedTools.propTypes = {
|
||||||
|
canRedo: PropTypes.func.isRequired,
|
||||||
|
canUndo: PropTypes.func.isRequired,
|
||||||
|
clearSelectedItems: PropTypes.func.isRequired,
|
||||||
|
format: PropTypes.oneOf(Object.keys(Formats)),
|
||||||
|
name: PropTypes.string,
|
||||||
|
onRedo: PropTypes.func.isRequired,
|
||||||
|
onUndo: PropTypes.func.isRequired,
|
||||||
|
onUpdateImage: PropTypes.func.isRequired,
|
||||||
|
onUpdateName: PropTypes.func.isRequired,
|
||||||
|
setSelectedItems: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
changeColorToEyeDropper: state.scratchPaint.color.eyeDropper.callback,
|
||||||
|
format: state.scratchPaint.format,
|
||||||
|
isEyeDropping: state.scratchPaint.color.eyeDropper.active,
|
||||||
|
mode: state.scratchPaint.mode,
|
||||||
|
pasteOffset: state.scratchPaint.clipboard.pasteOffset,
|
||||||
|
previousTool: state.scratchPaint.color.eyeDropper.previousTool,
|
||||||
|
selectedItems: state.scratchPaint.selectedItems,
|
||||||
|
viewBounds: state.scratchPaint.viewBounds
|
||||||
|
});
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
changeMode: mode => {
|
||||||
|
dispatch(changeMode(mode));
|
||||||
|
},
|
||||||
|
clearSelectedItems: () => {
|
||||||
|
dispatch(clearSelectedItems());
|
||||||
|
},
|
||||||
|
handleSwitchToBitmap: () => {
|
||||||
|
dispatch(changeFormat(Formats.BITMAP));
|
||||||
|
},
|
||||||
|
handleSwitchToVector: () => {
|
||||||
|
dispatch(changeFormat(Formats.VECTOR));
|
||||||
|
},
|
||||||
|
removeTextEditTarget: () => {
|
||||||
|
dispatch(setTextEditTarget());
|
||||||
|
},
|
||||||
|
setLayout: layout => {
|
||||||
|
dispatch(setLayout(layout));
|
||||||
|
},
|
||||||
|
setSelectedItems: format => {
|
||||||
|
dispatch(setSelectedItems(getSelectedLeafItems(), isBitmap(format)));
|
||||||
|
},
|
||||||
|
onDeactivateEyeDropper: () => {
|
||||||
|
// set redux values to default for eye dropper reducer
|
||||||
|
dispatch(deactivateEyeDropper());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(FixedTools);
|
|
@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
|
||||||
import {connect} from 'react-redux';
|
import {connect} from 'react-redux';
|
||||||
import bindAll from 'lodash.bindall';
|
import bindAll from 'lodash.bindall';
|
||||||
|
|
||||||
import CopyPasteHOC from './copy-paste-hoc.jsx';
|
import CopyPasteHOC from '../hocs/copy-paste-hoc.jsx';
|
||||||
import ModeToolsComponent from '../components/mode-tools/mode-tools.jsx';
|
import ModeToolsComponent from '../components/mode-tools/mode-tools.jsx';
|
||||||
import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items';
|
import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items';
|
||||||
import {incrementPasteOffset, setClipboardItems} from '../reducers/clipboard';
|
import {incrementPasteOffset, setClipboardItems} from '../reducers/clipboard';
|
||||||
|
|
|
@ -1,37 +1,29 @@
|
||||||
import paper from '@scratch/paper';
|
import paper from '@scratch/paper';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import log from '../log/log';
|
import log from '../log/log';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {connect} from 'react-redux';
|
import {connect} from 'react-redux';
|
||||||
|
|
||||||
import PaintEditorComponent from '../components/paint-editor/paint-editor.jsx';
|
import PaintEditorComponent from '../components/paint-editor/paint-editor.jsx';
|
||||||
import CopyPasteHOC from './copy-paste-hoc.jsx';
|
import KeyboardShortcutsHOC from '../hocs/keyboard-shortcuts-hoc.jsx';
|
||||||
import SelectionHOC from './selection-hoc.jsx';
|
import SelectionHOC from '../hocs/selection-hoc.jsx';
|
||||||
|
import UndoHOC from '../hocs/undo-hoc.jsx';
|
||||||
|
import UpdateImageHOC from '../hocs/update-image-hoc.jsx';
|
||||||
|
|
||||||
import {changeMode} from '../reducers/modes';
|
import {changeMode} from '../reducers/modes';
|
||||||
import {changeFormat} from '../reducers/format';
|
import {changeFormat} from '../reducers/format';
|
||||||
import {undo, redo, undoSnapshot} from '../reducers/undo';
|
|
||||||
import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items';
|
import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items';
|
||||||
import {deactivateEyeDropper} from '../reducers/eye-dropper';
|
import {deactivateEyeDropper} from '../reducers/eye-dropper';
|
||||||
import {setTextEditTarget} from '../reducers/text-edit-target';
|
import {setTextEditTarget} from '../reducers/text-edit-target';
|
||||||
import {updateViewBounds} from '../reducers/view-bounds';
|
import {updateViewBounds} from '../reducers/view-bounds';
|
||||||
import {setLayout} from '../reducers/layout';
|
import {setLayout} from '../reducers/layout';
|
||||||
|
|
||||||
import {getRaster, hideGuideLayers, showGuideLayers} from '../helper/layer';
|
import {getSelectedLeafItems} from '../helper/selection';
|
||||||
import {commitSelectionToBitmap, convertToBitmap, convertToVector, getHitBounds,
|
import {convertToBitmap, convertToVector} from '../helper/bitmap';
|
||||||
selectAllBitmap} from '../helper/bitmap';
|
|
||||||
import {performUndo, performRedo, performSnapshot, shouldShowUndo, shouldShowRedo} from '../helper/undo';
|
|
||||||
import {bringToFront, sendBackward, sendToBack, bringForward} from '../helper/order';
|
|
||||||
import {groupSelection, ungroupSelection} from '../helper/group';
|
|
||||||
import {scaleWithStrokes} from '../helper/math';
|
|
||||||
import {clearSelection, deleteSelection, getSelectedLeafItems,
|
|
||||||
selectAllItems, selectAllSegments} from '../helper/selection';
|
|
||||||
import {ART_BOARD_WIDTH, ART_BOARD_HEIGHT, SVG_ART_BOARD_WIDTH, SVG_ART_BOARD_HEIGHT} from '../helper/view';
|
|
||||||
import {resetZoom, zoomOnSelection} from '../helper/view';
|
import {resetZoom, zoomOnSelection} from '../helper/view';
|
||||||
import EyeDropperTool from '../helper/tools/eye-dropper';
|
import EyeDropperTool from '../helper/tools/eye-dropper';
|
||||||
|
|
||||||
import Modes from '../lib/modes';
|
import Modes from '../lib/modes';
|
||||||
import {BitmapModes} from '../lib/modes';
|
|
||||||
import Formats from '../lib/format';
|
import Formats from '../lib/format';
|
||||||
import {isBitmap, isVector} from '../lib/format';
|
import {isBitmap, isVector} from '../lib/format';
|
||||||
import bindAll from 'lodash.bindall';
|
import bindAll from 'lodash.bindall';
|
||||||
|
@ -43,52 +35,32 @@ class PaintEditor extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props);
|
super(props);
|
||||||
bindAll(this, [
|
bindAll(this, [
|
||||||
'handleUpdateImage',
|
|
||||||
'handleUpdateBitmap',
|
|
||||||
'handleUpdateVector',
|
|
||||||
'handleUndo',
|
|
||||||
'handleRedo',
|
|
||||||
'handleSendBackward',
|
|
||||||
'handleSendForward',
|
|
||||||
'handleSendToBack',
|
|
||||||
'handleSendToFront',
|
|
||||||
'handleSetSelectedItems',
|
|
||||||
'handleGroup',
|
|
||||||
'handleUngroup',
|
|
||||||
'handleZoomIn',
|
|
||||||
'handleZoomOut',
|
|
||||||
'handleZoomReset',
|
|
||||||
'canRedo',
|
|
||||||
'canUndo',
|
|
||||||
'switchMode',
|
'switchMode',
|
||||||
'onKeyPress',
|
|
||||||
'onMouseDown',
|
'onMouseDown',
|
||||||
'setCanvas',
|
'setCanvas',
|
||||||
'setTextArea',
|
'setTextArea',
|
||||||
'startEyeDroppingLoop',
|
'startEyeDroppingLoop',
|
||||||
'stopEyeDroppingLoop'
|
'stopEyeDroppingLoop',
|
||||||
|
'handleSetSelectedItems',
|
||||||
|
'handleZoomIn',
|
||||||
|
'handleZoomOut',
|
||||||
|
'handleZoomReset'
|
||||||
]);
|
]);
|
||||||
this.state = {
|
this.state = {
|
||||||
canvas: null,
|
canvas: null,
|
||||||
colorInfo: null
|
colorInfo: null
|
||||||
};
|
};
|
||||||
// When isSwitchingFormats is true, the format is about to switch, but isn't done switching.
|
|
||||||
// This gives currently active tools a chance to finish what they were doing.
|
|
||||||
this.isSwitchingFormats = false;
|
|
||||||
this.props.setLayout(this.props.rtl ? 'rtl' : 'ltr');
|
this.props.setLayout(this.props.rtl ? 'rtl' : 'ltr');
|
||||||
}
|
}
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
document.addEventListener('keydown', this.onKeyPress);
|
document.addEventListener('keydown', this.props.onKeyPress);
|
||||||
|
|
||||||
// document listeners used to detect if a mouse is down outside of the
|
// document listeners used to detect if a mouse is down outside of the
|
||||||
// canvas, and should therefore stop the eye dropper
|
// canvas, and should therefore stop the eye dropper
|
||||||
document.addEventListener('mousedown', this.onMouseDown);
|
document.addEventListener('mousedown', this.onMouseDown);
|
||||||
document.addEventListener('touchstart', this.onMouseDown);
|
document.addEventListener('touchstart', this.onMouseDown);
|
||||||
}
|
}
|
||||||
componentWillReceiveProps (newProps) {
|
componentWillReceiveProps (newProps) {
|
||||||
if ((isVector(this.props.format) && newProps.format === Formats.BITMAP) ||
|
|
||||||
(isBitmap(this.props.format) && newProps.format === Formats.VECTOR)) {
|
|
||||||
this.isSwitchingFormats = true;
|
|
||||||
}
|
|
||||||
if (isVector(this.props.format) && isBitmap(newProps.format)) {
|
if (isVector(this.props.format) && isBitmap(newProps.format)) {
|
||||||
this.switchMode(Formats.BITMAP);
|
this.switchMode(Formats.BITMAP);
|
||||||
} else if (isVector(newProps.format) && isBitmap(this.props.format)) {
|
} else if (isVector(newProps.format) && isBitmap(this.props.format)) {
|
||||||
|
@ -108,12 +80,11 @@ class PaintEditor extends React.Component {
|
||||||
this.props.onDeactivateEyeDropper();
|
this.props.onDeactivateEyeDropper();
|
||||||
this.stopEyeDroppingLoop();
|
this.stopEyeDroppingLoop();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.props.format === Formats.VECTOR && isBitmap(prevProps.format)) {
|
if (this.props.format === Formats.VECTOR && isBitmap(prevProps.format)) {
|
||||||
this.isSwitchingFormats = false;
|
convertToVector(this.props.clearSelectedItems, this.props.onUpdateImage);
|
||||||
convertToVector(this.props.clearSelectedItems, this.handleUpdateImage);
|
|
||||||
} else if (isVector(prevProps.format) && this.props.format === Formats.BITMAP) {
|
} else if (isVector(prevProps.format) && this.props.format === Formats.BITMAP) {
|
||||||
this.isSwitchingFormats = false;
|
convertToBitmap(this.props.clearSelectedItems, this.props.onUpdateImage);
|
||||||
convertToBitmap(this.props.clearSelectedItems, this.handleUpdateImage);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
|
@ -187,108 +158,6 @@ class PaintEditor extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
handleUpdateImage (skipSnapshot) {
|
|
||||||
// If in the middle of switching formats, rely on the current mode instead of format.
|
|
||||||
let actualFormat = this.props.format;
|
|
||||||
if (this.isSwitchingFormats) {
|
|
||||||
actualFormat = BitmapModes[this.props.mode] ? Formats.BITMAP : Formats.VECTOR;
|
|
||||||
}
|
|
||||||
if (isBitmap(actualFormat)) {
|
|
||||||
this.handleUpdateBitmap(skipSnapshot);
|
|
||||||
} else if (isVector(actualFormat)) {
|
|
||||||
this.handleUpdateVector(skipSnapshot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
handleUpdateBitmap (skipSnapshot) {
|
|
||||||
if (!getRaster().loaded) {
|
|
||||||
// In general, callers of updateImage should wait for getRaster().loaded = true before
|
|
||||||
// calling updateImage.
|
|
||||||
// However, this may happen if the user is rapidly undoing/redoing. In this case it's safe
|
|
||||||
// to skip the update.
|
|
||||||
log.warn('Bitmap layer should be loaded before calling updateImage.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Plaster the selection onto the raster layer before exporting, if there is a selection.
|
|
||||||
const plasteredRaster = getRaster().getSubRaster(getRaster().bounds);
|
|
||||||
plasteredRaster.remove(); // Don't insert
|
|
||||||
const selectedItems = getSelectedLeafItems();
|
|
||||||
if (selectedItems.length === 1 && selectedItems[0] instanceof paper.Raster) {
|
|
||||||
if (!selectedItems[0].loaded ||
|
|
||||||
(selectedItems[0].data && selectedItems[0].data.expanded && !selectedItems[0].data.expanded.loaded)) {
|
|
||||||
log.warn('Bitmap layer should be loaded before calling updateImage.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
commitSelectionToBitmap(selectedItems[0], plasteredRaster);
|
|
||||||
}
|
|
||||||
const rect = getHitBounds(plasteredRaster);
|
|
||||||
this.props.onUpdateImage(
|
|
||||||
false /* isVector */,
|
|
||||||
plasteredRaster.getImageData(rect),
|
|
||||||
(ART_BOARD_WIDTH / 2) - rect.x,
|
|
||||||
(ART_BOARD_HEIGHT / 2) - rect.y);
|
|
||||||
|
|
||||||
if (!skipSnapshot) {
|
|
||||||
performSnapshot(this.props.undoSnapshot, Formats.BITMAP);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
handleUpdateVector (skipSnapshot) {
|
|
||||||
const guideLayers = hideGuideLayers(true /* includeRaster */);
|
|
||||||
|
|
||||||
// Export at 0.5x
|
|
||||||
scaleWithStrokes(paper.project.activeLayer, .5, new paper.Point());
|
|
||||||
const bounds = paper.project.activeLayer.bounds;
|
|
||||||
// @todo generate view box
|
|
||||||
this.props.onUpdateImage(
|
|
||||||
true /* isVector */,
|
|
||||||
paper.project.exportSVG({
|
|
||||||
asString: true,
|
|
||||||
bounds: 'content',
|
|
||||||
matrix: new paper.Matrix().translate(-bounds.x, -bounds.y)
|
|
||||||
}),
|
|
||||||
(SVG_ART_BOARD_WIDTH / 2) - bounds.x,
|
|
||||||
(SVG_ART_BOARD_HEIGHT / 2) - bounds.y);
|
|
||||||
scaleWithStrokes(paper.project.activeLayer, 2, new paper.Point());
|
|
||||||
paper.project.activeLayer.applyMatrix = true;
|
|
||||||
|
|
||||||
showGuideLayers(guideLayers);
|
|
||||||
|
|
||||||
if (!skipSnapshot) {
|
|
||||||
performSnapshot(this.props.undoSnapshot, Formats.VECTOR);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
handleUndo () {
|
|
||||||
performUndo(this.props.undoState, this.props.onUndo, this.handleSetSelectedItems, this.handleUpdateImage);
|
|
||||||
}
|
|
||||||
handleRedo () {
|
|
||||||
performRedo(this.props.undoState, this.props.onRedo, this.handleSetSelectedItems, this.handleUpdateImage);
|
|
||||||
}
|
|
||||||
handleGroup () {
|
|
||||||
groupSelection(this.props.clearSelectedItems, this.handleSetSelectedItems, this.handleUpdateImage);
|
|
||||||
}
|
|
||||||
handleUngroup () {
|
|
||||||
ungroupSelection(this.props.clearSelectedItems, this.handleSetSelectedItems, this.handleUpdateImage);
|
|
||||||
}
|
|
||||||
handleSendBackward () {
|
|
||||||
sendBackward(this.handleUpdateImage);
|
|
||||||
}
|
|
||||||
handleSendForward () {
|
|
||||||
bringForward(this.handleUpdateImage);
|
|
||||||
}
|
|
||||||
handleSendToBack () {
|
|
||||||
sendToBack(this.handleUpdateImage);
|
|
||||||
}
|
|
||||||
handleSendToFront () {
|
|
||||||
bringToFront(this.handleUpdateImage);
|
|
||||||
}
|
|
||||||
handleSetSelectedItems () {
|
|
||||||
this.props.setSelectedItems(this.props.format);
|
|
||||||
}
|
|
||||||
canUndo () {
|
|
||||||
return shouldShowUndo(this.props.undoState);
|
|
||||||
}
|
|
||||||
canRedo () {
|
|
||||||
return shouldShowRedo(this.props.undoState);
|
|
||||||
}
|
|
||||||
handleZoomIn () {
|
handleZoomIn () {
|
||||||
zoomOnSelection(PaintEditor.ZOOM_INCREMENT);
|
zoomOnSelection(PaintEditor.ZOOM_INCREMENT);
|
||||||
this.props.updateViewBounds(paper.view.matrix);
|
this.props.updateViewBounds(paper.view.matrix);
|
||||||
|
@ -304,6 +173,9 @@ class PaintEditor extends React.Component {
|
||||||
this.props.updateViewBounds(paper.view.matrix);
|
this.props.updateViewBounds(paper.view.matrix);
|
||||||
this.handleSetSelectedItems();
|
this.handleSetSelectedItems();
|
||||||
}
|
}
|
||||||
|
handleSetSelectedItems () {
|
||||||
|
this.props.setSelectedItems(this.props.format);
|
||||||
|
}
|
||||||
setCanvas (canvas) {
|
setCanvas (canvas) {
|
||||||
this.setState({canvas: canvas});
|
this.setState({canvas: canvas});
|
||||||
this.canvas = canvas;
|
this.canvas = canvas;
|
||||||
|
@ -311,56 +183,6 @@ class PaintEditor extends React.Component {
|
||||||
setTextArea (element) {
|
setTextArea (element) {
|
||||||
this.setState({textArea: element});
|
this.setState({textArea: element});
|
||||||
}
|
}
|
||||||
onKeyPress (event) {
|
|
||||||
// Don't activate keyboard shortcuts during text editing
|
|
||||||
if (this.props.textEditing) return;
|
|
||||||
|
|
||||||
if (event.key === 'Escape') {
|
|
||||||
event.preventDefault();
|
|
||||||
clearSelection(this.props.clearSelectedItems);
|
|
||||||
} else if (event.key === 'Delete' || event.key === 'Backspace') {
|
|
||||||
if (deleteSelection(this.props.mode, this.handleUpdateImage)) {
|
|
||||||
this.handleSetSelectedItems();
|
|
||||||
}
|
|
||||||
} else if (event.metaKey || event.ctrlKey) {
|
|
||||||
if (event.shiftKey && event.key === 'z') {
|
|
||||||
this.handleRedo();
|
|
||||||
} else if (event.key === 'z') {
|
|
||||||
this.handleUndo();
|
|
||||||
} else if (event.key === 'c') {
|
|
||||||
this.props.onCopyToClipboard();
|
|
||||||
} else if (event.key === 'v') {
|
|
||||||
this.changeToASelectMode();
|
|
||||||
if (this.props.onPasteFromClipboard()) {
|
|
||||||
this.handleUpdateImage();
|
|
||||||
}
|
|
||||||
} else if (event.key === 'a') {
|
|
||||||
this.changeToASelectMode();
|
|
||||||
event.preventDefault();
|
|
||||||
this.selectAll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
changeToASelectMode () {
|
|
||||||
if (isBitmap(this.props.format)) {
|
|
||||||
if (this.props.mode !== Modes.BIT_SELECT) {
|
|
||||||
this.props.changeMode(Modes.BIT_SELECT);
|
|
||||||
}
|
|
||||||
} else if (this.props.mode !== Modes.SELECT && this.props.mode !== Modes.RESHAPE) {
|
|
||||||
this.props.changeMode(Modes.SELECT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
selectAll () {
|
|
||||||
if (isBitmap(this.props.format)) {
|
|
||||||
selectAllBitmap(this.props.clearSelectedItems);
|
|
||||||
this.handleSetSelectedItems();
|
|
||||||
} else if (this.props.mode === Modes.RESHAPE) {
|
|
||||||
if (selectAllSegments()) this.handleSetSelectedItems();
|
|
||||||
} else {
|
|
||||||
// Disable lint for easier to read logic
|
|
||||||
if (selectAllItems()) this.handleSetSelectedItems(); // eslint-disable-line no-lonely-if
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onMouseDown (event) {
|
onMouseDown (event) {
|
||||||
if (event.target === paper.view.element &&
|
if (event.target === paper.view.element &&
|
||||||
document.activeElement instanceof HTMLInputElement) {
|
document.activeElement instanceof HTMLInputElement) {
|
||||||
|
@ -427,8 +249,8 @@ class PaintEditor extends React.Component {
|
||||||
render () {
|
render () {
|
||||||
return (
|
return (
|
||||||
<PaintEditorComponent
|
<PaintEditorComponent
|
||||||
canRedo={this.canRedo}
|
canRedo={this.props.shouldShowRedo}
|
||||||
canUndo={this.canUndo}
|
canUndo={this.props.shouldShowUndo}
|
||||||
canvas={this.state.canvas}
|
canvas={this.state.canvas}
|
||||||
colorInfo={this.state.colorInfo}
|
colorInfo={this.state.colorInfo}
|
||||||
format={this.props.format}
|
format={this.props.format}
|
||||||
|
@ -443,17 +265,11 @@ class PaintEditor extends React.Component {
|
||||||
setCanvas={this.setCanvas}
|
setCanvas={this.setCanvas}
|
||||||
setTextArea={this.setTextArea}
|
setTextArea={this.setTextArea}
|
||||||
textArea={this.state.textArea}
|
textArea={this.state.textArea}
|
||||||
onGroup={this.handleGroup}
|
onRedo={this.props.onRedo}
|
||||||
onRedo={this.handleRedo}
|
|
||||||
onSendBackward={this.handleSendBackward}
|
|
||||||
onSendForward={this.handleSendForward}
|
|
||||||
onSendToBack={this.handleSendToBack}
|
|
||||||
onSendToFront={this.handleSendToFront}
|
|
||||||
onSwitchToBitmap={this.props.handleSwitchToBitmap}
|
onSwitchToBitmap={this.props.handleSwitchToBitmap}
|
||||||
onSwitchToVector={this.props.handleSwitchToVector}
|
onSwitchToVector={this.props.handleSwitchToVector}
|
||||||
onUndo={this.handleUndo}
|
onUndo={this.props.onUndo}
|
||||||
onUngroup={this.handleUngroup}
|
onUpdateImage={this.props.onUpdateImage}
|
||||||
onUpdateImage={this.handleUpdateImage}
|
|
||||||
onUpdateName={this.props.onUpdateName}
|
onUpdateName={this.props.onUpdateName}
|
||||||
onZoomIn={this.handleZoomIn}
|
onZoomIn={this.handleZoomIn}
|
||||||
onZoomOut={this.handleZoomOut}
|
onZoomOut={this.handleZoomOut}
|
||||||
|
@ -479,9 +295,8 @@ PaintEditor.propTypes = {
|
||||||
isEyeDropping: PropTypes.bool,
|
isEyeDropping: PropTypes.bool,
|
||||||
mode: PropTypes.oneOf(Object.keys(Modes)).isRequired,
|
mode: PropTypes.oneOf(Object.keys(Modes)).isRequired,
|
||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
onCopyToClipboard: PropTypes.func.isRequired,
|
|
||||||
onDeactivateEyeDropper: PropTypes.func.isRequired,
|
onDeactivateEyeDropper: PropTypes.func.isRequired,
|
||||||
onPasteFromClipboard: PropTypes.func.isRequired,
|
onKeyPress: PropTypes.func.isRequired,
|
||||||
onRedo: PropTypes.func.isRequired,
|
onRedo: PropTypes.func.isRequired,
|
||||||
onUndo: PropTypes.func.isRequired,
|
onUndo: PropTypes.func.isRequired,
|
||||||
onUpdateImage: PropTypes.func.isRequired,
|
onUpdateImage: PropTypes.func.isRequired,
|
||||||
|
@ -496,27 +311,18 @@ PaintEditor.propTypes = {
|
||||||
rtl: PropTypes.bool,
|
rtl: PropTypes.bool,
|
||||||
setLayout: PropTypes.func.isRequired,
|
setLayout: PropTypes.func.isRequired,
|
||||||
setSelectedItems: PropTypes.func.isRequired,
|
setSelectedItems: PropTypes.func.isRequired,
|
||||||
textEditing: PropTypes.bool.isRequired,
|
shouldShowRedo: PropTypes.func.isRequired,
|
||||||
undoSnapshot: PropTypes.func.isRequired,
|
shouldShowUndo: PropTypes.func.isRequired,
|
||||||
undoState: PropTypes.shape({
|
|
||||||
stack: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
||||||
pointer: PropTypes.number.isRequired
|
|
||||||
}),
|
|
||||||
updateViewBounds: PropTypes.func.isRequired,
|
updateViewBounds: PropTypes.func.isRequired,
|
||||||
viewBounds: PropTypes.instanceOf(paper.Matrix).isRequired
|
viewBounds: PropTypes.instanceOf(paper.Matrix).isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
changeColorToEyeDropper: state.scratchPaint.color.eyeDropper.callback,
|
changeColorToEyeDropper: state.scratchPaint.color.eyeDropper.callback,
|
||||||
clipboardItems: state.scratchPaint.clipboard.items,
|
|
||||||
format: state.scratchPaint.format,
|
format: state.scratchPaint.format,
|
||||||
isEyeDropping: state.scratchPaint.color.eyeDropper.active,
|
isEyeDropping: state.scratchPaint.color.eyeDropper.active,
|
||||||
mode: state.scratchPaint.mode,
|
mode: state.scratchPaint.mode,
|
||||||
pasteOffset: state.scratchPaint.clipboard.pasteOffset,
|
|
||||||
previousTool: state.scratchPaint.color.eyeDropper.previousTool,
|
previousTool: state.scratchPaint.color.eyeDropper.previousTool,
|
||||||
selectedItems: state.scratchPaint.selectedItems,
|
|
||||||
textEditing: state.scratchPaint.textEditTarget !== null,
|
|
||||||
undoState: state.scratchPaint.undo,
|
|
||||||
viewBounds: state.scratchPaint.viewBounds
|
viewBounds: state.scratchPaint.viewBounds
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
@ -545,21 +351,12 @@ const mapDispatchToProps = dispatch => ({
|
||||||
// set redux values to default for eye dropper reducer
|
// set redux values to default for eye dropper reducer
|
||||||
dispatch(deactivateEyeDropper());
|
dispatch(deactivateEyeDropper());
|
||||||
},
|
},
|
||||||
onUndo: format => {
|
|
||||||
dispatch(undo(format));
|
|
||||||
},
|
|
||||||
onRedo: format => {
|
|
||||||
dispatch(redo(format));
|
|
||||||
},
|
|
||||||
undoSnapshot: snapshot => {
|
|
||||||
dispatch(undoSnapshot(snapshot));
|
|
||||||
},
|
|
||||||
updateViewBounds: matrix => {
|
updateViewBounds: matrix => {
|
||||||
dispatch(updateViewBounds(matrix));
|
dispatch(updateViewBounds(matrix));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default SelectionHOC(CopyPasteHOC(connect(
|
export default UpdateImageHOC(SelectionHOC(UndoHOC(KeyboardShortcutsHOC(connect(
|
||||||
mapStateToProps,
|
mapStateToProps,
|
||||||
mapDispatchToProps
|
mapDispatchToProps
|
||||||
)(PaintEditor)));
|
)(PaintEditor)))));
|
||||||
|
|
|
@ -3,6 +3,7 @@ import {createCanvas, clearRaster, getRaster, hideGuideLayers, showGuideLayers}
|
||||||
import {getGuideColor} from './guides';
|
import {getGuideColor} from './guides';
|
||||||
import {clearSelection} from './selection';
|
import {clearSelection} from './selection';
|
||||||
import {inlineSvgFonts} from 'scratch-svg-renderer';
|
import {inlineSvgFonts} from 'scratch-svg-renderer';
|
||||||
|
import Formats from '../lib/format';
|
||||||
|
|
||||||
const forEachLinePoint = function (point1, point2, callback) {
|
const forEachLinePoint = function (point1, point2, callback) {
|
||||||
// Bresenham line algorithm
|
// Bresenham line algorithm
|
||||||
|
@ -374,7 +375,7 @@ const convertToBitmap = function (clearSelectedItems, onUpdateImage) {
|
||||||
new paper.Point(Math.floor(bounds.topLeft.x), Math.floor(bounds.topLeft.y)));
|
new paper.Point(Math.floor(bounds.topLeft.x), Math.floor(bounds.topLeft.y)));
|
||||||
}
|
}
|
||||||
paper.project.activeLayer.removeChildren();
|
paper.project.activeLayer.removeChildren();
|
||||||
onUpdateImage();
|
onUpdateImage(false /* skipSnapshot */, Formats.BITMAP /* formatOverride */);
|
||||||
};
|
};
|
||||||
img.onerror = () => {
|
img.onerror = () => {
|
||||||
// Fallback if browser does not support SVG data URIs in images.
|
// Fallback if browser does not support SVG data URIs in images.
|
||||||
|
@ -385,7 +386,7 @@ const convertToBitmap = function (clearSelectedItems, onUpdateImage) {
|
||||||
getRaster().drawImage(raster.canvas, raster.bounds.topLeft);
|
getRaster().drawImage(raster.canvas, raster.bounds.topLeft);
|
||||||
}
|
}
|
||||||
paper.project.activeLayer.removeChildren();
|
paper.project.activeLayer.removeChildren();
|
||||||
onUpdateImage();
|
onUpdateImage(false /* skipSnapshot */, Formats.BITMAP /* formatOverride */);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
// Hash tags will break image loading without being encoded first
|
// Hash tags will break image loading without being encoded first
|
||||||
|
@ -399,7 +400,7 @@ const convertToVector = function (clearSelectedItems, onUpdateImage) {
|
||||||
paper.project.activeLayer.addChild(trimmedRaster);
|
paper.project.activeLayer.addChild(trimmedRaster);
|
||||||
}
|
}
|
||||||
clearRaster();
|
clearRaster();
|
||||||
onUpdateImage();
|
onUpdateImage(false /* skipSnapshot */, Formats.VECTOR /* formatOverride */);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getColor_ = function (x, y, context) {
|
const getColor_ = function (x, y, context) {
|
||||||
|
|
|
@ -50,11 +50,10 @@ const CopyPasteHOC = function (WrappedComponent) {
|
||||||
this.props.setClipboardItems(clipboardItems);
|
this.props.setClipboardItems(clipboardItems);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Returns true if anything was pasted, false if nothing changed
|
|
||||||
handlePaste () {
|
handlePaste () {
|
||||||
clearSelection(this.props.clearSelectedItems);
|
clearSelection(this.props.clearSelectedItems);
|
||||||
|
|
||||||
if (this.props.clipboardItems.length === 0) return false;
|
if (this.props.clipboardItems.length === 0) return;
|
||||||
|
|
||||||
let items = [];
|
let items = [];
|
||||||
for (let i = 0; i < this.props.clipboardItems.length; i++) {
|
for (let i = 0; i < this.props.clipboardItems.length; i++) {
|
||||||
|
@ -63,7 +62,7 @@ const CopyPasteHOC = function (WrappedComponent) {
|
||||||
items.push(item);
|
items.push(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!items.length) return false;
|
if (!items.length) return;
|
||||||
// If pasting a group or non-raster to bitmap, rasterize first
|
// If pasting a group or non-raster to bitmap, rasterize first
|
||||||
if (isBitmap(this.props.format) && !(items.length === 1 && items[0] instanceof paper.Raster)) {
|
if (isBitmap(this.props.format) && !(items.length === 1 && items[0] instanceof paper.Raster)) {
|
||||||
const group = new paper.Group(items);
|
const group = new paper.Group(items);
|
||||||
|
@ -78,13 +77,15 @@ const CopyPasteHOC = function (WrappedComponent) {
|
||||||
}
|
}
|
||||||
this.props.incrementPasteOffset();
|
this.props.incrementPasteOffset();
|
||||||
this.props.setSelectedItems(this.props.format);
|
this.props.setSelectedItems(this.props.format);
|
||||||
return true;
|
this.props.onUpdateImage();
|
||||||
}
|
}
|
||||||
render () {
|
render () {
|
||||||
const componentProps = omit(this.props, [
|
const componentProps = omit(this.props, [
|
||||||
'clearSelectedItems',
|
'clearSelectedItems',
|
||||||
'clipboardItems',
|
'clipboardItems',
|
||||||
|
'format',
|
||||||
'incrementPasteOffset',
|
'incrementPasteOffset',
|
||||||
|
'mode',
|
||||||
'pasteOffset',
|
'pasteOffset',
|
||||||
'setClipboardItems',
|
'setClipboardItems',
|
||||||
'setSelectedItems']);
|
'setSelectedItems']);
|
||||||
|
@ -104,6 +105,7 @@ const CopyPasteHOC = function (WrappedComponent) {
|
||||||
format: PropTypes.oneOf(Object.keys(Formats)),
|
format: PropTypes.oneOf(Object.keys(Formats)),
|
||||||
incrementPasteOffset: PropTypes.func.isRequired,
|
incrementPasteOffset: PropTypes.func.isRequired,
|
||||||
mode: PropTypes.oneOf(Object.keys(Modes)),
|
mode: PropTypes.oneOf(Object.keys(Modes)),
|
||||||
|
onUpdateImage: PropTypes.func.isRequired,
|
||||||
pasteOffset: PropTypes.number,
|
pasteOffset: PropTypes.number,
|
||||||
setClipboardItems: PropTypes.func.isRequired,
|
setClipboardItems: PropTypes.func.isRequired,
|
||||||
setSelectedItems: PropTypes.func.isRequired
|
setSelectedItems: PropTypes.func.isRequired
|
132
src/hocs/keyboard-shortcuts-hoc.jsx
Normal file
132
src/hocs/keyboard-shortcuts-hoc.jsx
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
import bindAll from 'lodash.bindall';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import omit from 'lodash.omit';
|
||||||
|
import {connect} from 'react-redux';
|
||||||
|
|
||||||
|
import CopyPasteHOC from './copy-paste-hoc.jsx';
|
||||||
|
|
||||||
|
import {selectAllBitmap} from '../helper/bitmap';
|
||||||
|
import {clearSelection, deleteSelection, getSelectedLeafItems,
|
||||||
|
selectAllItems, selectAllSegments} from '../helper/selection';
|
||||||
|
import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items';
|
||||||
|
import {changeMode} from '../reducers/modes';
|
||||||
|
|
||||||
|
import {isBitmap} from '../lib/format';
|
||||||
|
import Formats from '../lib/format';
|
||||||
|
import Modes from '../lib/modes';
|
||||||
|
|
||||||
|
const KeyboardShortcutsHOC = function (WrappedComponent) {
|
||||||
|
class KeyboardShortcutsWrapper extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props);
|
||||||
|
bindAll(this, [
|
||||||
|
'handleKeyPress',
|
||||||
|
'changeToASelectMode',
|
||||||
|
'selectAll'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
handleKeyPress (event) {
|
||||||
|
// Don't activate keyboard shortcuts during text editing
|
||||||
|
if (this.props.textEditing) return;
|
||||||
|
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
event.preventDefault();
|
||||||
|
clearSelection(this.props.clearSelectedItems);
|
||||||
|
} else if (event.key === 'Delete' || event.key === 'Backspace') {
|
||||||
|
if (deleteSelection(this.props.mode, this.props.onUpdateImage)) {
|
||||||
|
this.props.setSelectedItems(this.props.format);
|
||||||
|
}
|
||||||
|
} else if (event.metaKey || event.ctrlKey) {
|
||||||
|
if (event.shiftKey && event.key === 'z') {
|
||||||
|
this.props.onRedo();
|
||||||
|
} else if (event.key === 'z') {
|
||||||
|
this.props.onUndo();
|
||||||
|
} else if (event.key === 'c') {
|
||||||
|
this.props.onCopyToClipboard();
|
||||||
|
} else if (event.key === 'v') {
|
||||||
|
this.changeToASelectMode();
|
||||||
|
this.props.onPasteFromClipboard();
|
||||||
|
} else if (event.key === 'a') {
|
||||||
|
this.changeToASelectMode();
|
||||||
|
event.preventDefault();
|
||||||
|
this.selectAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
changeToASelectMode () {
|
||||||
|
if (isBitmap(this.props.format)) {
|
||||||
|
if (this.props.mode !== Modes.BIT_SELECT) {
|
||||||
|
this.props.changeMode(Modes.BIT_SELECT);
|
||||||
|
}
|
||||||
|
} else if (this.props.mode !== Modes.SELECT && this.props.mode !== Modes.RESHAPE) {
|
||||||
|
this.props.changeMode(Modes.SELECT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
selectAll () {
|
||||||
|
if (isBitmap(this.props.format)) {
|
||||||
|
selectAllBitmap(this.props.clearSelectedItems);
|
||||||
|
this.props.setSelectedItems(this.props.format);
|
||||||
|
} else if (this.props.mode === Modes.RESHAPE) {
|
||||||
|
if (selectAllSegments()) this.props.setSelectedItems(this.props.format);
|
||||||
|
} else if (selectAllItems()) {
|
||||||
|
this.props.setSelectedItems(this.props.format);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render () {
|
||||||
|
const componentProps = omit(this.props, [
|
||||||
|
'changeMode',
|
||||||
|
'clearSelectedItems',
|
||||||
|
'format',
|
||||||
|
'mode',
|
||||||
|
'onCopyToClipboard',
|
||||||
|
'onPasteFromClipboard',
|
||||||
|
'setSelectedItems',
|
||||||
|
'textEditing']);
|
||||||
|
return (
|
||||||
|
<WrappedComponent
|
||||||
|
onKeyPress={this.handleKeyPress}
|
||||||
|
{...componentProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyboardShortcutsWrapper.propTypes = {
|
||||||
|
changeMode: PropTypes.func.isRequired,
|
||||||
|
clearSelectedItems: PropTypes.func.isRequired,
|
||||||
|
format: PropTypes.oneOf(Object.keys(Formats)),
|
||||||
|
mode: PropTypes.oneOf(Object.keys(Modes)).isRequired,
|
||||||
|
onCopyToClipboard: PropTypes.func.isRequired,
|
||||||
|
onPasteFromClipboard: PropTypes.func.isRequired,
|
||||||
|
onRedo: PropTypes.func.isRequired,
|
||||||
|
onUndo: PropTypes.func.isRequired,
|
||||||
|
onUpdateImage: PropTypes.func.isRequired,
|
||||||
|
setSelectedItems: PropTypes.func.isRequired,
|
||||||
|
textEditing: PropTypes.bool.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
mode: state.scratchPaint.mode,
|
||||||
|
format: state.scratchPaint.format,
|
||||||
|
textEditing: state.scratchPaint.textEditTarget !== null
|
||||||
|
});
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
changeMode: mode => {
|
||||||
|
dispatch(changeMode(mode));
|
||||||
|
},
|
||||||
|
clearSelectedItems: () => {
|
||||||
|
dispatch(clearSelectedItems());
|
||||||
|
},
|
||||||
|
setSelectedItems: format => {
|
||||||
|
dispatch(setSelectedItems(getSelectedLeafItems(), isBitmap(format)));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return CopyPasteHOC(connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(KeyboardShortcutsWrapper));
|
||||||
|
};
|
||||||
|
|
||||||
|
export default KeyboardShortcutsHOC;
|
95
src/hocs/undo-hoc.jsx
Normal file
95
src/hocs/undo-hoc.jsx
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
import bindAll from 'lodash.bindall';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import omit from 'lodash.omit';
|
||||||
|
import {connect} from 'react-redux';
|
||||||
|
|
||||||
|
import {getSelectedLeafItems} from '../helper/selection';
|
||||||
|
import {setSelectedItems} from '../reducers/selected-items';
|
||||||
|
import {performUndo, performRedo, shouldShowUndo, shouldShowRedo} from '../helper/undo';
|
||||||
|
import {undo, redo} from '../reducers/undo';
|
||||||
|
|
||||||
|
import {isBitmap} from '../lib/format';
|
||||||
|
import Formats from '../lib/format';
|
||||||
|
|
||||||
|
const UndoHOC = function (WrappedComponent) {
|
||||||
|
class UndoWrapper extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props);
|
||||||
|
bindAll(this, [
|
||||||
|
'handleUndo',
|
||||||
|
'handleRedo',
|
||||||
|
'handleSetSelectedItems',
|
||||||
|
'shouldShowUndo',
|
||||||
|
'shouldShowRedo'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
handleUndo () {
|
||||||
|
performUndo(this.props.undoState, this.props.onUndo, this.handleSetSelectedItems, this.props.onUpdateImage);
|
||||||
|
}
|
||||||
|
handleRedo () {
|
||||||
|
performRedo(this.props.undoState, this.props.onRedo, this.handleSetSelectedItems, this.props.onUpdateImage);
|
||||||
|
}
|
||||||
|
handleSetSelectedItems () {
|
||||||
|
this.props.setSelectedItems(this.props.format);
|
||||||
|
}
|
||||||
|
shouldShowUndo () {
|
||||||
|
return shouldShowUndo(this.props.undoState);
|
||||||
|
}
|
||||||
|
shouldShowRedo () {
|
||||||
|
return shouldShowRedo(this.props.undoState);
|
||||||
|
}
|
||||||
|
render () {
|
||||||
|
const componentProps = omit(this.props, [
|
||||||
|
'format',
|
||||||
|
'onUndo',
|
||||||
|
'onRedo',
|
||||||
|
'setSelectedItems',
|
||||||
|
'undoState']);
|
||||||
|
return (
|
||||||
|
<WrappedComponent
|
||||||
|
shouldShowRedo={this.shouldShowRedo}
|
||||||
|
shouldShowUndo={this.shouldShowUndo}
|
||||||
|
onRedo={this.handleRedo}
|
||||||
|
onUndo={this.handleUndo}
|
||||||
|
{...componentProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UndoWrapper.propTypes = {
|
||||||
|
format: PropTypes.oneOf(Object.keys(Formats)),
|
||||||
|
onRedo: PropTypes.func.isRequired,
|
||||||
|
onUndo: PropTypes.func.isRequired,
|
||||||
|
onUpdateImage: PropTypes.func.isRequired,
|
||||||
|
setSelectedItems: PropTypes.func.isRequired,
|
||||||
|
undoState: PropTypes.shape({
|
||||||
|
stack: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
pointer: PropTypes.number.isRequired
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
format: state.scratchPaint.format,
|
||||||
|
undoState: state.scratchPaint.undo
|
||||||
|
});
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
setSelectedItems: format => {
|
||||||
|
dispatch(setSelectedItems(getSelectedLeafItems(), isBitmap(format)));
|
||||||
|
},
|
||||||
|
onUndo: format => {
|
||||||
|
dispatch(undo(format));
|
||||||
|
},
|
||||||
|
onRedo: format => {
|
||||||
|
dispatch(redo(format));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(UndoWrapper);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UndoHOC;
|
154
src/hocs/update-image-hoc.jsx
Normal file
154
src/hocs/update-image-hoc.jsx
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
import paper from '@scratch/paper';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import log from '../log/log';
|
||||||
|
import bindAll from 'lodash.bindall';
|
||||||
|
import React from 'react';
|
||||||
|
import omit from 'lodash.omit';
|
||||||
|
import {connect} from 'react-redux';
|
||||||
|
|
||||||
|
import {undoSnapshot} from '../reducers/undo';
|
||||||
|
import {setSelectedItems} from '../reducers/selected-items';
|
||||||
|
|
||||||
|
import {getSelectedLeafItems} from '../helper/selection';
|
||||||
|
import {getRaster, hideGuideLayers, showGuideLayers} from '../helper/layer';
|
||||||
|
import {commitSelectionToBitmap, getHitBounds} from '../helper/bitmap';
|
||||||
|
import {performSnapshot} from '../helper/undo';
|
||||||
|
import {scaleWithStrokes} from '../helper/math';
|
||||||
|
import {ART_BOARD_WIDTH, ART_BOARD_HEIGHT, SVG_ART_BOARD_WIDTH, SVG_ART_BOARD_HEIGHT} from '../helper/view';
|
||||||
|
|
||||||
|
import Modes from '../lib/modes';
|
||||||
|
import {BitmapModes} from '../lib/modes';
|
||||||
|
import Formats from '../lib/format';
|
||||||
|
import {isBitmap, isVector} from '../lib/format';
|
||||||
|
|
||||||
|
const UpdateImageHOC = function (WrappedComponent) {
|
||||||
|
class UpdateImageWrapper extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props);
|
||||||
|
bindAll(this, [
|
||||||
|
'handleUpdateImage',
|
||||||
|
'handleUpdateBitmap',
|
||||||
|
'handleUpdateVector'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param {?boolean} skipSnapshot True if the call to update image should not trigger saving
|
||||||
|
* an undo state. For instance after calling undo.
|
||||||
|
* @param {?Formats} formatOverride Normally the mode is used to determine the format of the image,
|
||||||
|
* but the format used can be overridden here. In particular when converting between formats,
|
||||||
|
* the does not accurately represent the format.
|
||||||
|
*/
|
||||||
|
handleUpdateImage (skipSnapshot, formatOverride) {
|
||||||
|
// If in the middle of switching formats, rely on the current mode instead of format.
|
||||||
|
const actualFormat = formatOverride ? formatOverride :
|
||||||
|
BitmapModes[this.props.mode] ? Formats.BITMAP : Formats.VECTOR;
|
||||||
|
if (isBitmap(actualFormat)) {
|
||||||
|
this.handleUpdateBitmap(skipSnapshot);
|
||||||
|
} else if (isVector(actualFormat)) {
|
||||||
|
this.handleUpdateVector(skipSnapshot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handleUpdateBitmap (skipSnapshot) {
|
||||||
|
if (!getRaster().loaded) {
|
||||||
|
// In general, callers of updateImage should wait for getRaster().loaded = true before
|
||||||
|
// calling updateImage.
|
||||||
|
// However, this may happen if the user is rapidly undoing/redoing. In this case it's safe
|
||||||
|
// to skip the update.
|
||||||
|
log.warn('Bitmap layer should be loaded before calling updateImage.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Plaster the selection onto the raster layer before exporting, if there is a selection.
|
||||||
|
const plasteredRaster = getRaster().getSubRaster(getRaster().bounds);
|
||||||
|
plasteredRaster.remove(); // Don't insert
|
||||||
|
const selectedItems = getSelectedLeafItems();
|
||||||
|
if (selectedItems.length === 1 && selectedItems[0] instanceof paper.Raster) {
|
||||||
|
if (!selectedItems[0].loaded ||
|
||||||
|
(selectedItems[0].data &&
|
||||||
|
selectedItems[0].data.expanded &&
|
||||||
|
!selectedItems[0].data.expanded.loaded)) {
|
||||||
|
// This may get logged when rapidly undoing/redoing or changing costumes,
|
||||||
|
// in which case the warning is not relevant.
|
||||||
|
log.warn('Bitmap layer should be loaded before calling updateImage.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
commitSelectionToBitmap(selectedItems[0], plasteredRaster);
|
||||||
|
}
|
||||||
|
const rect = getHitBounds(plasteredRaster);
|
||||||
|
this.props.onUpdateImage(
|
||||||
|
false /* isVector */,
|
||||||
|
plasteredRaster.getImageData(rect),
|
||||||
|
(ART_BOARD_WIDTH / 2) - rect.x,
|
||||||
|
(ART_BOARD_HEIGHT / 2) - rect.y);
|
||||||
|
|
||||||
|
if (!skipSnapshot) {
|
||||||
|
performSnapshot(this.props.undoSnapshot, Formats.BITMAP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handleUpdateVector (skipSnapshot) {
|
||||||
|
const guideLayers = hideGuideLayers(true /* includeRaster */);
|
||||||
|
|
||||||
|
// Export at 0.5x
|
||||||
|
scaleWithStrokes(paper.project.activeLayer, .5, new paper.Point());
|
||||||
|
const bounds = paper.project.activeLayer.bounds;
|
||||||
|
// @todo (https://github.com/LLK/scratch-paint/issues/445) generate view box
|
||||||
|
this.props.onUpdateImage(
|
||||||
|
true /* isVector */,
|
||||||
|
paper.project.exportSVG({
|
||||||
|
asString: true,
|
||||||
|
bounds: 'content',
|
||||||
|
matrix: new paper.Matrix().translate(-bounds.x, -bounds.y)
|
||||||
|
}),
|
||||||
|
(SVG_ART_BOARD_WIDTH / 2) - bounds.x,
|
||||||
|
(SVG_ART_BOARD_HEIGHT / 2) - bounds.y);
|
||||||
|
scaleWithStrokes(paper.project.activeLayer, 2, new paper.Point());
|
||||||
|
paper.project.activeLayer.applyMatrix = true;
|
||||||
|
|
||||||
|
showGuideLayers(guideLayers);
|
||||||
|
|
||||||
|
if (!skipSnapshot) {
|
||||||
|
performSnapshot(this.props.undoSnapshot, Formats.VECTOR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render () {
|
||||||
|
const componentProps = omit(this.props, [
|
||||||
|
'format',
|
||||||
|
'onUpdateImage',
|
||||||
|
'undoSnapshot'
|
||||||
|
]);
|
||||||
|
return (
|
||||||
|
<WrappedComponent
|
||||||
|
onUpdateImage={this.handleUpdateImage}
|
||||||
|
{...componentProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateImageWrapper.propTypes = {
|
||||||
|
format: PropTypes.oneOf(Object.keys(Formats)),
|
||||||
|
mode: PropTypes.oneOf(Object.keys(Modes)).isRequired,
|
||||||
|
onUpdateImage: PropTypes.func.isRequired,
|
||||||
|
undoSnapshot: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
format: state.scratchPaint.format,
|
||||||
|
mode: state.scratchPaint.mode,
|
||||||
|
undoState: state.scratchPaint.undo
|
||||||
|
});
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
setSelectedItems: format => {
|
||||||
|
dispatch(setSelectedItems(getSelectedLeafItems(), isBitmap(format)));
|
||||||
|
},
|
||||||
|
undoSnapshot: snapshot => {
|
||||||
|
dispatch(undoSnapshot(snapshot));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(UpdateImageWrapper);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UpdateImageHOC;
|
Loading…
Reference in a new issue