Skip to content

Commit e76182d

Browse files
authored
Merge pull request #45 from tbolis/feature/copy-paste
adding copy/paste functionality
2 parents a6c74c8 + 4eacf75 commit e76182d

File tree

3 files changed

+145
-71
lines changed

3 files changed

+145
-71
lines changed

examples/main.jsx

Lines changed: 89 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ import DeleteIcon from '@material-ui/icons/Delete';
3030
import SaveIcon from '@material-ui/icons/Save';
3131
import ClearIcon from '@material-ui/icons/Clear';
3232
import AddIcon from '@material-ui/icons/Add';
33+
import CopyIcon from '@material-ui/icons/FileCopy';
34+
import RemoveIcon from '@material-ui/icons/Remove';
3335
import DownloadIcon from '@material-ui/icons/CloudDownload';
3436
import ZoomInIcon from '@material-ui/icons/ZoomIn';
3537
import ZoomOutIcon from '@material-ui/icons/ZoomOut';
@@ -139,18 +141,21 @@ class SketchFieldDemo extends React.Component {
139141
originY: 'top',
140142
imageUrl: 'https://files.gamebanana.com/img/ico/sprays/4ea2f4dad8d6f.png',
141143
expandTools: false,
144+
expandControls: false,
142145
expandColors: false,
143146
expandBack: false,
144147
expandImages: false,
145148
expandControlled: false,
146149
text: 'a text, cool!',
150+
enableCopyPaste: false,
147151
};
148152
}
149153

150154
_selectTool = event => {
151155
this.setState({
152156
tool: event.target.value,
153-
enableRemoveSelected: event.target.value === Tools.Select
157+
enableRemoveSelected: event.target.value === Tools.Select,
158+
enableCopyPaste: event.target.value === Tools.Select
154159
});
155160
};
156161

