devexpress-diagram
Version:
DevExpress Diagram Control
769 lines (733 loc) • 38.8 kB
text/typescript
import { ShapeTypes } from "../Model/Shapes/ShapeTypes";
import { DiagramItem, ItemDataKey } from "../Model/DiagramItem";
import { Shape } from "../Model/Shapes/Shape";
import { Connector } from "../Model/Connectors/Connector";
import { DiagramModel } from "../Model/Model";
import { INodeDataImporter, IEdgeDataImporter, IDataChangesListener, IItemDataImporter, IDataImportParameters } from "./Interfaces";
import { DataSource } from "./DataSource";
import { DataSourceNodeItem, DataSourceEdgeItem, DataSourceItem } from "./DataSourceItems";
import { ModelUtils } from "../Model/ModelUtils";
import { Data } from "../Utils/Data";
import { ObjectUtils } from "../Utils";
import { DiagramUnit } from "../Enums";
import { StringUtils } from "@devexpress/utils/lib/utils/string";
import { MathUtils } from "@devexpress/utils/lib/utils/math";
export class UpdateNodeKeyRelatedObjectsStackItem {
constructor(public shape: Shape, public nodeObj: DataSourceNodeItem) {
}
}
export class UpdateNodeKeyRelatedObjectsStackAction {
constructor(public kind: string, public nodeObj: any) {
}
}
export class DocumentDataSource extends DataSource {
private nodeInsertingLockCount: number = 0;
private updateNodeKeyRelatedObjectsCount: number = 0;
updateNodeKeyRelatedObjectsStack: UpdateNodeKeyRelatedObjectsStackItem[] = [];
updateNodeKeyRelatedObjectsStackActions: UpdateNodeKeyRelatedObjectsStackAction[] = [];
constructor(private changesListener: IDataChangesListener,
nodeDataSource: any[],
edgeDataSource: any[],
parameters?: IDataImportParameters,
nodeDataImporter?: INodeDataImporter,
edgeDataImporter?: IEdgeDataImporter) {
super("Document", nodeDataSource, edgeDataSource, parameters, nodeDataImporter, edgeDataImporter);
}
updateItemsByModel(model: DiagramModel) {
this.beginChangesNotification();
this.deleteNodes(model);
this.deleteEdges(model);
model.items.forEach(item => {
if(item instanceof Shape)
this.updateNode(model, item);
if(item instanceof Connector)
this.updateEdge(model, item);
});
this.endChangesNotification(false);
}
protected isItemObjectModified(item: DiagramItem, itemObj: DataSourceItem, importer: IItemDataImporter): boolean {
let modified = (importer.setLocked && itemObj.locked !== item.locked) ||
(importer.setZIndex && itemObj.zIndex !== item.zIndex) ||
(importer.setCustomData && !ObjectUtils.compareObjects(itemObj.customData, item.customData));
if(!modified && importer.setStyle) {
const defaultStyle = item.style.getDefaultInstance();
item.style.forEach(key => {
if(item.style[key] !== defaultStyle[key] && item.style[key] !== (itemObj.style && itemObj.style[key]))
modified = true;
});
}
if(!modified && importer.setStyleText) {
const defaultTextStyle = item.styleText.getDefaultInstance();
item.styleText.forEach(key => {
if(item.styleText[key] !== defaultTextStyle[key] && item.styleText[key] !== (itemObj.styleText && itemObj.styleText[key]))
modified = true;
});
}
return modified;
}
protected setDataObjectKeyRelatedProperty(method: (dataObj: any, key: ItemDataKey) => void, dataObj: any, key: ItemDataKey, allowAutoGeneratedProperty: boolean) {
if(allowAutoGeneratedProperty || this.autoGeneratedDataKeys[key] === undefined)
method(dataObj, key);
}
protected updateItemObjectProperties(itemObj: DataSourceItem, item: DiagramItem, importer: IItemDataImporter) {
if(importer.setCustomData) {
itemObj.customData = ObjectUtils.cloneObject(item.customData);
if(itemObj.dataObj && itemObj.customData !== undefined)
importer.setCustomData(itemObj.dataObj, item.customData);
}
if(importer.setLocked) {
itemObj.locked = item.locked;
if(itemObj.dataObj && itemObj.locked !== undefined)
importer.setLocked(itemObj.dataObj, item.locked);
}
if(importer.setStyle) {
const styleObj = item.style.toObject();
itemObj.style = styleObj;
if(itemObj.dataObj && itemObj.style !== undefined)
importer.setStyle(itemObj.dataObj, Data.objectToCssText(styleObj));
}
if(importer.setStyleText) {
const styleTextObj = item.styleText.toObject();
itemObj.styleText = styleTextObj;
if(itemObj.dataObj && itemObj.styleText !== undefined)
importer.setStyleText(itemObj.dataObj, Data.objectToCssText(styleTextObj));
}
if(importer.setZIndex) {
itemObj.zIndex = item.zIndex;
if(itemObj.dataObj && itemObj.zIndex !== undefined)
importer.setZIndex(itemObj.dataObj, item.zIndex);
}
}
protected deleteItems(dataSourceItems: DataSourceItem[],
findItem: (ItemKey) => any,
getParentArray: (DataSourceItem) => any[],
callback: (DataSourceItem, boolean) => void) {
const items = dataSourceItems.slice();
items.forEach(item => {
if(item.key !== undefined && item.key !== null && !findItem(item.key)) {
const parentArray = getParentArray(item);
const index = parentArray.indexOf(item.dataObj);
parentArray.splice(index, 1);
callback(item, index > -1);
}
});
}
protected updateNode(model: DiagramModel, shape: Shape): any {
let nodeObj = this.findNode(shape.dataKey);
if(!nodeObj) {
const dataObj = {};
if(shape.dataKey !== undefined && shape.dataKey !== null)
this.nodeDataImporter.setKey(dataObj, shape.dataKey);
nodeObj = this.addNodeInternal(dataObj, shape.description.key, shape.text);
this.nodeDataSource.push(nodeObj.dataObj);
this.setDataObjectKeyRelatedProperty(this.nodeDataImporter.setKey, dataObj, nodeObj.key, this.addInternalKeyOnInsert);
this.updateNodeObjectProperties(shape, nodeObj, model.units);
this.updateNodeObjectConnectedProperties(shape, nodeObj);
this.updateNodeObjectKey(shape, nodeObj, nodeObj.dataObj);
this.beginChangesNotification();
this.beginNodeInserting();
this.changesListener.notifyNodeInserted.call(this.changesListener, nodeObj.dataObj,
(data: any) => {
this.updateNodeObjectKey(shape, nodeObj, data);
this.endNodeInserting();
this.endChangesNotification(false);
},
(error: any) => {
this.endNodeInserting();
this.endChangesNotification(false);
}
);
}
else
if(this.isNodeObjectModified(shape, nodeObj, model.units)) {
this.updateNodeObjectProperties(shape, nodeObj, model.units);
this.updateNodeObjectConnectedProperties(shape, nodeObj);
this.beginChangesNotification();
this.changesListener.notifyNodeUpdated.call(this.changesListener,
this.nodeDataImporter.getKey(nodeObj.dataObj || nodeObj.key), nodeObj.dataObj,
(key: ItemDataKey, data: any) => {
this.endChangesNotification(false);
},
(error: any) => {
this.endChangesNotification(false);
}
);
}
else this.updateNodeObjectConnectedProperties(shape, nodeObj, this.changesListener);
}
areImageUrlsEqual(url1: string, url2: string): boolean {
return (url1 === url2) ||
(StringUtils.isNullOrEmpty(url1) && StringUtils.isNullOrEmpty(url2));
}
protected isNodeObjectModified(shape: Shape, nodeObj: DataSourceNodeItem, units: DiagramUnit): boolean {
return this.isItemObjectModified(shape, nodeObj, this.nodeDataImporter) ||
(nodeObj.type !== shape.description.key && !(nodeObj.type === undefined && shape.description.key === ShapeTypes.Rectangle)) ||
!this.compareStrings(nodeObj.text, shape.text) ||
(this.nodeDataImporter.setImage && !this.areImageUrlsEqual(nodeObj.image, shape.image.actualUrl)) ||
(this.nodeDataImporter.setLeft && !MathUtils.numberCloseTo(nodeObj.left, ModelUtils.getlUnitValue(units, shape.position.x))) ||
(this.nodeDataImporter.setTop && !MathUtils.numberCloseTo(nodeObj.top, ModelUtils.getlUnitValue(units, shape.position.y))) ||
(this.nodeDataImporter.setWidth && !MathUtils.numberCloseTo(nodeObj.width, ModelUtils.getlUnitValue(units, shape.size.width))) ||
(this.nodeDataImporter.setHeight && !MathUtils.numberCloseTo(nodeObj.height, ModelUtils.getlUnitValue(units, shape.size.height)));
}
protected updateNodeObjectProperties(shape: Shape, nodeObj: DataSourceNodeItem, units: DiagramUnit) {
this.updateItemObjectProperties(nodeObj, shape, this.nodeDataImporter);
if(this.nodeDataImporter.setType) {
nodeObj.type = shape.description.key;
this.nodeDataImporter.setType(nodeObj.dataObj, shape.description.key);
}
if(this.nodeDataImporter.setText) {
nodeObj.text = shape.text;
this.nodeDataImporter.setText(nodeObj.dataObj, shape.text);
}
if(this.nodeDataImporter.setImage) {
nodeObj.image = shape.image.actualUrl;
this.nodeDataImporter.setImage(nodeObj.dataObj, shape.image.actualUrl === undefined ? null : shape.image.actualUrl);
}
if(this.nodeDataImporter.setLeft) {
const left = ModelUtils.getlUnitValue(units, shape.position.x);
nodeObj.left = left;
this.nodeDataImporter.setLeft(nodeObj.dataObj, left);
}
if(this.nodeDataImporter.setTop) {
const top = ModelUtils.getlUnitValue(units, shape.position.y);
nodeObj.top = top;
this.nodeDataImporter.setTop(nodeObj.dataObj, top);
}
if(this.nodeDataImporter.setWidth) {
const width = ModelUtils.getlUnitValue(units, shape.size.width);
nodeObj.width = width;
this.nodeDataImporter.setWidth(nodeObj.dataObj, width);
}
if(this.nodeDataImporter.setHeight) {
const height = ModelUtils.getlUnitValue(units, shape.size.height);
nodeObj.height = height;
this.nodeDataImporter.setHeight(nodeObj.dataObj, height);
}
}
protected updateNodeObjectConnectedProperties(shape: Shape, nodeObj: DataSourceNodeItem, changesListener?: IDataChangesListener) {
if(this.useNodeParentId && this.nodeDataImporter.setParentKey !== undefined) {
const parentKey = this.getParentItemKey(shape);
const parentItem = this.findNode(parentKey);
this.updateNodeObjectParentKey(nodeObj, parentItem, changesListener);
}
if(this.useNodeContainerId && this.nodeDataImporter.setContainerKey !== undefined) {
const containerKey = this.getContainerShapeKey(shape);
const containerItem = this.findNode(containerKey);
this.updateNodeObjectContainerKey(nodeObj, containerItem, changesListener);
}
if(this.useNodeItems && this.nodeDataImporter.setItems !== undefined) {
const parentKey = this.getParentItemKey(shape);
const parentItem = this.findNode(parentKey);
this.updateNodeObjectItems(nodeObj, parentItem, changesListener);
}
if(this.useNodeChildren && this.nodeDataImporter.setChildren !== undefined) {
const containerKey = this.getContainerShapeKey(shape);
const containerItem = this.findNode(containerKey);
this.updateNodeObjectChildren(nodeObj, containerItem, changesListener);
}
}
IsNodeParentIdMode(): boolean {
return (this.useNodeParentId && this.nodeDataImporter.setParentKey !== undefined);
}
IsNodeItemsMode(): boolean {
return (this.useNodeItems && this.nodeDataImporter.setItems !== undefined);
}
protected updateNodeObjectParentKey(nodeObj: DataSourceNodeItem, parentNodeObj: DataSourceNodeItem,
changesListener?: IDataChangesListener) {
const parentKey = this.nodeDataImporter.getParentKey(nodeObj.dataObj);
const newParentKey = parentNodeObj ? this.nodeDataImporter.getKey(parentNodeObj.dataObj) : undefined;
if(parentKey !== newParentKey && !(this.isRootParentKey(parentKey) && this.isRootParentKey(newParentKey))) {
this.setDataObjectKeyRelatedProperty(this.nodeDataImporter.setParentKey, nodeObj.dataObj, newParentKey, false);
if(changesListener)
if(this.isInUpdateNodeKeyRelatedObjects())
this.addToUpdateNodeKeyRelatedObjectsStackAction("shape", nodeObj);
else
this.updateNodeObjectContainerOrParentKeyInternal(nodeObj, changesListener);
}
}
protected updateNodeObjectParentKeyInternal(nodeObj: DataSourceNodeItem, changesListener: IDataChangesListener) {
this.beginChangesNotification();
changesListener.notifyNodeUpdated.call(changesListener,
this.nodeDataImporter.getKey(nodeObj.dataObj) || nodeObj.key,
nodeObj.dataObj,
(key: ItemDataKey, data: any) => {
this.endChangesNotification(false);
},
(error: any) => {
this.endChangesNotification(false);
}
);
}
protected updateNodeObjectContainerKey(nodeObj: DataSourceNodeItem, containerNodeObj: DataSourceNodeItem,
changesListener?: IDataChangesListener) {
const containerKey = this.nodeDataImporter.getContainerKey(nodeObj.dataObj);
const newContainerKey = containerNodeObj ? this.nodeDataImporter.getKey(containerNodeObj.dataObj) : undefined;
if(containerKey !== newContainerKey && !(this.isRootParentKey(containerKey) && this.isRootParentKey(newContainerKey))) {
this.setDataObjectKeyRelatedProperty(this.nodeDataImporter.setContainerKey, nodeObj.dataObj, newContainerKey, false);
if(changesListener)
if(this.isInUpdateNodeKeyRelatedObjects())
this.addToUpdateNodeKeyRelatedObjectsStackAction("shape", nodeObj);
else
this.updateNodeObjectContainerOrParentKeyInternal(nodeObj, changesListener);
}
}
protected updateNodeObjectContainerOrParentKeyInternal(nodeObj: DataSourceNodeItem, changesListener: IDataChangesListener) {
this.beginChangesNotification();
changesListener.notifyNodeUpdated.call(changesListener,
this.nodeDataImporter.getKey(nodeObj.dataObj) || nodeObj.key,
nodeObj.dataObj,
(key: ItemDataKey, data: any) => {
this.endChangesNotification(false);
},
(error: any) => {
this.endChangesNotification(false);
}
);
}
protected isRootParentKey(key: ItemDataKey) {
return key === undefined || key === null || !this.findNode(key);
}
protected updateNodeObjectItems(nodeObj: DataSourceNodeItem, parentNodeObj: DataSourceNodeItem,
changesListener?: IDataChangesListener) {
if(parentNodeObj && nodeObj.parentDataObj !== parentNodeObj.dataObj ||
!parentNodeObj && nodeObj.parentDataObj)
if(!parentNodeObj || !this.checkNodeCyrcleItems(nodeObj.dataObj, parentNodeObj.dataObj)) {
const oldItemsArray = nodeObj.parentDataObj ? this.nodeDataImporter.getItems(nodeObj.parentDataObj) : this.nodeDataSource;
const index = oldItemsArray.indexOf(nodeObj.dataObj);
oldItemsArray.splice(index, 1);
const itemsArray = parentNodeObj ? this.nodeDataImporter.getItems(parentNodeObj.dataObj) : this.nodeDataSource;
if(!itemsArray)
this.nodeDataImporter.setItems(parentNodeObj.dataObj, [nodeObj.dataObj]);
else
itemsArray.push(nodeObj.dataObj);
nodeObj.parentDataObj = parentNodeObj && parentNodeObj.dataObj;
if(changesListener) {
this.beginChangesNotification();
changesListener.notifyNodeUpdated.call(changesListener,
this.nodeDataImporter.getKey(nodeObj.dataObj) || nodeObj.key,
nodeObj.dataObj,
(key: ItemDataKey, data: any) => {
this.endChangesNotification(false);
},
(error: any) => {
this.endChangesNotification(false);
}
);
}
}
}
protected updateNodeObjectChildren(nodeObj: DataSourceNodeItem, containerNodeObj: DataSourceNodeItem,
changesListener?: IDataChangesListener) {
if(containerNodeObj && nodeObj.containerDataObj !== containerNodeObj.dataObj ||
!containerNodeObj && nodeObj.containerDataObj) {
const oldChildrenArray = nodeObj.containerDataObj ? this.nodeDataImporter.getChildren(nodeObj.containerDataObj) : this.nodeDataSource;
const index = oldChildrenArray.indexOf(nodeObj.dataObj);
oldChildrenArray.splice(index, 1);
const childrenArray = containerNodeObj ? this.nodeDataImporter.getChildren(containerNodeObj.dataObj) : this.nodeDataSource;
if(!childrenArray)
this.nodeDataImporter.setChildren(containerNodeObj.dataObj, [nodeObj.dataObj]);
else
childrenArray.push(nodeObj.dataObj);
nodeObj.containerDataObj = containerNodeObj && containerNodeObj.dataObj;
if(changesListener) {
this.beginChangesNotification();
changesListener.notifyNodeUpdated.call(changesListener,
this.nodeDataImporter.getKey(nodeObj.dataObj) || nodeObj.key,
nodeObj.dataObj,
(key: ItemDataKey, data: any) => {
this.endChangesNotification(false);
},
(error: any) => {
this.endChangesNotification(false);
}
);
}
}
}
protected checkNodeCyrcleItems(nodeDataObj: any, parentDataObjCandidate: any) {
let result = false;
const items = this.nodeDataImporter.getItems(nodeDataObj);
if(items)
items.forEach(childDataObj => {
result = result || childDataObj === parentDataObjCandidate ||
this.checkNodeCyrcleItems(childDataObj, parentDataObjCandidate);
});
return result;
}
protected updateNodeObjectKey(shape: Shape, nodeObj: DataSourceNodeItem, dataObj: any) {
const key = this.nodeDataImporter.getKey(dataObj);
let dataKeyChanged = false;
if(key !== undefined && key !== null && key !== nodeObj.key) {
delete this.autoGeneratedDataKeys[nodeObj.key];
nodeObj.key = key;
dataKeyChanged = true;
}
shape.dataKey = nodeObj.key;
if(nodeObj.dataObj !== dataObj) {
const parentArray = this.getNodeArray(nodeObj);
const index = parentArray.indexOf(nodeObj.dataObj);
parentArray.splice(index, 1, dataObj);
nodeObj.dataObj = dataObj;
}
if(dataKeyChanged)
this.updateNodeKeyRelatedObjects(shape, nodeObj);
}
protected updateNodeKeyRelatedObjects(shape: Shape, nodeObj: DataSourceNodeItem) {
if(this.isInNodeInserting()) {
this.addToUpdateNodeKeyRelatedObjectsStack(shape, nodeObj);
return;
}
if(this.useNodeParentId && this.nodeDataImporter.setParentKey !== undefined) {
const childItems = this.getChildItems(shape);
childItems.forEach(item => {
const childNodeObj = this.findNode(item.dataKey);
if(childNodeObj)
this.updateNodeObjectParentKey(childNodeObj, nodeObj, this.changesListener);
});
}
if(this.useNodeContainerId && this.nodeDataImporter.setContainerKey !== undefined)
shape.children.forEach(item => {
const childNodeObj = item instanceof Shape ? this.findNode(item.dataKey) : undefined;
if(childNodeObj)
this.updateNodeObjectContainerKey(childNodeObj, nodeObj, this.changesListener);
});
if(this.useEdgesArray())
shape.attachedConnectors.forEach(connector => {
const edgeObj = this.findEdge(connector.dataKey);
if(edgeObj) {
if(shape === connector.beginItem)
this.updateEdgeObjectFromProperty(nodeObj, edgeObj, this.changesListener);
if(shape === connector.endItem)
this.updateEdgeObjectToProperty(nodeObj, edgeObj, this.changesListener);
}
});
}
protected deleteNodes(model: DiagramModel) {
this.deleteItems(this.nodes,
key => model.findShapeByDataKey(key),
item => this.getNodeArray(item),
(item: DataSourceItem, dataModified: boolean) => {
const key = (item.dataObj && this.nodeDataImporter.getKey(item.dataObj)) || item.key;
const nodeObj = this.findNode(key);
if(nodeObj)
this.nodes.splice(this.nodes.indexOf(nodeObj), 1);
if(dataModified) {
this.beginChangesNotification();
this.changesListener.notifyNodeRemoved.call(this.changesListener, key, item.dataObj,
(key: ItemDataKey, data: any) => {
this.endChangesNotification(false);
},
(error: any) => {
this.endChangesNotification(false);
}
);
}
}
);
}
protected getParentItem(shape: Shape): DiagramItem {
for(let i = 0; i < shape.attachedConnectors.length; i++)
if(shape.attachedConnectors[i].endItem === shape)
return shape.attachedConnectors[i].beginItem;
}
protected getParentItemKey(shape: Shape): ItemDataKey {
const parent = this.getParentItem(shape);
return parent && parent.dataKey;
}
protected getNodeArray(item: DataSourceNodeItem): any[] {
let items;
if(this.useNodeItems && item.parentDataObj)
items = this.nodeDataImporter.getItems(item.parentDataObj);
else if(item.containerDataObj)
items = this.nodeDataImporter.getChildren(item.containerDataObj);
return items || this.nodeDataSource;
}
protected getContainerShapeKey(shape: Shape): ItemDataKey {
return shape.container && shape.container.dataKey;
}
protected getChildItems(shape: Shape): DiagramItem[] {
const items = [];
for(let i = 0; i < shape.attachedConnectors.length; i++)
if(shape.attachedConnectors[i].beginItem === shape)
if(shape.attachedConnectors[i].endItem)
items.push(shape.attachedConnectors[i].endItem);
return items;
}
protected updateEdge(model: DiagramModel, connector: Connector): any {
const beginDataKey = connector.beginItem ? connector.beginItem.dataKey : undefined;
const endDataKey = connector.endItem ? connector.endItem.dataKey : undefined;
let edgeObj = this.findEdge(connector.dataKey);
if(!edgeObj) {
const dataObj = this.useEdgesArray() && this.canUpdateEdgeDataSource ? {} : undefined;
if(dataObj && connector.dataKey !== undefined && connector.dataKey !== null)
this.edgeDataImporter.setKey(dataObj, connector.dataKey);
edgeObj = this.addEdgeInternal(dataObj, beginDataKey, endDataKey);
if(dataObj) {
this.setDataObjectKeyRelatedProperty(this.edgeDataImporter.setKey, dataObj, edgeObj.key, this.addInternalKeyOnInsert);
this.edgeDataSource.push(edgeObj.dataObj);
}
this.updateEdgeObjectProperties(connector, edgeObj, model.units);
this.updateEdgeObjectKey(connector, edgeObj, edgeObj.dataObj);
if(dataObj) {
this.beginChangesNotification();
this.beginNodeInserting();
this.changesListener.notifyEdgeInserted.call(this.changesListener, edgeObj.dataObj,
(data: any) => {
this.updateEdgeObjectKey(connector, edgeObj, data);
this.endNodeInserting();
this.endChangesNotification(false);
},
(error: any) => {
this.endNodeInserting();
this.endChangesNotification(false);
}
);
}
}
else
if(this.isEdgeObjectModified(connector, edgeObj, model.units)) {
this.updateEdgeObjectProperties(connector, edgeObj, model.units);
if(edgeObj.dataObj) {
this.beginChangesNotification();
this.changesListener.notifyEdgeUpdated.call(this.changesListener,
this.edgeDataImporter.getKey(edgeObj.dataObj) || edgeObj.key, edgeObj.dataObj,
(key: ItemDataKey, data: any) => {
this.endChangesNotification(false);
},
(error: any) => {
this.endChangesNotification(false);
}
);
}
}
}
protected isEdgeObjectModified(connector: Connector, edgeObj: DataSourceEdgeItem, units: DiagramUnit): boolean {
return this.isItemObjectModified(connector, edgeObj, this.edgeDataImporter) ||
(edgeObj.from !== null ? edgeObj.from : undefined) !== (connector.beginItem ? connector.beginItem.dataKey : undefined) ||
(edgeObj.to === null ? undefined : edgeObj.to) !== (connector.endItem ? connector.endItem.dataKey : undefined) ||
(this.edgeDataImporter.setFromPointIndex && edgeObj.fromPointIndex !== connector.beginConnectionPointIndex) ||
(this.edgeDataImporter.setToPointIndex && edgeObj.toPointIndex !== connector.endConnectionPointIndex) ||
(this.edgeDataImporter.setPoints && (!edgeObj.points ||
!this.pointsAreEqual(edgeObj.points.map(ptObj => ptObj.x), connector.points.map(pt => ModelUtils.getlUnitValue(units, pt.x))) ||
!this.pointsAreEqual(edgeObj.points.map(ptObj => ptObj.y), connector.points.map(pt => ModelUtils.getlUnitValue(units, pt.y))))) ||
(this.edgeDataImporter.setText && !this.compareTexts(edgeObj, connector)) ||
(this.edgeDataImporter.setLineOption && edgeObj.lineOption !== connector.properties.lineOption) ||
(this.edgeDataImporter.setStartLineEnding && edgeObj.startLineEnding !== connector.properties.startLineEnding) ||
(this.edgeDataImporter.setEndLineEnding && edgeObj.endLineEnding !== connector.properties.endLineEnding);
}
pointsAreEqual(a: any[], b: any[]): boolean {
const aLen = a.length;
const bLen = a.length;
if(aLen !== bLen)
return false;
for(let i = 0; i < aLen; i++)
if(!MathUtils.numberCloseTo(a[i], b[i]))
return false;
return true;
}
protected updateEdgeObjectFromProperty(fromObj: DataSourceNodeItem, edgeObj: DataSourceEdgeItem,
changesListener?: IDataChangesListener) {
edgeObj.from = fromObj && fromObj.key;
if(edgeObj.dataObj) {
const fromKey = fromObj && fromObj.dataObj && this.nodeDataImporter.getKey(fromObj.dataObj);
this.setDataObjectKeyRelatedProperty(this.edgeDataImporter.setFrom, edgeObj.dataObj, fromKey, false);
if(changesListener)
if(this.isInUpdateNodeKeyRelatedObjects())
this.addToUpdateNodeKeyRelatedObjectsStackAction("edge", edgeObj);
else
this.updateEdgeObjectFromOrToPropertyInternal(edgeObj, changesListener);
}
}
protected updateEdgeObjectFromOrToPropertyInternal(edgeObj: DataSourceEdgeItem, changesListener: IDataChangesListener) {
this.beginChangesNotification();
changesListener.notifyEdgeUpdated.call(changesListener,
this.nodeDataImporter.getKey(edgeObj.dataObj) || edgeObj.key,
edgeObj.dataObj,
(key: ItemDataKey, data: any) => {
this.endChangesNotification(false);
},
(error: any) => {
this.endChangesNotification(false);
}
);
}
protected updateEdgeObjectToProperty(toObj: DataSourceNodeItem, edgeObj: DataSourceEdgeItem,
changesListener?: IDataChangesListener) {
edgeObj.to = toObj && toObj.key;
if(edgeObj.dataObj) {
const toKey = toObj && toObj.dataObj && this.nodeDataImporter.getKey(toObj.dataObj);
this.setDataObjectKeyRelatedProperty(this.edgeDataImporter.setTo, edgeObj.dataObj, toKey, false);
if(changesListener)
if(this.isInUpdateNodeKeyRelatedObjects())
this.addToUpdateNodeKeyRelatedObjectsStackAction("edge", edgeObj);
else
this.updateEdgeObjectFromOrToPropertyInternal(edgeObj, changesListener);
}
}
protected updateEdgeObjectProperties(connector: Connector, edgeObj: DataSourceEdgeItem, units: DiagramUnit) {
this.updateItemObjectProperties(edgeObj, connector, this.edgeDataImporter);
if(this.edgeDataImporter.setFrom) {
const fromObj = this.findNode(connector.beginItem && connector.beginItem.dataKey);
this.updateEdgeObjectFromProperty(fromObj, edgeObj);
}
if(this.edgeDataImporter.setTo) {
const toObj = this.findNode(connector.endItem && connector.endItem.dataKey);
this.updateEdgeObjectToProperty(toObj, edgeObj);
}
if(this.edgeDataImporter.setFromPointIndex) {
edgeObj.fromPointIndex = connector.beginConnectionPointIndex;
if(edgeObj.dataObj)
this.edgeDataImporter.setFromPointIndex(edgeObj.dataObj, connector.beginConnectionPointIndex);
}
if(this.edgeDataImporter.setToPointIndex) {
edgeObj.toPointIndex = connector.endConnectionPointIndex;
if(edgeObj.dataObj)
this.edgeDataImporter.setToPointIndex(edgeObj.dataObj, connector.endConnectionPointIndex);
}
if(this.edgeDataImporter.setPoints) {
const points = connector.points.map(pt => {
return {
x: ModelUtils.getlUnitValue(units, pt.x),
y: ModelUtils.getlUnitValue(units, pt.y)
};
});
edgeObj.points = points;
if(edgeObj.dataObj)
this.edgeDataImporter.setPoints(edgeObj.dataObj, points);
}
if(this.edgeDataImporter.setText) {
let text;
if(connector.getTextCount() === 1 && connector.getText())
text = connector.getText();
const texts = {};
connector.texts.forEach(text => {
texts[text.position] = text.value;
});
edgeObj.texts = texts;
if(edgeObj.dataObj) {
let textVal: any = "";
if(text)
textVal = text;
else if(texts && Object.keys(texts).length)
textVal = texts;
this.edgeDataImporter.setText(edgeObj.dataObj, textVal);
}
}
if(this.edgeDataImporter.setLineOption) {
edgeObj.lineOption = connector.properties.lineOption;
if(edgeObj.dataObj)
this.edgeDataImporter.setLineOption(edgeObj.dataObj, connector.properties.lineOption);
}
if(this.edgeDataImporter.setStartLineEnding) {
edgeObj.startLineEnding = connector.properties.startLineEnding;
if(edgeObj.dataObj)
this.edgeDataImporter.setStartLineEnding(edgeObj.dataObj, connector.properties.startLineEnding);
}
if(this.edgeDataImporter.setEndLineEnding) {
edgeObj.endLineEnding = connector.properties.endLineEnding;
if(edgeObj.dataObj)
this.edgeDataImporter.setEndLineEnding(edgeObj.dataObj, connector.properties.endLineEnding);
}
}
protected updateEdgeObjectKey(connector: Connector, edgeObj: DataSourceEdgeItem, dataObj: any) {
const key = dataObj && this.edgeDataImporter.getKey(dataObj);
if(key !== undefined && key !== null && key !== edgeObj.key) {
delete this.autoGeneratedDataKeys[edgeObj.key];
edgeObj.key = key;
}
connector.dataKey = edgeObj.key;
if(edgeObj.dataObj !== dataObj) {
const parentArray = this.edgeDataSource;
const index = parentArray.indexOf(edgeObj.dataObj);
parentArray.splice(index, 1, dataObj);
edgeObj.dataObj = dataObj;
}
}
protected deleteEdges(model: DiagramModel) {
this.deleteItems(this.edges,
key => model.findConnectorByDataKey(key),
item => this.edgeDataSource,
(item: DataSourceItem, dataModified: boolean) => {
const key = (item.dataObj && this.edgeDataImporter.getKey(item.dataObj)) || item.key;
const edgeObj = this.findEdge(key);
if(edgeObj)
this.edges.splice(this.edges.indexOf(edgeObj), 1);
if(dataModified) {
this.beginChangesNotification();
this.changesListener.notifyEdgeRemoved.call(this.changesListener, key, item.dataObj,
(key: ItemDataKey, data: any) => {
this.endChangesNotification(false);
},
(error: any) => {
this.endChangesNotification(false);
}
);
}
}
);
}
beginNodeInserting() {
this.nodeInsertingLockCount++;
}
endNodeInserting() {
this.nodeInsertingLockCount--;
if(this.nodeInsertingLockCount === 0)
this.raiseNodeInsertingStack();
}
isInNodeInserting(): boolean {
return this.nodeInsertingLockCount > 0;
}
addToUpdateNodeKeyRelatedObjectsStack(shape: Shape, nodeObj: DataSourceNodeItem) {
const item = new UpdateNodeKeyRelatedObjectsStackItem(shape, nodeObj);
this.updateNodeKeyRelatedObjectsStack.push(item);
}
raiseNodeInsertingStack() {
this.beginUpdateNodeKeyRelatedObjects();
while(this.updateNodeKeyRelatedObjectsStack.length > 0) {
const item = this.updateNodeKeyRelatedObjectsStack[0];
this.updateNodeKeyRelatedObjects(item.shape, item.nodeObj);
this.updateNodeKeyRelatedObjectsStack.splice(0, 1);
if(item.shape.description.hasTemplate && item.nodeObj)
this.changesListener.reloadInsertedItem(item.nodeObj.key);
}
this.endUpdateNodeKeyRelatedObjects();
}
beginUpdateNodeKeyRelatedObjects() {
this.updateNodeKeyRelatedObjectsCount++;
}
endUpdateNodeKeyRelatedObjects() {
this.updateNodeKeyRelatedObjectsCount--;
if(this.updateNodeKeyRelatedObjectsCount === 0)
this.raiseUpdateNodeKeyRelatedObjectsStack();
}
isInUpdateNodeKeyRelatedObjects(): boolean {
return this.updateNodeKeyRelatedObjectsCount > 0;
}
addToUpdateNodeKeyRelatedObjectsStackAction(kind: string, nodeObj: DataSourceNodeItem | DataSourceEdgeItem) {
const item = new UpdateNodeKeyRelatedObjectsStackAction(kind, nodeObj);
for(let i = 0; i < this.updateNodeKeyRelatedObjectsStackActions.length; i++)
if((this.updateNodeKeyRelatedObjectsStackActions[i].kind === kind) && (this.updateNodeKeyRelatedObjectsStackActions[i].nodeObj === nodeObj))
return;
this.updateNodeKeyRelatedObjectsStackActions.push(item);
}
raiseUpdateNodeKeyRelatedObjectsStack() {
while(this.updateNodeKeyRelatedObjectsStackActions.length > 0) {
const item = this.updateNodeKeyRelatedObjectsStackActions[0];
switch(item.kind) {
case "shape":
this.updateNodeObjectContainerOrParentKeyInternal(item.nodeObj, this.changesListener);
break;
case "edge":
this.updateEdgeObjectFromOrToPropertyInternal(item.nodeObj, this.changesListener);
break;
}
this.updateNodeKeyRelatedObjectsStackActions.splice(0, 1);
}
}
protected beginChangesNotification() : void {
this.changesListener.beginChangesNotification();
}
protected endChangesNotification(preventNotifyChanges : boolean) : void {
this.changesListener.endChangesNotification(preventNotifyChanges);
}
}