react-design-editor
Version:
Design Editor Tools with React.js + ant.design + fabric.js
1,232 lines • 67.4 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const fabric_1 = require("fabric");
const lodash_1 = require("lodash");
const uuidv4_1 = require("uuidv4");
const warning_1 = __importDefault(require("warning"));
const _1 = require(".");
const CanvasObject_1 = __importDefault(require("../CanvasObject"));
const constants_1 = require("../constants");
/**
* Main handler for Canvas
* @class Handler
* @implements {HandlerOptions}
*/
class Handler {
constructor(options) {
this.zoomStep = 0.05;
this.propertiesToInclude = constants_1.defaults.propertiesToInclude;
this.workareaOption = constants_1.defaults.workareaOption;
this.canvasOption = constants_1.defaults.canvasOption;
this.gridOption = constants_1.defaults.gridOption;
this.objectOption = constants_1.defaults.objectOption;
this.guidelineOption = constants_1.defaults.guidelineOption;
this.keyEvent = constants_1.defaults.keyEvent;
this.activeSelectionOption = constants_1.defaults.activeSelectionOption;
this.fabricObjects = CanvasObject_1.default;
this.handlers = {};
this.objectMap = {};
this.zoom = 1;
this.isCut = false;
this.isRequsetAnimFrame = false;
/**
* Init class fields
* @param {HandlerOptions} options
*/
this.initOption = (options) => {
this.id = options.id;
this.canvas = options.canvas;
this.container = options.container;
this.editable = options.editable;
this.interactionMode = options.interactionMode;
this.minZoom = options.minZoom;
this.maxZoom = options.maxZoom;
this.zoomStep = options.zoomStep || 0.05;
this.zoomEnabled = options.zoomEnabled;
this.width = options.width;
this.height = options.height;
this.objects = [];
this.setPropertiesToInclude(options.propertiesToInclude);
this.setWorkareaOption(options.workareaOption);
this.setCanvasOption(options.canvasOption);
this.setGridOption(options.gridOption);
this.setObjectOption(options.objectOption);
this.setFabricObjects(options.fabricObjects);
this.setGuidelineOption(options.guidelineOption);
this.setActiveSelectionOption(options.activeSelectionOption);
this.setKeyEvent(options.keyEvent);
};
/**
* Initialize callback
* @param {HandlerOptions} options
*/
this.initCallback = (options) => {
this.onAdd = options.onAdd;
this.onTooltip = options.onTooltip;
this.onZoom = options.onZoom;
this.onContext = options.onContext;
this.onClick = options.onClick;
this.onModified = options.onModified;
this.onDblClick = options.onDblClick;
this.onSelect = options.onSelect;
this.onRemove = options.onRemove;
this.onTransaction = options.onTransaction;
this.onInteraction = options.onInteraction;
this.onLoad = options.onLoad;
};
/**
* Initialize handlers
*
*/
this.initHandler = () => {
this.workareaHandler = new _1.WorkareaHandler(this);
this.imageHandler = new _1.ImageHandler(this);
this.chartHandler = new _1.ChartHandler(this);
this.elementHandler = new _1.ElementHandler(this);
this.cropHandler = new _1.CropHandler(this);
this.animationHandler = new _1.AnimationHandler(this);
this.contextmenuHandler = new _1.ContextmenuHandler(this);
this.tooltipHandler = new _1.TooltipHandler(this);
this.zoomHandler = new _1.ZoomHandler(this, this.zoomStep);
this.interactionHandler = new _1.InteractionHandler(this);
this.transactionHandler = new _1.TransactionHandler(this);
this.gridHandler = new _1.GridHandler(this);
this.portHandler = new _1.PortHandler(this);
this.linkHandler = new _1.LinkHandler(this);
this.nodeHandler = new _1.NodeHandler(this);
this.alignmentHandler = new _1.AlignmentHandler(this);
this.guidelineHandler = new _1.GuidelineHandler(this);
this.eventHandler = new _1.EventHandler(this);
this.drawingHandler = new _1.DrawingHandler(this);
this.shortcutHandler = new _1.ShortcutHandler(this);
};
/**
* Get primary object
* @returns {FabricObject[]}
*/
this.getObjects = () => {
const objects = this.canvas.getObjects().filter((obj) => {
if (obj.id === 'workarea') {
return false;
}
else if (obj.id === 'grid') {
return false;
}
else if (obj.superType === 'port') {
return false;
}
else if (!obj.id) {
return false;
}
return true;
});
if (objects.length) {
objects.forEach(obj => (this.objectMap[obj.id] = obj));
}
else {
this.objectMap = {};
}
return objects;
};
/**
* Set key pair
* @param {keyof FabricObject} key
* @param {*} value
* @returns
*/
this.set = (key, value) => {
const activeObject = this.canvas.getActiveObject();
if (!activeObject) {
return;
}
if (activeObject.type === 'svg' && (key === 'fill' || key === 'stroke')) {
activeObject._objects.forEach(obj => obj.set(key, value));
}
activeObject.set(key, value);
activeObject.setCoords();
this.canvas.requestRenderAll();
const { id, superType, type, player, width, height } = activeObject;
if (superType === 'element') {
if (key === 'visible') {
if (value) {
activeObject.element.style.display = 'block';
}
else {
activeObject.element.style.display = 'none';
}
}
const el = this.elementHandler.findById(id);
// update the element
this.elementHandler.setScaleOrAngle(el, activeObject);
this.elementHandler.setSize(el, activeObject);
this.elementHandler.setPosition(el, activeObject);
if (type === 'video' && player) {
player.setPlayerSize(width, height);
}
}
const { onModified } = this;
if (onModified) {
onModified(activeObject);
}
};
/**
* Set option
* @param {Partial<FabricObject>} option
* @returns
*/
this.setObject = (option) => {
const activeObject = this.canvas.getActiveObject();
if (!activeObject) {
return;
}
Object.keys(option).forEach(key => {
if (option[key] !== activeObject[key]) {
activeObject.set(key, option[key]);
activeObject.setCoords();
}
});
this.canvas.requestRenderAll();
const { id, superType, type, player, width, height } = activeObject;
if (superType === 'element') {
if ('visible' in option) {
if (option.visible) {
activeObject.element.style.display = 'block';
}
else {
activeObject.element.style.display = 'none';
}
}
const el = this.elementHandler.findById(id);
// update the element
this.elementHandler.setScaleOrAngle(el, activeObject);
this.elementHandler.setSize(el, activeObject);
this.elementHandler.setPosition(el, activeObject);
if (type === 'video' && player) {
player.setPlayerSize(width, height);
}
}
const { onModified } = this;
if (onModified) {
onModified(activeObject);
}
};
/**
* Set key pair by object
* @param {FabricObject} obj
* @param {string} key
* @param {*} value
* @returns
*/
this.setByObject = (obj, key, value) => {
if (!obj) {
return;
}
if (obj.type === 'svg') {
if (key === 'fill') {
obj.setFill(value);
}
else if (key === 'stroke') {
obj.setStroke(value);
}
}
obj.set(key, value);
obj.setCoords();
this.canvas.renderAll();
const { id, superType, type, player, width, height } = obj;
if (superType === 'element') {
if (key === 'visible') {
if (value) {
obj.element.style.display = 'block';
}
else {
obj.element.style.display = 'none';
}
}
const el = this.elementHandler.findById(id);
// update the element
this.elementHandler.setScaleOrAngle(el, obj);
this.elementHandler.setSize(el, obj);
this.elementHandler.setPosition(el, obj);
if (type === 'video' && player) {
player.setPlayerSize(width, height);
}
}
const { onModified } = this;
if (onModified) {
onModified(obj);
}
};
/**
* Set key pair by id
* @param {string} id
* @param {string} key
* @param {*} value
*/
this.setById = (id, key, value) => {
const findObject = this.findById(id);
this.setByObject(findObject, key, value);
};
/**
* Set partial by object
* @param {FabricObject} obj
* @param {FabricObjectOption} option
* @returns
*/
this.setByPartial = (obj, option) => {
if (!obj) {
return;
}
if (obj.type === 'svg') {
if (option.fill) {
obj.setFill(option.fill);
}
else if (option.stroke) {
obj.setStroke(option.stroke);
}
}
obj.set(option);
obj.setCoords();
this.canvas.renderAll();
const { id, superType, type, player, width, height } = obj;
if (superType === 'element') {
if ('visible' in option) {
if (option.visible) {
obj.element.style.display = 'block';
}
else {
obj.element.style.display = 'none';
}
}
const el = this.elementHandler.findById(id);
// update the element
this.elementHandler.setScaleOrAngle(el, obj);
this.elementHandler.setSize(el, obj);
this.elementHandler.setPosition(el, obj);
if (type === 'video' && player) {
player.setPlayerSize(width, height);
}
}
};
/**
* Set shadow
* @param {fabric.Shadow} option
* @returns
*/
this.setShadow = (option) => {
const activeObject = this.canvas.getActiveObject();
if (!activeObject) {
return;
}
activeObject.set('shadow', new fabric_1.fabric.Shadow(option));
this.canvas.requestRenderAll();
const { onModified } = this;
if (onModified) {
onModified(activeObject);
}
};
/**
* Set the image
* @param {FabricImage} obj
* @param {(File | string)} [source]
* @param {boolean} [keepSize] Keep size of previous Image
* @param {fabric.IImageOptions} [options]
* @returns
*/
this.setImage = (obj, source, keepSize, options) => {
const { height, scaleY } = obj;
const renderCallbaack = (imgObj, src) => {
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', (imgObj) => renderCallbaack(imgObj, null), {
dirty: true,
...options,
}));
}
if (source instanceof File) {
const reader = new FileReader();
reader.onload = () => {
obj.set('file', source);
obj.set('src', null);
resolve(obj.setSrc(reader.result, (imgObj) => renderCallbaack(imgObj, reader.result), {
dirty: true,
...options,
}));
};
reader.readAsDataURL(source);
}
else {
obj.set('file', null);
obj.set('src', source);
resolve(obj.setSrc(source, (imgObj) => renderCallbaack(imgObj, source), {
dirty: true,
crossOrigin: 'anonymous',
...options,
}));
}
});
};
/**
* Set image by id
* @param {string} id
* @param {*} source
* @param {boolean} [keepSize] Keep size of previous Image
* @returns
*/
this.setImageById = (id, source, keepSize) => {
const findObject = this.findById(id);
return Promise.resolve(this.setImage(findObject, source, keepSize));
};
/**
* Set Svg
*
* @param {SvgObject} obj
* @param {(File | string)} [source]
* @param {boolean} [keepSize] Keep size of previous SVG
* @param {boolean} [xmlString] XML string
*/
this.setSvg = (obj, source, keepSize, xmlString) => {
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, loadType: 'file', keepSize }));
}
else {
resolve(obj.loadSvg({ src: source, loadType: xmlString ? 'svg' : 'file', keepSize }));
}
});
};
/**
* Set visible
* @param {boolean} [visible]
* @returns
*/
this.setVisible = (visible) => {
const activeObject = this.canvas.getActiveObject();
if (!activeObject) {
return;
}
if (activeObject.superType === 'element') {
if (visible) {
activeObject.element.style.display = 'block';
}
else {
activeObject.element.style.display = 'none';
}
}
activeObject.set({
visible,
});
this.canvas.renderAll();
};
/**
* Set the position on Object
*
* @param {FabricObject} obj
* @param {boolean} [centered]
*/
this.centerObject = (obj, centered) => {
if (centered) {
this.canvas.centerObject(obj);
obj.setCoords();
}
else {
this.setByPartial(obj, {
left: obj.left / this.canvas.getZoom() -
obj.width / 2 -
this.canvas.viewportTransform[4] / this.canvas.getZoom(),
top: obj.top / this.canvas.getZoom() -
obj.height / 2 -
this.canvas.viewportTransform[5] / this.canvas.getZoom(),
});
}
};
/**
* Add object
* @param {FabricObjectOption} obj
* @param {boolean} [centered=true]
* @param {boolean} [loaded=false]
* @param {boolean} [group=false]
* @returns
*/
this.add = (obj, centered = true, loaded = false, group = false) => {
const { editable, onAdd, gridOption, objectOption } = this;
const option = {
hasControls: editable,
hasBorders: editable,
selectable: editable,
lockMovementX: !editable,
lockMovementY: !editable,
hoverCursor: !editable ? 'pointer' : 'move',
};
if (obj.type === 'i-text') {
option.editable = false;
}
else {
option.editable = editable;
}
if (editable && this.workarea.layout === 'fullscreen') {
option.scaleX = this.workarea.scaleX;
option.scaleY = this.workarea.scaleY;
}
const newOption = Object.assign({}, objectOption, obj, {
container: this.container.id,
editable,
}, option);
// Individually create canvas object
if (obj.superType === 'link') {
return this.linkHandler.create(newOption, loaded);
}
let createdObj;
// Create canvas object
if (obj.type === 'image') {
createdObj = this.addImage(newOption);
}
else if (obj.type === 'group') {
createdObj = this.addGroup(newOption);
}
else {
createdObj = this.fabricObjects[obj.type].create(newOption);
}
if (group) {
return createdObj;
}
this.canvas.add(createdObj);
this.objects = this.getObjects();
if (!editable && !(obj.superType === 'element')) {
createdObj.on('mousedown', this.eventHandler.object.mousedown);
}
if (createdObj.dblclick) {
createdObj.on('mousedblclick', this.eventHandler.object.mousedblclick);
}
if (this.objects.some(object => object.type === 'gif')) {
this.startRequestAnimFrame();
}
else {
this.stopRequestAnimFrame();
}
if (obj.superType !== 'drawing' && obj.superType !== 'link' && editable && !loaded) {
this.centerObject(createdObj, centered);
}
if (createdObj.superType === 'node') {
this.portHandler.create(createdObj);
if (createdObj.iconButton) {
this.canvas.add(createdObj.iconButton);
}
}
if (!editable && createdObj.animation && createdObj.animation.autoplay) {
this.animationHandler.play(createdObj.id);
}
if (createdObj.superType === 'node') {
createdObj.set('shadow', {
color: createdObj.stroke,
});
}
if (gridOption.enabled) {
this.gridHandler.setCoords(createdObj);
}
if (!this.transactionHandler.active && !loaded) {
this.transactionHandler.save('add');
}
if (onAdd && editable && !loaded) {
onAdd(createdObj);
}
return createdObj;
};
/**
* Add group object
*
* @param {FabricGroup} obj
* @param {boolean} [centered=true]
* @param {boolean} [loaded=false]
* @returns
*/
this.addGroup = (obj) => {
const { objects = [], ...other } = obj;
const _objects = objects.map(child => this.add(child, false, true, true));
return new fabric_1.fabric.Group(_objects, other);
};
/**
* Add iamge object
* @param {FabricImage} obj
* @returns
*/
this.addImage = (obj) => {
const { objectOption } = this;
const { filters = [], src, file, ...otherOption } = obj;
const image = new Image();
// if (typeof src === 'string') {
// image.src = src;
// }
const createdObj = new fabric_1.fabric.Image(image, {
...objectOption,
...otherOption,
});
createdObj.set({
filters: this.imageHandler.createFilters(filters),
});
this.setImage(createdObj, src || file);
return createdObj;
};
/**
* Remove object
* @param {FabricObject} target
* @returns {any}
*/
this.remove = (target) => {
const activeObject = target || this.canvas.getActiveObject();
if (this.prevTarget && this.prevTarget.superType === 'link') {
this.linkHandler.remove(this.prevTarget);
if (!this.transactionHandler.active) {
this.transactionHandler.save('remove');
}
return;
}
if (!activeObject) {
return;
}
if (typeof activeObject.deletable !== 'undefined' && !activeObject.deletable) {
return;
}
if (activeObject.type !== 'activeSelection') {
this.canvas.discardActiveObject();
if (activeObject.superType === 'element') {
this.elementHandler.removeById(activeObject.id);
}
if (activeObject.superType === 'link') {
this.linkHandler.remove(activeObject);
}
else if (activeObject.superType === 'node') {
if (activeObject.toPort) {
if (activeObject.toPort.links.length) {
activeObject.toPort.links.forEach((link) => {
this.linkHandler.remove(link, 'from');
});
}
this.canvas.remove(activeObject.toPort);
}
if (activeObject.fromPort && activeObject.fromPort.length) {
activeObject.fromPort.forEach((port) => {
if (port.links.length) {
port.links.forEach((link) => {
this.linkHandler.remove(link, 'to');
});
}
this.canvas.remove(port);
});
}
}
this.canvas.remove(activeObject);
}
else {
const { _objects: activeObjects } = activeObject;
const existDeleted = activeObjects.some((obj) => typeof obj.deletable !== 'undefined' && !obj.deletable);
if (existDeleted) {
return;
}
this.canvas.discardActiveObject();
activeObjects.forEach((obj) => {
if (obj.superType === 'element') {
this.elementHandler.removeById(obj.id);
}
else if (obj.superType === 'node') {
if (obj.toPort) {
if (obj.toPort.links.length) {
obj.toPort.links.forEach((link) => {
this.linkHandler.remove(link, 'from');
});
}
this.canvas.remove(obj.toPort);
}
if (obj.fromPort && obj.fromPort.length) {
obj.fromPort.forEach((port) => {
if (port.links.length) {
port.links.forEach((link) => {
this.linkHandler.remove(link, 'to');
});
}
this.canvas.remove(port);
});
}
}
this.canvas.remove(obj);
});
}
if (!this.transactionHandler.active) {
this.transactionHandler.save('remove');
}
this.objects = this.getObjects();
const { onRemove } = this;
if (onRemove) {
onRemove(activeObject);
}
};
/**
* Remove object by id
* @param {string} id
*/
this.removeById = (id) => {
const findObject = this.findById(id);
if (findObject) {
this.remove(findObject);
}
};
/**
* Delete at origin object list
* @param {string} id
*/
this.removeOriginById = (id) => {
const object = this.findOriginByIdWithIndex(id);
if (object.index > 0) {
this.objects.splice(object.index, 1);
}
};
/**
* Duplicate object
* @returns
*/
this.duplicate = () => {
const { onAdd, propertiesToInclude, gridOption: { grid = 10 }, } = this;
const activeObject = this.canvas.getActiveObject();
if (!activeObject) {
return;
}
if (typeof activeObject.cloneable !== 'undefined' && !activeObject.cloneable) {
return;
}
activeObject.clone((clonedObj) => {
this.canvas.discardActiveObject();
clonedObj.set({
left: clonedObj.left + grid,
top: clonedObj.top + grid,
evented: true,
});
if (clonedObj.type === 'activeSelection') {
const activeSelection = clonedObj;
activeSelection.canvas = this.canvas;
activeSelection.forEachObject((obj) => {
obj.set('id', (0, uuidv4_1.uuid)());
if (obj.superType === 'node') {
obj.set('shadow', {
color: obj.stroke,
});
}
this.canvas.add(obj);
this.objects = this.getObjects();
if (obj.dblclick) {
obj.on('mousedblclick', this.eventHandler.object.mousedblclick);
}
});
if (onAdd) {
onAdd(activeSelection);
}
activeSelection.setCoords();
}
else {
if (activeObject.id === clonedObj.id) {
clonedObj.set('id', (0, uuidv4_1.uuid)());
}
if (clonedObj.superType === 'node') {
clonedObj.set('shadow', {
color: clonedObj.stroke,
});
}
this.canvas.add(clonedObj);
this.objects = this.getObjects();
if (clonedObj.dblclick) {
clonedObj.on('mousedblclick', this.eventHandler.object.mousedblclick);
}
if (onAdd) {
onAdd(clonedObj);
}
}
this.canvas.setActiveObject(clonedObj);
this.portHandler.create(clonedObj);
this.canvas.requestRenderAll();
}, propertiesToInclude);
};
/**
* Duplicate object by id
* @param {string} id
* @returns
*/
this.duplicateById = (id) => {
const { onAdd, propertiesToInclude, gridOption: { grid = 10 }, } = this;
const findObject = this.findById(id);
if (findObject) {
if (typeof findObject.cloneable !== 'undefined' && !findObject.cloneable) {
return false;
}
findObject.clone((cloned) => {
cloned.set({
left: cloned.left + grid,
top: cloned.top + grid,
id: (0, uuidv4_1.uuid)(),
evented: true,
});
this.canvas.add(cloned);
this.objects = this.getObjects();
if (onAdd) {
onAdd(cloned);
}
if (cloned.dblclick) {
cloned.on('mousedblclick', this.eventHandler.object.mousedblclick);
}
this.canvas.setActiveObject(cloned);
this.portHandler.create(cloned);
this.canvas.requestRenderAll();
}, propertiesToInclude);
}
return true;
};
/**
* Cut object
*
*/
this.cut = () => {
this.copy();
this.remove();
this.isCut = true;
};
/**
* Copy to clipboard
*
* @param {*} value
*/
this.copyToClipboard = (value) => {
const textarea = document.createElement('textarea');
document.body.appendChild(textarea);
textarea.value = value;
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
this.canvas.wrapperEl.focus();
};
/**
* Copy object
*
* @returns
*/
this.copy = () => {
const { propertiesToInclude } = this;
const activeObject = this.canvas.getActiveObject();
if (activeObject && activeObject.superType === 'link') {
return false;
}
if (activeObject) {
if (typeof activeObject.cloneable !== 'undefined' && !activeObject.cloneable) {
return false;
}
if (activeObject.type === 'activeSelection') {
const activeSelection = activeObject;
if (activeSelection.getObjects().some((obj) => obj.superType === 'node')) {
if (this.keyEvent.clipboard) {
const links = [];
const objects = activeSelection.getObjects().map((obj, index) => {
if (typeof obj.cloneable !== 'undefined' && !obj.cloneable) {
return null;
}
if (obj.fromPort.length) {
obj.fromPort.forEach((port) => {
if (port.links.length) {
port.links.forEach((link) => {
const linkTarget = {
fromNodeIndex: index,
fromPortId: port.id,
type: 'curvedLink',
superType: 'link',
};
const findIndex = activeSelection
.getObjects()
.findIndex((compObj) => compObj.id === link.toNode.id);
if (findIndex >= 0) {
linkTarget.toNodeIndex = findIndex;
links.push(linkTarget);
}
});
}
});
}
return {
name: obj.name,
description: obj.description,
superType: obj.superType,
type: obj.type,
nodeClazz: obj.nodeClazz,
configuration: obj.configuration,
descriptor: obj.descriptor,
properties: {
left: activeObject.left + activeObject.width / 2 + obj.left || 0,
top: activeObject.top + activeObject.height / 2 + obj.top || 0,
iconName: obj.descriptor.icon,
},
};
});
this.copyToClipboard(JSON.stringify(objects.concat(links), null, '\t'));
return true;
}
this.clipboard = activeObject;
return true;
}
}
activeObject.clone((cloned) => {
if (this.keyEvent.clipboard) {
if (cloned.superType === 'node') {
const node = {
name: cloned.name,
description: cloned.description,
superType: cloned.superType,
type: cloned.type,
nodeClazz: cloned.nodeClazz,
configuration: cloned.configuration,
descriptor: cloned.descriptor,
properties: {
left: cloned.left || 0,
top: cloned.top || 0,
iconName: cloned.descriptor.icon,
},
};
const objects = [node];
this.copyToClipboard(JSON.stringify(objects, null, '\t'));
}
else {
this.copyToClipboard(JSON.stringify(cloned.toObject(propertiesToInclude), null, '\t'));
}
}
else {
this.clipboard = cloned;
}
}, propertiesToInclude);
}
return true;
};
/**
* Paste object
*
* @returns
*/
this.paste = () => {
const { onAdd, propertiesToInclude, gridOption: { grid = 10 }, clipboard, isCut, } = this;
const padding = isCut ? 0 : grid;
if (!clipboard) {
return false;
}
if (typeof clipboard.cloneable !== 'undefined' && !clipboard.cloneable) {
return false;
}
this.isCut = false;
if (clipboard.type === 'activeSelection') {
if (clipboard.getObjects().some((obj) => obj.superType === 'node')) {
this.canvas.discardActiveObject();
const objects = [];
const linkObjects = [];
clipboard.getObjects().forEach((obj) => {
if (typeof obj.cloneable !== 'undefined' && !obj.cloneable) {
return;
}
const clonedObj = obj.duplicate();
if (clonedObj.type === 'SwitchNode') {
clonedObj.set({
left: obj.left + padding + padding,
top: obj.top + padding,
});
}
else {
clonedObj.set({
left: obj.left + padding,
top: obj.top + padding,
});
}
if (obj.fromPort.length) {
obj.fromPort.forEach((port) => {
if (port.links.length) {
port.links.forEach((link) => {
const linkTarget = {
fromNode: clonedObj.id,
fromPort: port.id,
};
const findIndex = clipboard
.getObjects()
.findIndex((compObj) => compObj.id === link.toNode.id);
if (findIndex >= 0) {
linkTarget.toNodeIndex = findIndex;
linkObjects.push(linkTarget);
}
});
}
});
}
if (clonedObj.dblclick) {
clonedObj.on('mousedblclick', this.eventHandler.object.mousedblclick);
}
this.canvas.add(clonedObj);
this.objects = this.getObjects();
this.portHandler.create(clonedObj);
objects.push(clonedObj);
});
if (linkObjects.length) {
linkObjects.forEach((linkObject) => {
const { fromNode, fromPort, toNodeIndex } = linkObject;
const toNode = objects[toNodeIndex];
const link = {
type: 'curvedLink',
fromNodeId: fromNode,
fromPortId: fromPort,
toNodeId: toNode.id,
toPortId: toNode.toPort.id,
};
this.linkHandler.create(link);
});
}
const activeSelection = new fabric_1.fabric.ActiveSelection(objects, {
canvas: this.canvas,
...this.activeSelectionOption,
});
if (isCut) {
this.clipboard = null;
}
else {
this.clipboard = activeSelection;
}
if (!this.transactionHandler.active) {
this.transactionHandler.save('paste');
}
this.canvas.setActiveObject(activeSelection);
this.canvas.renderAll();
return true;
}
}
clipboard.clone((clonedObj) => {
this.canvas.discardActiveObject();
clonedObj.set({
left: clonedObj.left + padding,
top: clonedObj.top + padding,
id: isCut ? clipboard.id : (0, uuidv4_1.uuid)(),
evented: true,
});
if (clonedObj.type === 'activeSelection') {
clonedObj.canvas = this.canvas;
clonedObj.forEachObject((obj) => {
obj.set('id', isCut ? obj.id : (0, uuidv4_1.uuid)());
this.canvas.add(obj);
if (obj.dblclick) {
obj.on('mousedblclick', this.eventHandler.object.mousedblclick);
}
});
}
else {
if (clonedObj.superType === 'node') {
clonedObj = clonedObj.duplicate();
}
this.canvas.add(clonedObj);
if (clonedObj.dblclick) {
clonedObj.on('mousedblclick', this.eventHandler.object.mousedblclick);
}
}
const newClipboard = clipboard.set({
top: clonedObj.top,
left: clonedObj.left,
});
if (isCut) {
this.clipboard = null;
}
else {
this.clipboard = newClipboard;
}
if (clonedObj.superType === 'node') {
this.portHandler.create(clonedObj);
}
if (!this.transactionHandler.active) {
this.transactionHandler.save('paste');
}
// TODO...
// After toGroup svg elements not rendered.
this.objects = this.getObjects();
if (onAdd) {
onAdd(clonedObj);
}
clonedObj.setCoords();
this.canvas.setActiveObject(clonedObj);
this.canvas.requestRenderAll();
}, propertiesToInclude);
return true;
};
/**
* Find object by object
* @param {FabricObject} obj
*/
this.find = (obj) => this.findById(obj.id);
/**
* Find object by id
* @param {string} id
* @returns {(FabricObject | null)}
*/
this.findById = (id) => {
let findObject;
const exist = this.objects.some(obj => {
if (obj.id === id) {
findObject = obj;
return true;
}
return false;
});
if (!exist) {
(0, warning_1.default)(true, 'Not found object by id.');
return null;
}
return findObject;
};
/**
* Find object in origin list
* @param {string} id
* @returns
*/
this.findOriginById = (id) => {
let findObject;
const exist = this.objects.some(obj => {
if (obj.id === id) {
findObject = obj;
return true;
}
return false;
});
if (!exist) {
console.warn('Not found object by id.');
return null;
}
return findObject;
};
/**
* Return origin object list
* @param {string} id
* @returns
*/
this.findOriginByIdWithIndex = (id) => {
let findObject;
let index = -1;
const exist = this.objects.some((obj, i) => {
if (obj.id === id) {
findObject = obj;
index = i;
return true;
}
return false;
});
if (!exist) {
console.warn('Not found object by id.');
return {};
}
return {
object: findObject,
index,
};
};
/**
* Select object
* @param {FabricObject} obj
* @param {boolean} [find]
*/
this.select = (obj, find) => {
let findObject = obj;
if (find) {
findObject = this.find(obj);
}
if (findObject) {
this.canvas.discardActiveObject();
this.canvas.setActiveObject(findObject);
this.canvas.requestRenderAll();
}
};
/**
* Select by id
* @param {string} id
*/
this.selectById = (id) => {
const findObject = this.findById(id);
if (findObject) {
this.canvas.discardActiveObject();
this.canvas.setActiveObject(findObject);
this.canvas.requestRenderAll();
}
};
/**
* Select all
* @returns
*/
this.selectAll = () => {
this.canvas.discardActiveObject();
const filteredObjects = this.canvas.getObjects().filter((obj) => {
if (obj.id === 'workarea') {
return false;
}
else if (!obj.evented) {
return false;
}
else if (obj.superType === 'link') {
return false;
}
else if (obj.superType === 'port') {
return false;
}
else if (obj.superType === 'element') {
return false;
}
else if (obj.locked) {
return false;
}
return true;
});
if (!filteredObjects.length) {
return;
}
if (filteredObjects.length === 1) {
this.canvas.setActiveObject(filteredObjects[0]);
this.canvas.renderAll();
return;
}
const activeSelection = new fabric_1.fabric.ActiveSelection(filteredObjects, {
canvas: this.canvas,
...this.activeSelectionOption,
});
this.canvas.setActiveObject(activeSelection);
this.canvas.renderAll();
};
/**
* Save origin width, height
* @param {FabricObject} obj
* @param {number} width
* @param {number} height
*/
this.originScaleToResize = (obj, width, height) => {
if (obj.id === 'workarea') {
this.setByPartial(obj, {
workareaWidth: obj.width,
workareaHeight: obj.height,
});
}
this.setByPartial(obj, {
scaleX: width / obj.width,
scaleY: height / obj.height,
});
};
/**
* When set the width, height, Adjust the size
* @param {number} width
* @param {number} height
*/
this.scaleToResize = (width, height) => {
const activeObject = this.can