UNPKG

devexpress-diagram

Version:

DevExpress Diagram Control

769 lines (733 loc) 38.8 kB
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); } }