add copy/paste

This commit is contained in:
DD 2017-11-01 16:57:02 -04:00
parent 1a654a22ed
commit 7e1e8d96ac
8 changed files with 127 additions and 22 deletions

View file

@ -1,3 +1,4 @@
import paper from '@scratch/paper';
import classNames from 'classnames'; import classNames from 'classnames';
import {connect} from 'react-redux'; import {connect} from 'react-redux';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
@ -115,16 +116,16 @@ const ModeToolsComponent = props => {
<div className={classNames(props.className, styles.modeTools)}> <div className={classNames(props.className, styles.modeTools)}>
<InputGroup className={classNames(styles.modDashedBorder, styles.modLabeledIconHeight)}> <InputGroup className={classNames(styles.modDashedBorder, styles.modLabeledIconHeight)}>
<LabeledIconButton <LabeledIconButton
disabled disabled={!props.selectedItems.length}
imgSrc={copyIcon} imgSrc={copyIcon}
title={props.intl.formatMessage(messages.copy)} title={props.intl.formatMessage(messages.copy)}
onClick={function () {}} onClick={props.onCopyToClipboard}
/> />
<LabeledIconButton <LabeledIconButton
disabled disabled={!(props.clipboard.length > 0)}
imgSrc={pasteIcon} imgSrc={pasteIcon}
title={props.intl.formatMessage(messages.paste)} title={props.intl.formatMessage(messages.paste)}
onClick={function () {}} onClick={props.onPasteFromClipboard}
/> />
</InputGroup> </InputGroup>
{/* <LabeledIconButton {/* <LabeledIconButton
@ -152,17 +153,23 @@ const ModeToolsComponent = props => {
ModeToolsComponent.propTypes = { ModeToolsComponent.propTypes = {
brushValue: PropTypes.number, brushValue: PropTypes.number,
className: PropTypes.string, className: PropTypes.string,
clipboard: PropTypes.arrayOf(PropTypes.array),
eraserValue: PropTypes.number, eraserValue: PropTypes.number,
intl: intlShape.isRequired, intl: intlShape.isRequired,
mode: PropTypes.string.isRequired, mode: PropTypes.string.isRequired,
onBrushSliderChange: PropTypes.func, onBrushSliderChange: PropTypes.func,
onEraserSliderChange: PropTypes.func onCopyToClipboard: PropTypes.func.isRequired,
onEraserSliderChange: PropTypes.func,
onPasteFromClipboard: PropTypes.func.isRequired,
selectedItems: PropTypes.arrayOf(PropTypes.instanceOf(paper.Item))
}; };
const mapStateToProps = state => ({ const mapStateToProps = state => ({
mode: state.scratchPaint.mode, mode: state.scratchPaint.mode,
brushValue: state.scratchPaint.brushMode.brushSize, brushValue: state.scratchPaint.brushMode.brushSize,
eraserValue: state.scratchPaint.eraserMode.brushSize clipboard: state.scratchPaint.clipboard,
eraserValue: state.scratchPaint.eraserMode.brushSize,
selectedItems: state.scratchPaint.selectedItems
}); });
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({
onBrushSliderChange: brushSize => { onBrushSliderChange: brushSize => {

View file

@ -247,7 +247,10 @@ class PaintEditorComponent extends React.Component {
/> />
</InputGroup> </InputGroup>
<InputGroup className={styles.modModeTools}> <InputGroup className={styles.modModeTools}>
<ModeToolsComponent /> <ModeToolsComponent
onCopyToClipboard={this.props.onCopyToClipboard}
onPasteFromClipboard={this.props.onPasteFromClipboard}
/>
</InputGroup> </InputGroup>
</div> </div>
</div> </div>
@ -339,7 +342,9 @@ PaintEditorComponent.propTypes = {
canUndo: PropTypes.func.isRequired, canUndo: PropTypes.func.isRequired,
intl: intlShape, intl: intlShape,
name: PropTypes.string, name: PropTypes.string,
onCopyToClipboard: PropTypes.func.isRequired,
onGroup: PropTypes.func.isRequired, onGroup: PropTypes.func.isRequired,
onPasteFromClipboard: PropTypes.func.isRequired,
onRedo: PropTypes.func.isRequired, onRedo: PropTypes.func.isRequired,
onSendBackward: PropTypes.func.isRequired, onSendBackward: PropTypes.func.isRequired,
onSendForward: PropTypes.func.isRequired, onSendForward: PropTypes.func.isRequired,

View file

@ -5,12 +5,13 @@ import PaintEditorComponent from '../components/paint-editor/paint-editor.jsx';
import {changeMode} from '../reducers/modes'; import {changeMode} from '../reducers/modes';
import {undo, redo, undoSnapshot} from '../reducers/undo'; import {undo, redo, undoSnapshot} from '../reducers/undo';
import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items'; import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items';
import {setClipboardItems} from '../reducers/clipboard';
import {getGuideLayer, getBackgroundGuideLayer} from '../helper/layer'; import {getGuideLayer, getBackgroundGuideLayer} from '../helper/layer';
import {performUndo, performRedo, performSnapshot, shouldShowUndo, shouldShowRedo} from '../helper/undo'; import {performUndo, performRedo, performSnapshot, shouldShowUndo, shouldShowRedo} from '../helper/undo';
import {bringToFront, sendBackward, sendToBack, bringForward} from '../helper/order'; import {bringToFront, sendBackward, sendToBack, bringForward} from '../helper/order';
import {groupSelection, ungroupSelection} from '../helper/group'; import {groupSelection, ungroupSelection} from '../helper/group';
import {getSelectedLeafItems} from '../helper/selection'; import {clearSelection, getSelectedLeafItems, getSelectedRootItems} from '../helper/selection';
import {resetZoom, zoomOnSelection} from '../helper/view'; import {resetZoom, zoomOnSelection} from '../helper/view';
import Modes from '../modes/modes'; import Modes from '../modes/modes';
@ -35,7 +36,9 @@ class PaintEditor extends React.Component {
'handleGroup', 'handleGroup',
'handleUngroup', 'handleUngroup',
'canRedo', 'canRedo',
'canUndo' 'canUndo',
'handleCopyToClipboard',
'handlePasteFromClipboard'
]); ]);
} }
componentDidMount () { componentDidMount () {
@ -98,6 +101,33 @@ class PaintEditor extends React.Component {
handleSendToFront () { handleSendToFront () {
bringToFront(this.handleUpdateSvg); bringToFront(this.handleUpdateSvg);
} }
handleCopyToClipboard () {
const selectedItems = getSelectedRootItems();
if (selectedItems.length > 0) {
const clipboardItems = [];
for (let i = 0; i < selectedItems.length; i++) {
const jsonItem = selectedItems[i].exportJSON({asString: false});
clipboardItems.push(jsonItem);
}
this.props.setClipboardItems(clipboardItems);
}
}
handlePasteFromClipboard () {
clearSelection(this.props.clearSelectedItems);
if (this.props.clipboard.length > 0) {
for (let i = 0; i < this.props.clipboard.length; i++) {
const item = paper.Base.importJSON(this.props.clipboard[i]);
if (item) {
item.selected = true;
}
paper.project.getActiveLayer().addChild(item);
}
this.props.setSelectedItems();
paper.project.view.update();
this.handleUpdateSvg();
}
}
canUndo () { canUndo () {
return shouldShowUndo(this.props.undoState); return shouldShowUndo(this.props.undoState);
} }
@ -123,7 +153,9 @@ class PaintEditor extends React.Component {
rotationCenterY={this.props.rotationCenterY} rotationCenterY={this.props.rotationCenterY}
svg={this.props.svg} svg={this.props.svg}
svgId={this.props.svgId} svgId={this.props.svgId}
onCopyToClipboard={this.handleCopyToClipboard}
onGroup={this.handleGroup} onGroup={this.handleGroup}
onPasteFromClipboard={this.handlePasteFromClipboard}
onRedo={this.handleRedo} onRedo={this.handleRedo}
onSendBackward={this.handleSendBackward} onSendBackward={this.handleSendBackward}
onSendForward={this.handleSendForward} onSendForward={this.handleSendForward}
@ -143,6 +175,7 @@ class PaintEditor extends React.Component {
PaintEditor.propTypes = { PaintEditor.propTypes = {
clearSelectedItems: PropTypes.func.isRequired, clearSelectedItems: PropTypes.func.isRequired,
clipboard: PropTypes.arrayOf(PropTypes.array),
name: PropTypes.string, name: PropTypes.string,
onKeyPress: PropTypes.func.isRequired, onKeyPress: PropTypes.func.isRequired,
onRedo: PropTypes.func.isRequired, onRedo: PropTypes.func.isRequired,
@ -151,6 +184,7 @@ PaintEditor.propTypes = {
onUpdateSvg: PropTypes.func.isRequired, onUpdateSvg: PropTypes.func.isRequired,
rotationCenterX: PropTypes.number, rotationCenterX: PropTypes.number,
rotationCenterY: PropTypes.number, rotationCenterY: PropTypes.number,
setClipboardItems: PropTypes.func.isRequired,
setSelectedItems: PropTypes.func.isRequired, setSelectedItems: PropTypes.func.isRequired,
svg: PropTypes.string, svg: PropTypes.string,
svgId: PropTypes.string, svgId: PropTypes.string,
@ -163,7 +197,8 @@ PaintEditor.propTypes = {
const mapStateToProps = state => ({ const mapStateToProps = state => ({
selectedItems: state.scratchPaint.selectedItems, selectedItems: state.scratchPaint.selectedItems,
undoState: state.scratchPaint.undo undoState: state.scratchPaint.undo,
clipboard: state.scratchPaint.clipboard
}); });
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({
onKeyPress: event => { onKeyPress: event => {
@ -191,6 +226,9 @@ const mapDispatchToProps = dispatch => ({
}, },
undoSnapshot: snapshot => { undoSnapshot: snapshot => {
dispatch(undoSnapshot(snapshot)); dispatch(undoSnapshot(snapshot));
},
setClipboardItems: items => {
dispatch(setClipboardItems(items));
} }
}); });

View file

@ -157,7 +157,7 @@ class PaperCanvas extends React.Component {
PaperCanvas.propTypes = { PaperCanvas.propTypes = {
canvasRef: PropTypes.func, canvasRef: PropTypes.func,
clearUndo: PropTypes.func.isRequired, clearUndo: PropTypes.func.isRequired,
mode: PropTypes.instanceOf(Modes), mode: PropTypes.oneOf(Object.values(Modes)),
onUpdateSvg: PropTypes.func.isRequired, onUpdateSvg: PropTypes.func.isRequired,
rotationCenterX: PropTypes.number, rotationCenterX: PropTypes.number,
rotationCenterY: PropTypes.number, rotationCenterY: PropTypes.number,

View file

@ -394,14 +394,6 @@ const selectRootItem = function () {
} }
}; };
const shouldShowIfSelection = function () {
return getSelectedRootItems().length > 0;
};
const shouldShowIfSelectionRecursive = function () {
return getSelectedRootItems().length > 0;
};
const shouldShowSelectAll = function () { const shouldShowSelectAll = function () {
return paper.project.getItems({class: paper.PathItem}).length > 0; return paper.project.getItems({class: paper.PathItem}).length > 0;
}; };
@ -419,7 +411,5 @@ export {
getSelectedRootItems, getSelectedRootItems,
processRectangularSelection, processRectangularSelection,
selectRootItem, selectRootItem,
shouldShowIfSelection,
shouldShowIfSelectionRecursive,
shouldShowSelectAll shouldShowSelectAll
}; };

31
src/reducers/clipboard.js Normal file
View file

@ -0,0 +1,31 @@
import log from '../log/log';
const SET = 'scratch-paint/clipboard/SET';
const initialState = [];
const reducer = function (state, action) {
if (typeof state === 'undefined') state = initialState;
switch (action.type) {
case SET:
if (!action.clipboardItems || !(action.clipboardItems instanceof Array) || action.clipboardItems.length === 0) {
log.warn(`Invalid clipboard item format`);
return state;
}
return action.clipboardItems;
default:
return state;
}
};
// Action creators ==================================
const setClipboardItems = function (clipboardItems) {
return {
type: SET,
clipboardItems: clipboardItems
};
};
export {
reducer as default,
setClipboardItems
};

View file

@ -3,6 +3,7 @@ import modeReducer from './modes';
import brushModeReducer from './brush-mode'; import brushModeReducer from './brush-mode';
import eraserModeReducer from './eraser-mode'; import eraserModeReducer from './eraser-mode';
import colorReducer from './color'; import colorReducer from './color';
import clipboardReducer from './clipboard';
import hoverReducer from './hover'; import hoverReducer from './hover';
import modalsReducer from './modals'; import modalsReducer from './modals';
import selectedItemReducer from './selected-items'; import selectedItemReducer from './selected-items';
@ -11,8 +12,9 @@ import undoReducer from './undo';
export default combineReducers({ export default combineReducers({
mode: modeReducer, mode: modeReducer,
brushMode: brushModeReducer, brushMode: brushModeReducer,
eraserMode: eraserModeReducer,
color: colorReducer, color: colorReducer,
clipboard: clipboardReducer,
eraserMode: eraserModeReducer,
hoveredItemId: hoverReducer, hoveredItemId: hoverReducer,
modals: modalsReducer, modals: modalsReducer,
selectedItems: selectedItemReducer, selectedItems: selectedItemReducer,

View file

@ -0,0 +1,32 @@
/* eslint-env jest */
import clipboardReducer from '../../src/reducers/clipboard';
import {setClipboardItems} from '../../src/reducers/clipboard';
test('initialState', () => {
let defaultState;
expect(clipboardReducer(defaultState /* state */, {type: 'anything'} /* action */)).toBeDefined();
});
test('setClipboardItems', () => {
let defaultState;
const newSelected1 = ['selected1', 'selected2'];
const newSelected2 = ['selected1', 'selected3'];
expect(clipboardReducer(defaultState /* state */, setClipboardItems(newSelected1) /* action */))
.toEqual(newSelected1);
expect(clipboardReducer(newSelected1, setClipboardItems(newSelected2) /* action */))
.toEqual(newSelected2);
});
test('invalidSetClipboardItems', () => {
const origState = ['selected1', 'selected2'];
const nothingSelected = [];
expect(clipboardReducer(origState /* state */, setClipboardItems() /* action */))
.toBe(origState);
expect(clipboardReducer(origState /* state */, setClipboardItems('notAnArray') /* action */))
.toBe(origState);
expect(clipboardReducer(origState /* state */, setClipboardItems(nothingSelected) /* action */))
.toBe(origState);
});