diff --git a/src/components/mode-tools/mode-tools.jsx b/src/components/mode-tools/mode-tools.jsx
index 7c5fbc7b..22c7d47b 100644
--- a/src/components/mode-tools/mode-tools.jsx
+++ b/src/components/mode-tools/mode-tools.jsx
@@ -1,3 +1,4 @@
+import paper from '@scratch/paper';
import classNames from 'classnames';
import {connect} from 'react-redux';
import PropTypes from 'prop-types';
@@ -115,16 +116,16 @@ const ModeToolsComponent = props => {
0)}
imgSrc={pasteIcon}
title={props.intl.formatMessage(messages.paste)}
- onClick={function () {}}
+ onClick={props.onPasteFromClipboard}
/>
{/* {
ModeToolsComponent.propTypes = {
brushValue: PropTypes.number,
className: PropTypes.string,
+ clipboard: PropTypes.arrayOf(PropTypes.array),
eraserValue: PropTypes.number,
intl: intlShape.isRequired,
mode: PropTypes.string.isRequired,
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 => ({
mode: state.scratchPaint.mode,
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 => ({
onBrushSliderChange: brushSize => {
diff --git a/src/components/paint-editor/paint-editor.jsx b/src/components/paint-editor/paint-editor.jsx
index 6ea036a1..c010102a 100644
--- a/src/components/paint-editor/paint-editor.jsx
+++ b/src/components/paint-editor/paint-editor.jsx
@@ -247,7 +247,10 @@ class PaintEditorComponent extends React.Component {
/>
-
+
@@ -339,7 +342,9 @@ PaintEditorComponent.propTypes = {
canUndo: PropTypes.func.isRequired,
intl: intlShape,
name: PropTypes.string,
+ onCopyToClipboard: PropTypes.func.isRequired,
onGroup: PropTypes.func.isRequired,
+ onPasteFromClipboard: PropTypes.func.isRequired,
onRedo: PropTypes.func.isRequired,
onSendBackward: PropTypes.func.isRequired,
onSendForward: PropTypes.func.isRequired,
diff --git a/src/containers/paint-editor.jsx b/src/containers/paint-editor.jsx
index 9156b6e6..79803d34 100644
--- a/src/containers/paint-editor.jsx
+++ b/src/containers/paint-editor.jsx
@@ -5,12 +5,13 @@ import PaintEditorComponent from '../components/paint-editor/paint-editor.jsx';
import {changeMode} from '../reducers/modes';
import {undo, redo, undoSnapshot} from '../reducers/undo';
import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items';
+import {setClipboardItems} from '../reducers/clipboard';
import {getGuideLayer, getBackgroundGuideLayer} from '../helper/layer';
import {performUndo, performRedo, performSnapshot, shouldShowUndo, shouldShowRedo} from '../helper/undo';
import {bringToFront, sendBackward, sendToBack, bringForward} from '../helper/order';
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 Modes from '../modes/modes';
@@ -35,7 +36,9 @@ class PaintEditor extends React.Component {
'handleGroup',
'handleUngroup',
'canRedo',
- 'canUndo'
+ 'canUndo',
+ 'handleCopyToClipboard',
+ 'handlePasteFromClipboard'
]);
}
componentDidMount () {
@@ -98,6 +101,33 @@ class PaintEditor extends React.Component {
handleSendToFront () {
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 () {
return shouldShowUndo(this.props.undoState);
}
@@ -123,7 +153,9 @@ class PaintEditor extends React.Component {
rotationCenterY={this.props.rotationCenterY}
svg={this.props.svg}
svgId={this.props.svgId}
+ onCopyToClipboard={this.handleCopyToClipboard}
onGroup={this.handleGroup}
+ onPasteFromClipboard={this.handlePasteFromClipboard}
onRedo={this.handleRedo}
onSendBackward={this.handleSendBackward}
onSendForward={this.handleSendForward}
@@ -143,6 +175,7 @@ class PaintEditor extends React.Component {
PaintEditor.propTypes = {
clearSelectedItems: PropTypes.func.isRequired,
+ clipboard: PropTypes.arrayOf(PropTypes.array),
name: PropTypes.string,
onKeyPress: PropTypes.func.isRequired,
onRedo: PropTypes.func.isRequired,
@@ -151,6 +184,7 @@ PaintEditor.propTypes = {
onUpdateSvg: PropTypes.func.isRequired,
rotationCenterX: PropTypes.number,
rotationCenterY: PropTypes.number,
+ setClipboardItems: PropTypes.func.isRequired,
setSelectedItems: PropTypes.func.isRequired,
svg: PropTypes.string,
svgId: PropTypes.string,
@@ -163,7 +197,8 @@ PaintEditor.propTypes = {
const mapStateToProps = state => ({
selectedItems: state.scratchPaint.selectedItems,
- undoState: state.scratchPaint.undo
+ undoState: state.scratchPaint.undo,
+ clipboard: state.scratchPaint.clipboard
});
const mapDispatchToProps = dispatch => ({
onKeyPress: event => {
@@ -191,6 +226,9 @@ const mapDispatchToProps = dispatch => ({
},
undoSnapshot: snapshot => {
dispatch(undoSnapshot(snapshot));
+ },
+ setClipboardItems: items => {
+ dispatch(setClipboardItems(items));
}
});
diff --git a/src/containers/paper-canvas.jsx b/src/containers/paper-canvas.jsx
index 28efc18b..565f23bf 100644
--- a/src/containers/paper-canvas.jsx
+++ b/src/containers/paper-canvas.jsx
@@ -157,7 +157,7 @@ class PaperCanvas extends React.Component {
PaperCanvas.propTypes = {
canvasRef: PropTypes.func,
clearUndo: PropTypes.func.isRequired,
- mode: PropTypes.instanceOf(Modes),
+ mode: PropTypes.oneOf(Object.values(Modes)),
onUpdateSvg: PropTypes.func.isRequired,
rotationCenterX: PropTypes.number,
rotationCenterY: PropTypes.number,
diff --git a/src/helper/selection.js b/src/helper/selection.js
index 9f11b496..a6ad9230 100644
--- a/src/helper/selection.js
+++ b/src/helper/selection.js
@@ -394,14 +394,6 @@ const selectRootItem = function () {
}
};
-const shouldShowIfSelection = function () {
- return getSelectedRootItems().length > 0;
-};
-
-const shouldShowIfSelectionRecursive = function () {
- return getSelectedRootItems().length > 0;
-};
-
const shouldShowSelectAll = function () {
return paper.project.getItems({class: paper.PathItem}).length > 0;
};
@@ -419,7 +411,5 @@ export {
getSelectedRootItems,
processRectangularSelection,
selectRootItem,
- shouldShowIfSelection,
- shouldShowIfSelectionRecursive,
shouldShowSelectAll
};
diff --git a/src/reducers/clipboard.js b/src/reducers/clipboard.js
new file mode 100644
index 00000000..bd5a2c3f
--- /dev/null
+++ b/src/reducers/clipboard.js
@@ -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
+};
diff --git a/src/reducers/scratch-paint-reducer.js b/src/reducers/scratch-paint-reducer.js
index ff0fcff3..7bbe8c6f 100644
--- a/src/reducers/scratch-paint-reducer.js
+++ b/src/reducers/scratch-paint-reducer.js
@@ -3,6 +3,7 @@ import modeReducer from './modes';
import brushModeReducer from './brush-mode';
import eraserModeReducer from './eraser-mode';
import colorReducer from './color';
+import clipboardReducer from './clipboard';
import hoverReducer from './hover';
import modalsReducer from './modals';
import selectedItemReducer from './selected-items';
@@ -11,8 +12,9 @@ import undoReducer from './undo';
export default combineReducers({
mode: modeReducer,
brushMode: brushModeReducer,
- eraserMode: eraserModeReducer,
color: colorReducer,
+ clipboard: clipboardReducer,
+ eraserMode: eraserModeReducer,
hoveredItemId: hoverReducer,
modals: modalsReducer,
selectedItems: selectedItemReducer,
diff --git a/test/unit/clipboard-reducer.test.js b/test/unit/clipboard-reducer.test.js
new file mode 100644
index 00000000..7428016a
--- /dev/null
+++ b/test/unit/clipboard-reducer.test.js
@@ -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);
+});