7 RelationMenuDirectiveObj,
13 import {ComponentInstanceFactory, ComponentFactory, GRAPH_EVENTS, GraphColors} from "app/utils";
14 import {EventListenerService, LoaderService} from "app/services";
15 import {CompositionGraphLinkUtils} from "./utils/composition-graph-links-utils";
16 import {CompositionGraphGeneralUtils} from "./utils/composition-graph-general-utils";
17 import {CompositionGraphNodesUtils} from "./utils/composition-graph-nodes-utils";
18 import {CommonGraphUtils} from "../common/common-graph-utils";
19 import {MatchCapabilitiesRequirementsUtils} from "./utils/match-capability-requierment-utils";
20 import {CompositionGraphPaletteUtils} from "./utils/composition-graph-palette-utils";
21 import {ComponentInstanceNodesStyle} from "../common/style/component-instances-nodes-style";
22 import {CytoscapeEdgeEditation} from 'third-party/cytoscape.js-edge-editation/CytoscapeEdgeEditation.js';
23 import {ComponentServiceNg2} from "../../../ng2/services/component-services/component.service";
24 import {ComponentGenericResponse} from "../../../ng2/services/responses/component-generic-response";
26 interface ICompositionGraphScope extends ng.IScope {
31 // Link menu - create link menu
32 relationMenuDirectiveObj:RelationMenuDirectiveObj;
33 isLinkMenuOpen:boolean;
34 createLinkFromMenu:(chosenMatch:MatchBase, vl:Component)=>void;
36 //modify link menu - for now only delete menu
37 relationMenuTimeout:ng.IPromise<any>;
38 linkMenuObject:LinkMenu;
40 //left palette functions callbacks
41 dropCallback(event:JQueryEventObject, ui:any):void;
42 beforeDropCallback(event:IDragDropEvent):void;
43 verifyDrop(event:JQueryEventObject, ui:any):void;
46 deleteRelation(link:Cy.CollectionEdges):void;
48 /*//asset popover menu
49 assetPopoverObj:AssetPopoverObj;
50 assetPopoverOpen:boolean;
51 hideAssetPopover():void;
52 deleteNode(nodeId:string):void;*/
55 export class CompositionGraph implements ng.IDirective {
56 private _cy:Cy.Instance;
57 private _currentlyCLickedNodePosition:Cy.Position;
58 // private $document:JQuery = $(document);
59 private dragElement:JQuery;
60 private dragComponent:ComponentInstance;
62 constructor(private $q:ng.IQService,
63 private $log:ng.ILogService,
64 private $timeout:ng.ITimeoutService,
65 private NodesFactory:NodesFactory,
66 private CompositionGraphLinkUtils:CompositionGraphLinkUtils,
67 private GeneralGraphUtils:CompositionGraphGeneralUtils,
68 private ComponentInstanceFactory:ComponentInstanceFactory,
69 private NodesGraphUtils:CompositionGraphNodesUtils,
70 private eventListenerService:EventListenerService,
71 private ComponentFactory:ComponentFactory,
72 private LoaderService:LoaderService,
73 private commonGraphUtils:CommonGraphUtils,
74 private matchCapabilitiesRequirementsUtils:MatchCapabilitiesRequirementsUtils,
75 private CompositionGraphPaletteUtils:CompositionGraphPaletteUtils,
76 private ComponentServiceNg2: ComponentServiceNg2) {
81 template = require('./composition-graph.html');
87 link = (scope:ICompositionGraphScope, el:JQuery) => {
89 this.loadGraph(scope, el);
91 if(scope.component.componentInstances && scope.component.componentInstancesRelations) {
92 this.loadGraphData(scope);
94 //when we don't have the data we register to on graph load event
95 this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_COMPOSITION_GRAPH_DATA_LOADED, () => {
96 this.loadGraphData(scope);
99 scope.$on('$destroy', () => {
101 _.forEach(GRAPH_EVENTS, (event) => {
102 this.eventListenerService.unRegisterObserver(event);
108 private loadGraphData = (scope:ICompositionGraphScope) => {
109 this.initGraphNodes(scope.component.componentInstances, scope.isViewOnly);
110 this.commonGraphUtils.initGraphLinks(this._cy, scope.component.componentInstancesRelations);
111 this.commonGraphUtils.initUcpeChildren(this._cy);
114 private loadGraph = (scope:ICompositionGraphScope, el:JQuery) => {
116 let graphEl = el.find('.sdc-composition-graph-wrapper');
117 this.initGraph(graphEl, scope.isViewOnly);
118 this.initDropZone(scope);
119 this.registerCytoscapeGraphEvents(scope);
120 this.registerCustomEvents(scope, el);
121 this.initViewMode(scope.isViewOnly);
125 private initGraph(graphEl:JQuery, isViewOnly:boolean) {
127 this._cy = cytoscape({
129 style: ComponentInstanceNodesStyle.getCompositionGraphStyle(),
130 zoomingEnabled: false,
131 selectionType: 'single',
132 boxSelectionEnabled: true,
133 autolock: isViewOnly,
134 autoungrabify: isViewOnly
138 private initViewMode(isViewOnly:boolean) {
141 //remove event listeners
142 this._cy.off('drag');
143 this._cy.off('handlemouseout');
144 this._cy.off('handlemouseover');
145 this._cy.edges().unselectify();
149 private registerCustomEvents(scope:ICompositionGraphScope, el:JQuery) {
151 this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_PALETTE_COMPONENT_HOVER_IN, (leftPaletteComponent:LeftPaletteComponent) => {
152 this.$log.info(`composition-graph::registerEventServiceEvents:: palette hover on component: ${leftPaletteComponent.uniqueId}`);
154 let nodesData = this.NodesGraphUtils.getAllNodesData(this._cy.nodes());
155 let nodesLinks = this.GeneralGraphUtils.getAllCompositionCiLinks(this._cy);
157 if (this.GeneralGraphUtils.componentRequirementsAndCapabilitiesCaching.containsKey(leftPaletteComponent.uniqueId)) {
158 let cacheComponent = this.GeneralGraphUtils.componentRequirementsAndCapabilitiesCaching.getValue(leftPaletteComponent.uniqueId);
159 let filteredNodesData = this.matchCapabilitiesRequirementsUtils.findByMatchingCapabilitiesToRequirements(cacheComponent, nodesData, nodesLinks);
161 this.matchCapabilitiesRequirementsUtils.highlightMatchingComponents(filteredNodesData, this._cy);
162 this.matchCapabilitiesRequirementsUtils.fadeNonMachingComponents(filteredNodesData, nodesData, this._cy);
167 //----------------------- ORIT TO FIX------------------------//
169 this.ComponentServiceNg2.getCapabilitiesAndRequirements(leftPaletteComponent.componentType, leftPaletteComponent.uniqueId).subscribe((response: ComponentGenericResponse) => {
171 let component = this.ComponentFactory.createEmptyComponent(leftPaletteComponent.componentType);
172 component.uniqueId = component.uniqueId;
173 component.capabilities = response.capabilities;
174 component.requirements = response.requirements;
175 this.GeneralGraphUtils.componentRequirementsAndCapabilitiesCaching.setValue(leftPaletteComponent.uniqueId, component);
176 let filteredNodesData = this.matchCapabilitiesRequirementsUtils.findByMatchingCapabilitiesToRequirements(component, nodesData, nodesLinks);
177 this.matchCapabilitiesRequirementsUtils.fadeNonMachingComponents(filteredNodesData, nodesData, this._cy);
178 this.matchCapabilitiesRequirementsUtils.highlightMatchingComponents(filteredNodesData, this._cy)
182 this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_PALETTE_COMPONENT_HOVER_OUT, () => {
183 this._cy.emit('hidehandles');
184 this.matchCapabilitiesRequirementsUtils.resetFadedNodes(this._cy);
187 this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_PALETTE_COMPONENT_DRAG_START, (dragElement, dragComponent) => {
189 this.dragElement = dragElement;
190 this.dragComponent = this.ComponentInstanceFactory.createComponentInstanceFromComponent(dragComponent);
193 this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_PALETTE_COMPONENT_DRAG_ACTION, (event:IDragDropEvent) => {
194 this.CompositionGraphPaletteUtils.onComponentDrag(this._cy, event, this.dragElement, this.dragComponent);
198 this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_COMPONENT_INSTANCE_NAME_CHANGED, (component:ComponentInstance) => {
200 let selectedNode = this._cy.getElementById(component.uniqueId);
201 selectedNode.data().componentInstance.name = component.name;
202 selectedNode.data('name', component.name); //used for tooltip
203 selectedNode.data('displayName', selectedNode.data().getDisplayName()); //abbreviated
207 this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_DELETE_COMPONENT_INSTANCE, (componentInstance:ComponentInstance) => {
208 let nodeToDelete = this._cy.getElementById(componentInstance.uniqueId);
209 this.NodesGraphUtils.deleteNode(this._cy, scope.component, nodeToDelete);
212 this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_DELETE_MULTIPLE_COMPONENTS, () => {
214 this._cy.$('node:selected').each((i:number, node:Cy.CollectionNodes) => {
215 this.NodesGraphUtils.deleteNode(this._cy, scope.component, node);
220 this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_DELETE_EDGE, (releaseLoading:boolean, linksToDelete:Cy.CollectionEdges) => {
221 this.CompositionGraphLinkUtils.deleteLink(this._cy, scope.component, releaseLoading, linksToDelete);
224 this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_INSERT_NODE_TO_UCPE, (node:Cy.CollectionNodes, ucpe:Cy.CollectionNodes, updateExistingNode:boolean) => {
226 this.commonGraphUtils.initUcpeChildData(node, ucpe);
227 //check if item is a VL, and if so, skip adding the binding to ucpe
228 if (!(node.data() instanceof CompositionCiNodeVl)) {
229 this.CompositionGraphLinkUtils.createVfToUcpeLink(scope.component, this._cy, ucpe.data(), node.data()); //create link from the node to the ucpe
232 if (updateExistingNode) {
233 let vlsPendingDeletion:Cy.CollectionNodes = this.NodesGraphUtils.deleteNodeVLsUponMoveToOrFromUCPE(scope.component, node.cy(), node); //delete connected VLs that no longer have 2 links
234 this.CompositionGraphLinkUtils.deleteLinksWhenNodeMovedFromOrToUCPE(scope.component, node.cy(), node, vlsPendingDeletion); //delete all connected links if needed
235 this.GeneralGraphUtils.pushUpdateComponentInstanceActionToQueue(scope.component, true, node.data().componentInstance); //update componentInstance position
240 this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_REMOVE_NODE_FROM_UCPE, (node:Cy.CollectionNodes, ucpe:Cy.CollectionNodes) => {
241 this.commonGraphUtils.removeUcpeChildData(node);
242 let vlsPendingDeletion:Cy.CollectionNodes = this.NodesGraphUtils.deleteNodeVLsUponMoveToOrFromUCPE(scope.component, node.cy(), node);
243 this.CompositionGraphLinkUtils.deleteLinksWhenNodeMovedFromOrToUCPE(scope.component, node.cy(), node, vlsPendingDeletion); //delete all connected links if needed
244 this.GeneralGraphUtils.pushUpdateComponentInstanceActionToQueue(scope.component, true, node.data().componentInstance); //update componentInstance position
247 this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_VERSION_CHANGED, (component:Component) => {
248 scope.component = component;
249 this._cy.elements().remove();
250 this.loadGraphData(scope);
254 scope.createLinkFromMenu = (chosenMatch:MatchBase):void => {
255 scope.isLinkMenuOpen = false;
256 this.CompositionGraphLinkUtils.createLinkFromMenu(this._cy, chosenMatch, scope.component);
259 scope.hideRelationMenu = () => {
260 this.commonGraphUtils.safeApply(scope, () => {
261 scope.linkMenuObject = null;
262 this.$timeout.cancel(scope.relationMenuTimeout);
267 scope.deleteRelation = (link:Cy.CollectionEdges) => {
268 scope.hideRelationMenu();
270 //if multiple edges selected, delete the VL itself so edges get deleted automatically
271 if (this._cy.$('edge:selected').length > 1) {
272 this.NodesGraphUtils.deleteNode(this._cy, scope.component, this._cy.$('node:selected'));
274 this.CompositionGraphLinkUtils.deleteLink(this._cy, scope.component, true, link);
279 scope.hideAssetPopover = ():void => {
281 this.commonGraphUtils.safeApply(scope, () => {
282 scope.assetPopoverOpen = false;
283 scope.assetPopoverObj = null;
287 scope.deleteNode = (nodeId:string):void => {
288 if (!scope.isViewOnly) {
289 this.NodesGraphUtils.confirmDeleteNode(nodeId, this._cy, scope.component);
290 //scope.hideAssetPopover();
295 private registerCytoscapeGraphEvents(scope:ICompositionGraphScope) {
297 this._cy.on('addedgemouseup', (event, data) => {
298 scope.relationMenuDirectiveObj = this.CompositionGraphLinkUtils.onLinkDrawn(this._cy, data.source, data.target);
299 if (scope.relationMenuDirectiveObj != null) {
301 scope.isLinkMenuOpen = true;
305 this._cy.on('tapstart', 'node', (event:Cy.EventObject) => {
306 this._currentlyCLickedNodePosition = angular.copy(event.cyTarget[0].position()); //update node position on drag
307 if (event.cyTarget.data().isUcpe) {
308 this._cy.nodes('.ucpe-cp').unlock();
309 event.cyTarget.style('opacity', 0.5);
311 //scope.hideAssetPopover();
314 this._cy.on('drag', 'node', (event:Cy.EventObject) => {
316 if (event.cyTarget.data().isDraggable) {
317 event.cyTarget.style({'overlay-opacity': 0.24});
318 if (this.GeneralGraphUtils.isValidDrop(this._cy, event.cyTarget)) {
319 event.cyTarget.style({'overlay-color': GraphColors.NODE_BACKGROUND_COLOR});
321 event.cyTarget.style({'overlay-color': GraphColors.NODE_OVERLAPPING_BACKGROUND_COLOR});
325 if (event.cyTarget.data().isUcpe) {
326 let pos = event.cyTarget.position();
328 this._cy.nodes('[?isInsideGroup]').positions((i, node)=> {
330 x: pos.x + node.data("ucpeOffset").x,
331 y: pos.y + node.data("ucpeOffset").y
337 /* this._cy.on('mouseover', 'node', (event:Cy.EventObject) => {
338 if (!this._cy.scratch('_edge_editation_highlights')) {
339 this.commonGraphUtils.safeApply(scope, () => {
340 this.showNodePopoverMenu(scope, event.cyTarget[0]);
345 this._cy.on('mouseout', 'node', (event:Cy.EventObject) => {
346 scope.hideAssetPopover();
348 this._cy.on('handlemouseover', (event, payload) => {
350 if (payload.node.grabbed() /* || this._cy.scratch('_edge_editation_highlights') === true*/) { //no need to add opacity while we are dragging and hovering othe nodes- or if opacity was already calculated for these nodes
353 let nodesData = this.NodesGraphUtils.getAllNodesData(this._cy.nodes());
354 let nodesLinks = this.GeneralGraphUtils.getAllCompositionCiLinks(this._cy);
356 let linkableNodes = this.commonGraphUtils.getLinkableNodes(this._cy, payload.node);
357 let filteredNodesData = this.matchCapabilitiesRequirementsUtils.findByMatchingCapabilitiesToRequirements(payload.node.data().componentInstance, linkableNodes, nodesLinks);
358 this.matchCapabilitiesRequirementsUtils.highlightMatchingComponents(filteredNodesData, this._cy);
359 this.matchCapabilitiesRequirementsUtils.fadeNonMachingComponents(filteredNodesData, nodesData, this._cy, payload.node.data());
361 this._cy.scratch()._edge_editation_highlights = true;
362 scope.hideAssetPopover();*/
365 this._cy.on('handlemouseout', () => {
366 if (this._cy.scratch('_edge_editation_highlights') === true) {
367 this._cy.removeScratch('_edge_editation_highlights');
368 this._cy.emit('hidehandles');
369 this.matchCapabilitiesRequirementsUtils.resetFadedNodes(this._cy);
374 this._cy.on('tapend', (event:Cy.EventObject) => {
376 if (event.cyTarget === this._cy) { //On Background clicked
377 if (this._cy.$('node:selected').length === 0) { //if the background click but not dragged
378 this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_GRAPH_BACKGROUND_CLICKED);
380 scope.hideRelationMenu();
383 else if (event.cyTarget.isEdge()) { //On Edge clicked
384 if (scope.isViewOnly) return;
385 this.CompositionGraphLinkUtils.handleLinkClick(this._cy, event);
386 this.openModifyLinkMenu(scope, this.CompositionGraphLinkUtils.getModifyLinkMenu(event.cyTarget[0], event), 6000);
389 else { //On Node clicked
390 this._cy.nodes(':grabbed').style({'overlay-opacity': 0});
392 let isUcpe:boolean = event.cyTarget.data().isUcpe;
393 let newPosition = event.cyTarget[0].position();
394 //node position changed (drop after drag event) - we need to update position
395 if (this._currentlyCLickedNodePosition.x !== newPosition.x || this._currentlyCLickedNodePosition.y !== newPosition.y) {
396 let nodesMoved:Cy.CollectionNodes = this._cy.$(':grabbed');
398 nodesMoved = nodesMoved.add(this._cy.nodes('[?isInsideGroup]:free')); //'child' nodes will not be recognized as "grabbed" elements within cytoscape. manually add them to collection of nodes moved.
400 this.NodesGraphUtils.onNodesPositionChanged(this._cy, scope.component, nodesMoved);
402 this.$log.debug('composition-graph::onNodeSelectedEvent:: fired');
404 this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_NODE_SELECTED, event.cyTarget.data().componentInstance);
405 //open node popover menu
406 //this.showNodePopoverMenu(scope, event.cyTarget[0]);
411 this._cy.nodes('.ucpe-cp').lock();
412 event.cyTarget.style('opacity', 1);
418 this._cy.on('boxselect', 'node', (event:Cy.EventObject) => {
419 this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_NODE_SELECTED, event.cyTarget.data().componentInstance);
424 private showNodePopoverMenu = (scope:ICompositionGraphScope, node:Cy.CollectionNodes) => {
426 scope.assetPopoverObj = this.NodesGraphUtils.createAssetPopover(this._cy, node, scope.isViewOnly);
427 scope.assetPopoverOpen = true;
430 private openModifyLinkMenu = (scope:ICompositionGraphScope, linkMenuObject:LinkMenu, timeOutInMilliseconds?:number) => {
432 this.commonGraphUtils.safeApply(scope, () => {
433 scope.linkMenuObject = linkMenuObject;
436 scope.relationMenuTimeout = this.$timeout(() => {
437 scope.hideRelationMenu();
438 }, timeOutInMilliseconds ? timeOutInMilliseconds : 6000);
441 private initGraphNodes(componentInstances:ComponentInstance[], isViewOnly:boolean) {
443 if (!isViewOnly) { //Init nodes handle extension - enable dynamic links
445 let handles = new CytoscapeEdgeEditation;
446 handles.init(this._cy, 18);
447 handles.registerHandle(ComponentInstanceNodesStyle.getBasicNodeHanlde());
448 handles.registerHandle(ComponentInstanceNodesStyle.getBasicSmallNodeHandle());
449 handles.registerHandle(ComponentInstanceNodesStyle.getUcpeCpNodeHandle());
453 _.each(componentInstances, (instance) => {
454 let compositionGraphNode:CompositionCiNodeBase = this.NodesFactory.createNode(instance);
455 this.commonGraphUtils.addComponentInstanceNodeToGraph(this._cy, compositionGraphNode);
460 private initDropZone(scope:ICompositionGraphScope) {
462 if (scope.isViewOnly) {
465 scope.dropCallback = (event:IDragDropEvent) => {
466 this.$log.debug(`composition-graph::dropCallback:: fired`);
467 this.CompositionGraphPaletteUtils.addNodeFromPalette(this._cy, event, scope.component);
470 scope.verifyDrop = (event:JQueryEventObject) => {
472 if (this.dragElement.hasClass('red')) {
478 scope.beforeDropCallback = (event:IDragDropEvent):ng.IPromise<void> => {
479 let deferred:ng.IDeferred<void> = this.$q.defer<void>();
480 if (this.dragElement.hasClass('red')) {
486 return deferred.promise;
490 public static factory = ($q,
496 ComponentInstanceFactory,
498 EventListenerService,
502 MatchCapabilitiesRequirementsUtils,
503 CompositionGraphPaletteUtils,
504 ComponentServiceNg2) => {
505 return new CompositionGraph(
512 ComponentInstanceFactory,
514 EventListenerService,
518 MatchCapabilitiesRequirementsUtils,
519 CompositionGraphPaletteUtils,
520 ComponentServiceNg2);
524 CompositionGraph.factory.$inject = [
529 'CompositionGraphLinkUtils',
530 'CompositionGraphGeneralUtils',
531 'ComponentInstanceFactory',
532 'CompositionGraphNodesUtils',
533 'EventListenerService',
537 'MatchCapabilitiesRequirementsUtils',
538 'CompositionGraphPaletteUtils',
539 'ComponentServiceNg2'