diff --git a/package.json b/package.json
index 770d0dc8..913ef3c8 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "react-design-editor",
- "version": "0.0.56",
+ "version": "0.0.57",
"description": "Design Editor Tools with React.js + ant.design + fabric.js",
"main": "dist/react-design-editor.min.js",
"typings": "lib/index.d.ts",
diff --git a/public/images/sample/chiller.svg b/public/images/sample/chiller.svg
new file mode 100644
index 00000000..8494eb77
--- /dev/null
+++ b/public/images/sample/chiller.svg
@@ -0,0 +1,430 @@
+
\ No newline at end of file
diff --git a/src/canvas/handlers/Handler.ts b/src/canvas/handlers/Handler.ts
index 70675c1b..947c19ba 100644
--- a/src/canvas/handlers/Handler.ts
+++ b/src/canvas/handlers/Handler.ts
@@ -30,6 +30,7 @@ import { defaults } from '../constants';
import { LinkObject } from '../objects/Link';
import { NodeObject } from '../objects/Node';
import { PortObject } from '../objects/Port';
+import { SvgObject } from '../objects/Svg';
import {
CanvasOption,
FabricCanvas,
@@ -616,17 +617,36 @@ class Handler implements HandlerOptions {
* Set the image
* @param {FabricImage} obj
* @param {(File | string)} [source]
+ * @param {boolean} [keepSize]
* @returns
*/
- public setImage = (obj: FabricImage, source?: File | string): Promise => {
+ public setImage = (
+ obj: FabricImage,
+ source?: File | string,
+ keepSize?: boolean,
+ options?: fabric.IImageOptions,
+ ): Promise => {
+ const { height, scaleY } = obj;
+ const renderCallbaack = (imgObj: FabricImage, src: string) => {
+ if (keepSize) {
+ const scale = (height * scaleY) / imgObj.height;
+ imgObj.set({ scaleY: scale, scaleX: scale, src });
+ }
+ this.canvas.requestRenderAll();
+ };
return new Promise(resolve => {
if (!source) {
obj.set('file', null);
obj.set('src', null);
resolve(
- obj.setSrc('./images/sample/transparentBg.png', () => this.canvas.renderAll(), {
- dirty: true,
- }) as FabricImage,
+ obj.setSrc(
+ './images/sample/transparentBg.png',
+ (imgObj: FabricImage) => renderCallbaack(imgObj, null),
+ {
+ dirty: true,
+ ...options,
+ },
+ ) as FabricImage,
);
}
if (source instanceof File) {
@@ -635,9 +655,14 @@ class Handler implements HandlerOptions {
obj.set('file', source);
obj.set('src', null);
resolve(
- obj.setSrc(reader.result as string, () => this.canvas.renderAll(), {
- dirty: true,
- }) as FabricImage,
+ obj.setSrc(
+ reader.result as string,
+ (imgObj: FabricImage) => renderCallbaack(imgObj, reader.result as string),
+ {
+ dirty: true,
+ ...options,
+ },
+ ) as FabricImage,
);
};
reader.readAsDataURL(source);
@@ -645,9 +670,10 @@ class Handler implements HandlerOptions {
obj.set('file', null);
obj.set('src', source);
resolve(
- obj.setSrc(source, () => this.canvas.renderAll(), {
+ obj.setSrc(source, (imgObj: FabricImage) => renderCallbaack(imgObj, source), {
dirty: true,
crossOrigin: 'anonymous',
+ ...options,
}) as FabricImage,
);
}
@@ -660,9 +686,38 @@ class Handler implements HandlerOptions {
* @param {*} source
* @returns
*/
- public setImageById = (id: string, source: any) => {
+ public setImageById = (id: string, source: any, keepSize?: boolean) => {
const findObject = this.findById(id) as FabricImage;
- return Promise.resolve(this.setImage(findObject, source));
+ return Promise.resolve(this.setImage(findObject, source, keepSize));
+ };
+
+ /**
+ * Set Svg
+ *
+ * @param {SvgObject} obj
+ * @param {(File | string)} [source]
+ * @param {boolean} [setSvg]
+ * @param {boolean} [keepSize]
+ */
+ public setSvg = (
+ obj: SvgObject,
+ source?: File | string,
+ isPath?: boolean,
+ keepSize?: boolean,
+ ): Promise => {
+ return new Promise(resolve => {
+ if (!source) {
+ resolve(obj.loadSvg({ src: './images/sample/chiller.svg', loadType: 'file', keepSize }));
+ }
+ if (source instanceof File) {
+ const reader = new FileReader();
+ reader.readAsDataURL(source);
+ reader.onload = () =>
+ resolve(obj.loadSvg({ src: reader.result as string, loadType: 'file', keepSize }));
+ } else {
+ resolve(obj.loadSvg({ src: source, loadType: isPath ? 'svg' : 'file', keepSize }));
+ }
+ });
};
/**
diff --git a/src/canvas/objects/Svg.ts b/src/canvas/objects/Svg.ts
index 6293d90b..642e11ef 100644
--- a/src/canvas/objects/Svg.ts
+++ b/src/canvas/objects/Svg.ts
@@ -3,13 +3,14 @@ import { FabricGroup, FabricObject, FabricObjectOption, toObject } from '../util
export type SvgObject = (FabricGroup | FabricObject) & {
loadSvg(option: SvgOption): Promise;
- setFill(value: string): SvgObject;
- setStroke(value: string): SvgObject;
+ setFill(value: string, filter?: (obj: FabricObject) => boolean): SvgObject;
+ setStroke(value: string, filter?: (obj: FabricObject) => boolean): SvgObject;
};
export interface SvgOption extends FabricObjectOption {
- svg?: string;
+ src?: string;
loadType?: 'file' | 'svg';
+ keepSize?: boolean;
}
const Svg = fabric.util.createClass(fabric.Group, {
@@ -18,9 +19,16 @@ const Svg = fabric.util.createClass(fabric.Group, {
this.callSuper('initialize', [], option);
this.loadSvg(option);
},
- addSvgElements(objects: FabricObject[], options: any, path: string) {
- const createdObj = fabric.util.groupSVGElements(objects, options, path) as SvgObject;
- this.set(options);
+ addSvgElements(objects: FabricObject[], options: SvgOption) {
+ const createdObj = fabric.util.groupSVGElements(objects, options) as SvgObject;
+ const { height, scaleY } = this;
+ const scale = height ? (height * scaleY) / createdObj.height : createdObj.scaleY;
+ this.set({ ...options, scaleX: scale, scaleY: scale });
+ if (this._objects?.length) {
+ (this as FabricGroup).getObjects().forEach(obj => {
+ this.remove(obj);
+ });
+ }
if (createdObj.getObjects) {
(createdObj as FabricGroup).getObjects().forEach(obj => {
this.add(obj);
@@ -51,10 +59,6 @@ const Svg = fabric.util.createClass(fabric.Group, {
}
this.add(createdObj);
}
- this.set({
- fill: options.fill,
- stroke: options.stroke,
- });
this.setCoords();
if (this.canvas) {
this.canvas.requestRenderAll();
@@ -62,25 +66,31 @@ const Svg = fabric.util.createClass(fabric.Group, {
return this;
},
loadSvg(option: SvgOption) {
- const { svg, loadType, fill, stroke } = option;
+ const { src, loadType, fill, stroke } = option;
return new Promise(resolve => {
if (loadType === 'svg') {
- fabric.loadSVGFromString(svg, (objects, options) => {
- resolve(this.addSvgElements(objects, { ...options, fill, stroke }, svg));
+ fabric.loadSVGFromString(src, (objects, options) => {
+ resolve(this.addSvgElements(objects, { ...options, fill, stroke }));
});
} else {
- fabric.loadSVGFromURL(svg, (objects, options) => {
- resolve(this.addSvgElements(objects, { ...options, fill, stroke }, svg));
+ fabric.loadSVGFromURL(src, (objects, options) => {
+ resolve(this.addSvgElements(objects, { ...options, fill, stroke }));
});
}
});
},
- setFill(value: any) {
- this.getObjects().forEach((obj: FabricObject) => obj.set('fill', value));
+ setFill(value: any, filter: (obj: FabricObject) => boolean = () => true) {
+ this.getObjects()
+ .filter(filter)
+ .forEach((obj: FabricObject) => obj.set('fill', value));
+ this.canvas.requestRenderAll();
return this;
},
- setStroke(value: any) {
- this.getObjects().forEach((obj: FabricObject) => obj.set('stroke', value));
+ setStroke(value: any, filter: (obj: FabricObject) => boolean = () => true) {
+ this.getObjects()
+ .filter(filter)
+ .forEach((obj: FabricObject) => obj.set('stroke', value));
+ this.canvas.requestRenderAll();
return this;
},
toObject(propertiesToInclude: string[]) {
diff --git a/src/editors/imagemap/Descriptors.json b/src/editors/imagemap/Descriptors.json
index b07f0ef0..c5dfdb6f 100644
--- a/src/editors/imagemap/Descriptors.json
+++ b/src/editors/imagemap/Descriptors.json
@@ -259,7 +259,8 @@
"type": "svg",
"superType": "svg",
"name": "New SVG",
- "loadType": "svg"
+ "loadType": "file",
+ "src": "./images/sample/chiller.svg"
}
}
]
diff --git a/src/editors/imagemap/ImageMapEditor.js b/src/editors/imagemap/ImageMapEditor.js
index b79615cc..f49bc7a9 100644
--- a/src/editors/imagemap/ImageMapEditor.js
+++ b/src/editors/imagemap/ImageMapEditor.js
@@ -192,11 +192,13 @@ class ImageMapEditor extends Component {
});
return;
}
- if (changedKey === 'file' || changedKey === 'src' || changedKey === 'code') {
+ if (changedKey === 'file' || changedKey === 'src' || changedKey === 'code' || changedKey === 'svg') {
if (selectedItem.type === 'image') {
- this.canvasRef.handler.setImageById(selectedItem.id, changedValue);
+ this.canvasRef.handler.setImageById(selectedItem.id, changedValue, true);
} else if (selectedItem.superType === 'element') {
this.canvasRef.handler.elementHandler.setById(selectedItem.id, changedValue);
+ } else if (selectedItem.superType === 'svg') {
+ this.canvasRef.handler.setSvg(selectedItem, changedValue);
}
return;
}
@@ -331,7 +333,11 @@ class ImageMapEditor extends Component {
}
return;
}
- this.canvasRef.handler.set(changedKey, changedValue);
+ if (selectedItem.type === 'svg' && changedKey === 'fill') {
+ selectedItem.setFill(changedValue);
+ } else {
+ this.canvasRef.handler.set(changedKey, changedValue);
+ }
},
onChangeWokarea: (changedKey, changedValue, allValues) => {
if (changedKey === 'layout') {
diff --git a/src/editors/imagemap/ImageMapItems.js b/src/editors/imagemap/ImageMapItems.js
index 1133fe60..988d2c9b 100644
--- a/src/editors/imagemap/ImageMapItems.js
+++ b/src/editors/imagemap/ImageMapItems.js
@@ -1,15 +1,14 @@
-import React, { Component } from 'react';
-import PropTypes from 'prop-types';
-import { Collapse, notification, Input, message } from 'antd';
+import { Collapse, Input, message, notification } from 'antd';
import classnames from 'classnames';
import i18n from 'i18next';
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import { uuid } from 'uuidv4';
+import CommonButton from '../../components/common/CommonButton';
+import Scrollbar from '../../components/common/Scrollbar';
import { Flex } from '../../components/flex';
import Icon from '../../components/icon/Icon';
-import Scrollbar from '../../components/common/Scrollbar';
-import CommonButton from '../../components/common/CommonButton';
-import { SVGModal } from '../../components/common';
-import { uuid } from 'uuidv4';
notification.config({
top: 80,
@@ -104,17 +103,8 @@ class ImageMapItems extends Component {
}
const id = uuid();
const option = Object.assign({}, item.option, { id });
- if (item.option.superType === 'svg' && item.type === 'default') {
- this.handlers.onSVGModalVisible(item.option);
- return;
- }
canvasRef.handler.add(option, centered);
},
- onAddSVG: (option, centered) => {
- const { canvasRef } = this.props;
- canvasRef.handler.add({ ...option, type: 'svg', superType: 'svg', id: uuid(), name: 'New SVG' }, centered);
- this.handlers.onSVGModalVisible();
- },
onDrawingItem: item => {
const { canvasRef } = this.props;
if (canvasRef.handler.interactionMode === 'polygon') {
@@ -318,12 +308,6 @@ class ImageMapItems extends Component {
-
);
}
diff --git a/src/editors/imagemap/properties/PropertyDefinition.js b/src/editors/imagemap/properties/PropertyDefinition.js
index ee3ff065..9c9c3d26 100644
--- a/src/editors/imagemap/properties/PropertyDefinition.js
+++ b/src/editors/imagemap/properties/PropertyDefinition.js
@@ -1,20 +1,21 @@
-import MarkerProperty from './MarkerProperty';
+import AnimationProperty from './AnimationProperty';
+import ChartProperty from './ChartProperty';
+import ElementProperty from './ElementProperty';
import GeneralProperty from './GeneralProperty';
-import StyleProperty from './StyleProperty';
-import TooltipProperty from './TooltipProperty';
+import IframeProperty from './IframeProperty';
+import ImageFilterProperty from './ImageFilterProperty';
import ImageProperty from './ImageProperty';
-import TextProperty from './TextProperty';
-import MapProperty from './MapProperty';
import LinkProperty from './LinkProperty';
-import VideoProperty from './VideoProperty';
-import ElementProperty from './ElementProperty';
-import IframeProperty from './IframeProperty';
-import AnimationProperty from './AnimationProperty';
+import MapProperty from './MapProperty';
+import MarkerProperty from './MarkerProperty';
import ShadowProperty from './ShadowProperty';
-import UserProperty from './UserProperty';
+import StyleProperty from './StyleProperty';
+import SvgProperty from './SvgProperty';
+import TextProperty from './TextProperty';
+import TooltipProperty from './TooltipProperty';
import TriggerProperty from './TriggerProperty';
-import ImageFilterProperty from './ImageFilterProperty';
-import ChartProperty from './ChartProperty';
+import UserProperty from './UserProperty';
+import VideoProperty from './VideoProperty';
export default {
map: {
@@ -394,6 +395,10 @@ export default {
title: 'General',
component: GeneralProperty,
},
+ svg: {
+ title: 'SVG',
+ component: SvgProperty,
+ },
link: {
title: 'Link',
component: LinkProperty,
diff --git a/src/editors/imagemap/properties/SvgProperty.js b/src/editors/imagemap/properties/SvgProperty.js
new file mode 100644
index 00000000..9ca850a5
--- /dev/null
+++ b/src/editors/imagemap/properties/SvgProperty.js
@@ -0,0 +1,40 @@
+import { Form, Radio } from 'antd';
+import i18n from 'i18next';
+import React from 'react';
+
+import FileUpload from '../../../components/common/FileUpload';
+
+export default {
+ render(canvasRef, form, data) {
+ if (!data) {
+ return null;
+ }
+ const loadType = data.loadType || 'file';
+ return (
+
+
+ {form.getFieldDecorator('loadType', {
+ initialValue: loadType,
+ })(
+
+ {i18n.t('common.file')}
+ {i18n.t('common.svg')}
+ ,
+ )}
+
+
+ {form.getFieldDecorator('src', {
+ rules: [
+ {
+ required: true,
+ message: i18n.t('validation.enter-property', {
+ arg: loadType === 'svg' ? i18n.t('common.svg') : i18n.t('common.file'),
+ }),
+ },
+ ],
+ })(loadType === 'svg' ? : )}
+
+
+ );
+ },
+};