@@ -381,7 +386,7 @@ class SketchFieldDemo extends React.Component {
381386
<Collapse in={this.state.expandTools}>
382387
<CardContent>
383388
<div className="row">
384-
<div className="col">
389+
<div className="col-lg-12">
385390
<TextField
386391
select={true}
387392
label="Canvas Tool"
@@ -396,14 +401,6 @@ class SketchFieldDemo extends React.Component {
396401
<MenuItem value={Tools.Pan} key="Pan">Pan</MenuItem>
397402
</TextField>
398403
</div>
399-
<div className="col">
400-
<IconButton
401-
color="primary"
402-
disabled={!this.state.enableRemoveSelected}
403-
onClick={this._removeSelected}>
404-
<DeleteIcon/>
405-
</IconButton>
406-
</div>
407404
</div>
408405
<br/>
409406
<br/>
@@ -427,51 +424,89 @@ class SketchFieldDemo extends React.Component {
427424
onClick={(e) => this._sketch.zoom(0.8)}>
428425
<ZoomOutIcon/>
429426
</IconButton>
430-
<br/>
431-
<div className="row">
432-
<div className="col-lg-7">
433-
<TextField
434-
label='Text'
435-
helperText='Add text to Sketch'
436-
onChange={(e) => this.setState({ text: e.target.value })}
437-
value={this.state.text}/>
438-
</div>
439-
<div className="col-lg-3">
440-
<IconButton
441-
color="primary"
442-
onClick={this._addText}>
443-
<AddIcon/>
444-
</IconButton>
445-
</div>
427+
</div>
428+
<div className="row">
429+
<div className="col-lg-7">
430+
<TextField
431+
label='Text'
432+
helperText='Add text to Sketch'
433+
onChange={(e) => this.setState({ text: e.target.value })}
434+
value={this.state.text}/>
435+
</div>
436+
<div className="col-lg-3">
437+
<IconButton
438+
color="primary"
439+
onClick={this._addText}>
440+
<AddIcon/>
441+
</IconButton>
442+
</div>
443+
</div>
444+
</CardContent>
445+
</Collapse>
446+
</Card>
447+
<Card style={styles.card}>
448+
<CardHeader
449+
title="Controls"
450+
subheader="Copy/Paste etc."
451+
action={
452+
<IconButton
453+
onClick={(e) => this.setState({ expandControls: !this.state.expandControls })}>
454+
<ExpandMore/>
455+
</IconButton>
456+
}/>
457+
<Collapse in={this.state.expandControls}>
458+
<CardContent>
459+
<div className="row">
460+
<div className="col-lg-12">
461+
<FormControlLabel
462+
control={
463+
<Switch
464+
value={this.state.controlledSize}
465+
onChange={(e) => this.setState({ controlledSize: !this.state.controlledSize })}
466+
/>
467+
}
468+
label="Control size"
469+
/>
470+
<br/>
471+
<Typography id="xSize">Change Canvas Width</Typography>
472+
<Slider
473+
step={1}
474+
min={10}
475+
max={1000}
476+
value={this.state.sketchWidth}
477+
onChange={(e, v) => this.setState({ sketchWidth: v })}/>
478+
<br/>
479+
<Typography id="ySize">Change Canvas Height</Typography>
480+
<Slider
481+
step={1}
482+
min={10}
483+
max={1000}
484+
value={this.state.sketchHeight}
485+
onChange={(e, v) => this.setState({ sketchHeight: v })}/>
486+
<br/>
487+
</div>
488+
</div>
489+
<label htmlFor="zoom">Selection Actions (Select an object first!)</label>
490+
<div className="row">
491+
<div className="col">
492+
<IconButton
493+
color="primary"
494+
disabled={!this.state.enableCopyPaste}
495+
onClick={(e) => {
496+
this._sketch.copy();
497+
this._sketch.paste();
498+
}}>
499+
<CopyIcon/>
500+
</IconButton>
501+
</div>
502+
<div className="col">
503+
<IconButton
504+
color="primary"
505+
disabled={!this.state.enableRemoveSelected}
506+
onClick={this._removeSelected}>
507+
<RemoveIcon/>
508+
</IconButton>
446509
</div>
447-
448-
<br/>
449-
<FormControlLabel
450-
control={
451-
<Switch
452-
value={this.state.controlledSize}
453-
onChange={(e) => this.setState({ controlledSize: !this.state.controlledSize })}
454-
/>
455-
}
456-
label="Control size"
457-
/>
458-
<br/>
459-
<Typography id="xSize">Change Canvas Width</Typography>
460-
<Slider
461-
step={1}
462-
min={10}
463-
max={1000}
464-
value={this.state.sketchWidth}
465-
onChange={(e, v) => this.setState({ sketchWidth: v })}/>
466-
<br/>
467-
<Typography id="ySize">Change Canvas Height</Typography>
468-
<Slider
469-
step={1}
470-
min={10}
471-
max={1000}
472-
value={this.state.sketchHeight}
473-
onChange={(e, v) => this.setState({ sketchHeight: v })}/>
474-
<br/>
475510
</div>
476511
</CardContent>
477512
</Collapse>

src/SketchField.jsx

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,7 @@ class SketchField extends PureComponent {
466466
if (activeObj) {
467467
let selected = [];
468468
if (activeObj.type === 'activeSelection') {
469-
activeObj.forEachObject((obj) => [].push(obj));
469+
activeObj.forEachObject(obj => selected.push(obj));
470470
} else {
471471
selected.push(activeObj)
472472
}
@@ -483,6 +483,36 @@ class SketchField extends PureComponent {
483483
}
484484
};
485485

486+
copy = () => {
487+
let canvas = this._fc;
488+
canvas.getActiveObject().clone(cloned => this._clipboard = cloned);
489+
};
490+
491+
paste = () => {
492+
// clone again, so you can do multiple copies.
493+
this._clipboard.clone(clonedObj => {
494+
let canvas = this._fc;
495+
canvas.discardActiveObject();
496+
clonedObj.set({
497+
left: clonedObj.left + 10,
498+
top: clonedObj.top + 10,
499+
evented: true,
500+
});
501+
if (clonedObj.type === 'activeSelection') {
502+
// active selection needs a reference to the canvas.
503+
clonedObj.canvas = canvas;
504+
clonedObj.forEachObject(obj => canvas.add(obj));
505+
clonedObj.setCoords();
506+
} else {
507+
canvas.add(clonedObj);
508+
}
509+
this._clipboard.top += 10;
510+
this._clipboard.left += 10;
511+
canvas.setActiveObject(clonedObj);
512+
canvas.requestRenderAll();
513+
});
514+
};
515+
486516
/**
487517
* Sets the background from the dataUrl given
488518
*
@@ -573,22 +603,11 @@ class SketchField extends PureComponent {
573603
canvas.on('object:moving', this._onObjectMoving);
574604
canvas.on('object:scaling', this._onObjectScaling);
575605
canvas.on('object:rotating', this._onObjectRotating);
576-
// Events binding
577-
canvas.on('object:added', this._onObjectAdded);
578-
canvas.on('object:modified', this._onObjectModified);
579-
canvas.on('object:removed', this._onObjectRemoved);
580-
canvas.on('mouse:down', this._onMouseDown);
581-
canvas.on('mouse:move', this._onMouseMove);
582-
canvas.on('mouse:up', this._onMouseUp);
583-
canvas.on('mouse:out', this._onMouseOut);
584-
canvas.on('object:moving', this._onObjectMoving);
585-
canvas.on('object:scaling', this._onObjectScaling);
586-
canvas.on('object:rotating', this._onObjectRotating);
587-
// IText Events fired on Adding Text
588-
// canvas.on("text:event:changed", console.log)
589-
// canvas.on("text:selection:changed", console.log)
590-
// canvas.on("text:editing:entered", console.log)
591-
// canvas.on("text:editing:exited", console.log)
606+
// IText Events fired on Adding Text
607+
// canvas.on("text:event:changed", console.log)
608+
// canvas.on("text:selection:changed", console.log)
609+
// canvas.on("text:editing:entered", console.log)
610+
// canvas.on("text:editing:exited", console.log)
592611

593612
this.disableTouchScroll();
594613

test/SketchFieldTest.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,4 +192,24 @@ describe('SketchField', () => {
192192
expect(canvas.getObjects().map(o => o.id)).toEqual(['rect2', 'rect1']);
193193
});
194194

195+
it('Copy/Paste selected object', () => {
196+
const sketch = mount(<SketchField tool="rectangle"/>).instance();
197+
const canvas = sketch._fc;
198+
expect(canvas).toBeDefined();
199+
200+
const startPt = { x: 10, y: 10 };
201+
const endPt = { x: 40, y: 50 };
202+
203+
canvas.renderOnAddRemove = false;
204+
objectFromDrag(canvas, startPt, endPt, 'rect1');
205+
objectFromDrag(canvas, startPt, endPt, 'rect2');
206+
canvas.setActiveObject(canvas.getObjects()[0]);
207+
208+
expect(canvas.getObjects().length).toEqual(2);
209+
210+
sketch.copy();
211+
sketch.paste();
212+
213+
expect(canvas.getObjects().length).toEqual(3);
214+
});
195215
});

0 commit comments

Comments
 (0)