Skip to content

Commit f8f5191

Browse files
committed
[#164] Support SVG Object
1 parent 1d869b2 commit f8f5191

File tree

10 files changed

+106
-77
lines changed

10 files changed

+106
-77
lines changed

src/components/canvas/CanvasObject.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
} from './objects';
1717
import { FabricObject } from './utils';
1818
import { Code } from './objects/Element';
19+
import Svg, { SvgOption } from './objects/Svg';
1920

2021
export interface ObjectSchema {
2122
create: (...option: any) => fabric.Object;
@@ -116,6 +117,9 @@ const CanvasObject: CanvasObjectSchema = {
116117
create: (fromNode, fromPort, toNode, toPort, option) =>
117118
new OrthogonalLink(fromNode, fromPort, toNode, toPort, option),
118119
},
120+
svg: {
121+
create: (option: SvgOption) => new Svg(option),
122+
},
119123
};
120124

121125
export default CanvasObject;

src/components/canvas/global.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ declare module 'fabric/fabric-impl' {
1818
class CurvedLink {}
1919
class OrthogonalLink {}
2020
class Cube {}
21+
// SVG
22+
class Svg {}
2123
}
2224

2325
declare global {

src/components/canvas/handlers/Handler.ts

Lines changed: 24 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -705,9 +705,6 @@ class Handler implements HandlerOptions {
705705
if (obj.superType === 'link') {
706706
return this.linkHandler.create(newOption, loaded);
707707
}
708-
if (obj.type === 'svg') {
709-
return this.addSVG(newOption, centered, loaded);
710-
}
711708
let createdObj;
712709
// Create canvas object
713710
if (obj.type === 'image') {
@@ -800,51 +797,6 @@ class Handler implements HandlerOptions {
800797
return createdObj;
801798
};
802799

803-
/**
804-
* Add svg object
805-
* @param {*} obj
806-
* @param {boolean} [centered=true]
807-
* @param {boolean} [loaded=false]
808-
* @returns
809-
*/
810-
public addSVG = (obj: any, centered = true, loaded = false) => {
811-
const { objectOption } = this;
812-
return new Promise((resolve: any) => {
813-
const getSVGElements = (object: any, objects: any, options: any) => {
814-
const createdObj = fabric.util.groupSVGElements(objects, options) as FabricObject;
815-
createdObj.set({ ...objectOption, ...object });
816-
this.canvas.add(createdObj);
817-
this.objects = this.getObjects();
818-
const { onAdd, editable } = this;
819-
if (!editable && !(obj.superType === 'element')) {
820-
createdObj.on('mousedown', this.eventHandler.object.mousedown);
821-
}
822-
if (createdObj.dblclick) {
823-
createdObj.on('mousedblclick', this.eventHandler.object.mousedblclick);
824-
}
825-
if (editable && !loaded) {
826-
this.centerObject(createdObj, centered);
827-
}
828-
if (!editable && createdObj.animation && createdObj.animation.autoplay) {
829-
this.animationHandler.play(createdObj.id);
830-
}
831-
if (onAdd && !loaded && editable) {
832-
onAdd(createdObj);
833-
}
834-
return createdObj;
835-
};
836-
if (obj.loadType === 'svg') {
837-
fabric.loadSVGFromString(obj.svg, (objects, options) => {
838-
resolve(getSVGElements(obj, objects, options));
839-
});
840-
} else {
841-
fabric.loadSVGFromURL(obj.svg, (objects, options) => {
842-
resolve(getSVGElements(obj, objects, options));
843-
});
844-
}
845-
});
846-
};
847-
848800
/**
849801
* Remove object
850802
* @param {FabricObject} target
@@ -1148,7 +1100,7 @@ class Handler implements HandlerOptions {
11481100
return true;
11491101
}
11501102
}
1151-
activeObject.clone((cloned: any) => {
1103+
activeObject.clone((cloned: FabricObject) => {
11521104
if (this.keyEvent.clipboard) {
11531105
if (cloned.superType === 'node') {
11541106
const node = {
@@ -1166,12 +1118,12 @@ class Handler implements HandlerOptions {
11661118
};
11671119
const objects = [node];
11681120
this.copyToClipboard(JSON.stringify(objects, null, '\t'));
1169-
return;
1121+
} else {
1122+
this.copyToClipboard(JSON.stringify(cloned.toObject(propertiesToInclude), null, '\t'));
11701123
}
1171-
this.copyToClipboard(JSON.stringify(cloned.toObject(propertiesToInclude), null, '\t'));
1172-
return;
1124+
} else {
1125+
this.clipboard = cloned;
11731126
}
1174-
this.clipboard = cloned;
11751127
}, propertiesToInclude);
11761128
}
11771129
return true;
@@ -1269,11 +1221,11 @@ class Handler implements HandlerOptions {
12691221
} else {
12701222
this.clipboard = activeSelection;
12711223
}
1272-
this.canvas.setActiveObject(activeSelection);
1273-
this.canvas.renderAll();
12741224
if (!this.transactionHandler.active) {
12751225
this.transactionHandler.save('paste');
12761226
}
1227+
this.canvas.setActiveObject(activeSelection);
1228+
this.canvas.renderAll();
12771229
return true;
12781230
}
12791231
}
@@ -1282,7 +1234,7 @@ class Handler implements HandlerOptions {
12821234
clonedObj.set({
12831235
left: clonedObj.left + padding,
12841236
top: clonedObj.top + padding,
1285-
id: isCut ? clonedObj.id : v4(),
1237+
id: isCut ? clipboard.id : v4(),
12861238
evented: true,
12871239
});
12881240
if (clonedObj.type === 'activeSelection') {
@@ -1293,12 +1245,7 @@ class Handler implements HandlerOptions {
12931245
if (obj.dblclick) {
12941246
obj.on('mousedblclick', this.eventHandler.object.mousedblclick);
12951247
}
1296-
this.objects = this.getObjects();
12971248
});
1298-
if (onAdd) {
1299-
onAdd(clonedObj);
1300-
}
1301-
clonedObj.setCoords();
13021249
} else {
13031250
if (clonedObj.superType === 'node') {
13041251
clonedObj = clonedObj.duplicate();
@@ -1307,10 +1254,6 @@ class Handler implements HandlerOptions {
13071254
if (clonedObj.dblclick) {
13081255
clonedObj.on('mousedblclick', this.eventHandler.object.mousedblclick);
13091256
}
1310-
this.objects = this.getObjects();
1311-
if (onAdd) {
1312-
onAdd(clonedObj);
1313-
}
13141257
}
13151258
const newClipboard = clipboard.set({
13161259
top: clonedObj.top,
@@ -1321,12 +1264,21 @@ class Handler implements HandlerOptions {
13211264
} else {
13221265
this.clipboard = newClipboard;
13231266
}
1324-
this.canvas.setActiveObject(clonedObj);
1325-
this.portHandler.create(clonedObj);
1326-
this.canvas.requestRenderAll();
1267+
if (clonedObj.superType === 'node') {
1268+
this.portHandler.create(clonedObj);
1269+
}
13271270
if (!this.transactionHandler.active) {
13281271
this.transactionHandler.save('paste');
13291272
}
1273+
// TODO...
1274+
// After toGroup svg elements not rendered.
1275+
this.objects = this.getObjects();
1276+
if (onAdd) {
1277+
onAdd(clonedObj);
1278+
}
1279+
clonedObj.setCoords();
1280+
this.canvas.setActiveObject(clonedObj);
1281+
this.canvas.requestRenderAll();
13301282
}, propertiesToInclude);
13311283
return true;
13321284
};
@@ -1594,6 +1546,7 @@ class Handler implements HandlerOptions {
15941546
this.add(obj, false, true);
15951547
this.canvas.renderAll();
15961548
});
1549+
this.objects = this.getObjects();
15971550
if (callback) {
15981551
callback(this.canvas);
15991552
}
@@ -1612,12 +1565,12 @@ class Handler implements HandlerOptions {
16121565
public toGroup = (target?: FabricObject) => {
16131566
const activeObject = target || (this.canvas.getActiveObject() as fabric.ActiveSelection);
16141567
if (!activeObject) {
1615-
return;
1568+
return null;
16161569
}
16171570
if (activeObject.type !== 'activeSelection') {
1618-
return;
1571+
return null;
16191572
}
1620-
const group = activeObject.toGroup() as any;
1573+
const group = activeObject.toGroup() as FabricObject<fabric.Group>;
16211574
group.set({
16221575
id: v4(),
16231576
name: 'New group',

src/components/canvas/objects/Node.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,6 @@ const Node = fabric.util.createClass(fabric.Group, {
162162
cloneable: this.get('cloneable'),
163163
fromPort: this.get('fromPort'),
164164
toPort: this.get('toPort'),
165-
errorFlag: this.get('errorFlag'),
166165
});
167166
},
168167
defaultPortOption() {
@@ -258,6 +257,9 @@ const Node = fabric.util.createClass(fabric.Group, {
258257
return this.singlePort(portOption);
259258
},
260259
setErrors(errors: any) {
260+
this.set({
261+
errors,
262+
});
261263
if (errors) {
262264
this.errorFlag.set({
263265
visible: true,
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { fabric } from 'fabric';
2+
import { FabricObject, toObject, FabricObjectOption, FabricGroup } from '../utils';
3+
4+
export type SvgObject = FabricGroup | FabricObject;
5+
6+
export interface SvgOption extends FabricObjectOption {
7+
svg?: string;
8+
loadType?: 'file' | 'svg';
9+
}
10+
11+
const Svg = fabric.util.createClass(fabric.Group, {
12+
type: 'svg',
13+
initialize(option: SvgOption = {}) {
14+
const { svg, loadType } = option;
15+
this.callSuper('initialize', [], option);
16+
this.loadSvg(svg, loadType);
17+
},
18+
addSvgElements(objects: FabricObject[], options: any, path: string) {
19+
const createdObj = fabric.util.groupSVGElements(objects, options, path) as SvgObject;
20+
this.set(options);
21+
if (createdObj.getObjects) {
22+
(<FabricGroup>createdObj).getObjects().forEach(obj => this.add(obj));
23+
} else {
24+
createdObj.set({
25+
originX: 'center',
26+
originY: 'center',
27+
});
28+
this.add(createdObj);
29+
}
30+
this.setCoords();
31+
if (this.canvas) {
32+
this.canvas.requestRenderAll();
33+
}
34+
},
35+
loadSvg(svg: string, loadType: 'file' | 'svg') {
36+
return new Promise<SvgObject>(resolve => {
37+
if (loadType === 'svg') {
38+
fabric.loadSVGFromString(svg, (objects, options) => {
39+
resolve(this.addSvgElements(objects, options, svg));
40+
});
41+
} else {
42+
fabric.loadSVGFromURL(svg, (objects, options) => {
43+
resolve(this.addSvgElements(objects, options, svg));
44+
});
45+
}
46+
});
47+
},
48+
toObject(propertiesToInclude: string[]) {
49+
return toObject(this, propertiesToInclude, {
50+
svg: this.get('svg'),
51+
loadType: this.get('loadType'),
52+
});
53+
},
54+
_render(ctx: CanvasRenderingContext2D) {
55+
this.callSuper('_render', ctx);
56+
},
57+
});
58+
59+
Svg.fromObject = (option: SvgOption, callback: (obj: SvgObject) => any) => {
60+
return callback(new Svg(option));
61+
};
62+
63+
// @ts-ignore
64+
window.fabric.Svg = Svg;
65+
66+
export default Svg;

src/components/canvas/objects/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ export { default as Port } from './Port';
1212
export { default as Line } from './Line';
1313
export { default as CirclePort } from './CirclePort';
1414
export { default as Cube } from './Cube';
15+
export { default as Svg } from './Svg';

src/components/common/SVGModal.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class SVGModal extends Component {
1414
};
1515

1616
state = {
17-
loadType: 'svg',
17+
loadType: 'file',
1818
visible: false,
1919
};
2020

@@ -79,8 +79,8 @@ class SVGModal extends Component {
7979
initialValue: loadType,
8080
})(
8181
<Radio.Group onChange={this.handleChangeSvgType}>
82-
<Radio.Button value="svg">{i18n.t('common.svg')}</Radio.Button>
8382
<Radio.Button value="file">{i18n.t('common.file')}</Radio.Button>
83+
<Radio.Button value="svg">{i18n.t('common.svg')}</Radio.Button>
8484
</Radio.Group>,
8585
)}
8686
</Form.Item>

src/components/imagemap/Descriptors.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@
257257
},
258258
"option": {
259259
"type": "svg",
260+
"superType": "svg",
260261
"name": "New SVG",
261262
"loadType": "svg"
262263
}

src/components/imagemap/ImageMapItems.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,15 +104,15 @@ class ImageMapItems extends Component {
104104
}
105105
const id = v4();
106106
const option = Object.assign({}, item.option, { id });
107-
if (item.option.type === 'svg' && item.type === 'default') {
107+
if (item.option.superType === 'svg' && item.type === 'default') {
108108
this.handlers.onSVGModalVisible(item.option);
109109
return;
110110
}
111111
canvasRef.handler.add(option, centered);
112112
},
113113
onAddSVG: (option, centered) => {
114114
const { canvasRef } = this.props;
115-
canvasRef.handler.add({ ...option, type: 'svg', id: v4(), name: 'New SVG' }, centered);
115+
canvasRef.handler.add({ ...option, type: 'svg', superType: 'svg', id: v4(), name: 'New SVG' }, centered);
116116
this.handlers.onSVGModalVisible();
117117
},
118118
onDrawingItem: item => {

src/components/workflow/WorkflowEditor.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ class WorkflowEditor extends Component {
175175
try {
176176
this.canvasRef.handler.exportJSON().forEach(obj => {
177177
if (obj.superType === 'node') {
178-
if (obj.errorFlag.visible) {
178+
if (obj.errors) {
179179
throw new NodeConfigurationError(
180180
i18n.t('workflow.validate-fields-error'),
181181
obj.id,

0 commit comments

Comments
 (0)