diff options
Diffstat (limited to 'catalog-ui/src/app/directives/graphs-v2')
5 files changed, 298 insertions, 6 deletions
diff --git a/catalog-ui/src/app/directives/graphs-v2/composition-graph/composition-graph.directive.ts b/catalog-ui/src/app/directives/graphs-v2/composition-graph/composition-graph.directive.ts index 804e772ac4..a6af9a8110 100644 --- a/catalog-ui/src/app/directives/graphs-v2/composition-graph/composition-graph.directive.ts +++ b/catalog-ui/src/app/directives/graphs-v2/composition-graph/composition-graph.directive.ts @@ -35,7 +35,7 @@ import { NodesFactory, Point } from "app/models"; -import { ComponentInstanceFactory, ComponentFactory, GRAPH_EVENTS, GraphColors } from "app/utils"; +import { ComponentInstanceFactory, ComponentFactory, GRAPH_EVENTS, GraphColors, GraphUIObjects, ModalsHandler } from "app/utils"; import { EventListenerService, LoaderService } from "app/services"; import { CompositionGraphLinkUtils } from "./utils/composition-graph-links-utils"; import { CompositionGraphGeneralUtils } from "./utils/composition-graph-general-utils"; @@ -127,6 +127,11 @@ export interface ICompositionGraphScope extends ng.IScope { deletePathsOnCy(): void; drawPathOnCy(data: ForwardingPath): void; selectedPathId: string; + + copyComponentInstance(): void; + pasteComponentInstance(event: IDragDropEvent): void; + + origComponentId: string; } export class CompositionGraph implements ng.IDirective { @@ -134,6 +139,7 @@ export class CompositionGraph implements ng.IDirective { private _currentlyCLickedNodePosition: Cy.Position; private dragElement: JQuery; private dragComponent: ComponentInstance; + private cyBackgroundClickEvent: any; constructor(private $q: ng.IQService, private $log: ng.ILogService, @@ -154,7 +160,8 @@ export class CompositionGraph implements ng.IDirective { private ModalServiceNg2: ModalService, private ConnectionWizardServiceNg2: ConnectionWizardService, private ComponentInstanceServiceNg2: ComponentInstanceServiceNg2, - private servicePathGraphUtils: ServicePathGraphUtils) { + private servicePathGraphUtils: ServicePathGraphUtils, + private ModalsHandler: ModalsHandler) { } @@ -531,8 +538,135 @@ export class CompositionGraph implements ng.IDirective { this.CompositionGraphLinkUtils.deleteLink(this._cy, scope.component, true, link); } }; + + + document.onkeydown = (event) => { + let isModalExist = document.getElementsByTagName('body')[0].classList.contains('modal-open'); + + if (!scope.isViewOnly && !isModalExist) { + + switch (event.keyCode) { + + case 46: //delete + scope.deleteSelectedElements(); + break; + + case 67: //ctrl+c : copy componentInstance + if (event.ctrlKey && scope.component.isService()) { + scope.copyComponentInstance(); + } + break; + case 86: // ctrl+v: paste componentInstance + if (event.ctrlKey && scope.component.isService()) { + let hidePasteObj = $(".w-canvas-content-paste"); + hidePasteObj.click(); + } + break; + + } + } + }; + + scope.deleteSelectedElements = (): void => { + if (this._cy) { + let nodesToDelete = this._cy.$('node:selected'); + let edgesSelected = this._cy.$('edge:selected'); + if (nodesToDelete.length + edgesSelected.length <= 0) { + return; + } + let componentInstancetobeDele; + let title: string = "Delete Confirmation"; + let message: string = "Are you sure you would like to delete selected elements?"; + if (nodesToDelete.size() == 1 && edgesSelected.size() == 0) { + + componentInstancetobeDele = nodesToDelete[0].data().componentInstance; + let showName = nodesToDelete[0].data().componentInstance.name; + message = "Are you sure you would like to delete" + " " + showName + "?"; + } + + let onOk = (): void => { + if (nodesToDelete.size() == 1 && edgesSelected.size() == 0) { + this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_DELETE_COMPONENT_INSTANCE, componentInstancetobeDele); + } + else { + this.NodesGraphUtils.batchDeleteNodes(this._cy, scope.component, nodesToDelete); + } + + }; + + this.ModalsHandler.openConfirmationModal(title, message, false).then(onOk); + } + + }; + + scope.copyComponentInstance = (): void => { + scope.origComponentId = scope.component.uniqueId; + if (scope.component.selectedInstance) { + scope.origSelectedInstance = scope.component.selectedInstance; + } + }; + + + scope.pasteComponentInstance = (event: IDragDropEvent): void => { + event.clientX = event.clientX ? event.clientX : this.cyBackgroundClickEvent ? this.cyBackgroundClickEvent.originalEvent.clientX : null; + event.clientY = event.clientY ? event.clientY : this.cyBackgroundClickEvent ? this.cyBackgroundClickEvent.originalEvent.clientY : null; + + if (event.clientX == null || event.clientY == null) { + return; + } + let offsetPosition = { + x: event.clientX - GraphUIObjects.DIAGRAM_PALETTE_WIDTH_OFFSET, + y: event.clientY - GraphUIObjects.DIAGRAM_HEADER_OFFSET + }; + + let mousePosition = this.commonGraphUtils.HTMLCoordsToCytoscapeCoords(this._cy.extent(), offsetPosition); + let newPositionX = mousePosition.x; + let newPositionY = mousePosition.y; + let origSelectedInstance = scope.origSelectedInstance; + + let copyComponentInstance = `{ + "posX":"${newPositionX}", + "posY":"${newPositionY}", + "name":"${origSelectedInstance.componentName}", + "componentVersion":"${origSelectedInstance.componentVersion}", + "originType":"${origSelectedInstance.originType}", + "icon":"${origSelectedInstance.icon}", + "componentUid":"${origSelectedInstance.componentUid}" + }`; + + this.isComponentPasteValid(scope, this._cy, event, offsetPosition, origSelectedInstance); + + + let onSuccess = (response): void => { + let success = (component: Component) => { + scope.component.componentInstances = component.componentInstances; + if (component.isService() && component.componentInstances.length > 0) { + _.each(component.componentInstances, (instance) => { + if (instance.uniqueId == response.componentInstance.uniqueId) { + let compositionGraphNode: CompositionCiNodeBase = this.NodesFactory.createNode(instance); + this.commonGraphUtils.addComponentInstanceNodeToGraph(this._cy, compositionGraphNode); + } + }); + } + scope.isLoading = false; + }; + scope.component.getComponent().then(success); + }; + let onFailed = (error: any): void => { + console.log(error); + scope.isLoading = false; + }; + + if (scope.pasteValid) { + scope.isLoading = true; + scope.component.pasteMenuComponentInstance(origSelectedInstance.uniqueId, copyComponentInstance).then(onSuccess, onFailed); + } + }; + } + + private registerCytoscapeGraphEvents(scope: ICompositionGraphScope) { this._cy.on('addedgemouseup', (event, data) => { @@ -633,6 +767,7 @@ export class CompositionGraph implements ng.IDirective { } this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_GRAPH_BACKGROUND_CLICKED); } + this.cyBackgroundClickEvent = event; scope.hideRelationMenu(); } @@ -752,7 +887,6 @@ export class CompositionGraph implements ng.IDirective { }); } - private initDropZone(scope: ICompositionGraphScope) { if (scope.isViewOnly) { @@ -866,7 +1000,30 @@ export class CompositionGraph implements ng.IDirective { } }; }; + private isComponentPasteValid(scope: ICompositionGraphScope, cy: Cy.Instance, event: IDragDropEvent, offsetPosition: Cy.Position, origSelectedInstance: ComponentInstance) { + let bbox = this._getNodeBBox(cy, event, origSelectedInstance, offsetPosition); + if (this.GeneralGraphUtils.isPaletteDropValid(cy, bbox, origSelectedInstance)) { + scope.pasteValid = true; + } else { + scope.pasteValid = false; + } + } + + private _getNodeBBox(cy: Cy.Instance, event: IDragDropEvent, origSelectedInstance: ComponentInstance, position?: Cy.Position) { + let bbox = <Cy.BoundingBox>{}; + if (!position) { + position = this.commonGraphUtils.getCytoscapeNodePosition(cy, event); + } + let cushionWidth: number = 40; + let cushionHeight: number = 40; + + bbox.x1 = position.x - cushionWidth / 2; + bbox.y1 = position.y - cushionHeight / 2; + bbox.x2 = position.x + cushionWidth / 2; + bbox.y2 = position.y + cushionHeight / 2; + return bbox; + } public static factory = ($q, $log, @@ -887,7 +1044,8 @@ export class CompositionGraph implements ng.IDirective { ModalService, ConnectionWizardService, ComponentInstanceServiceNg2, - ServicePathGraphUtils) => { + ServicePathGraphUtils, + ModalsHandler) => { return new CompositionGraph( $q, $log, @@ -908,7 +1066,8 @@ export class CompositionGraph implements ng.IDirective { ModalService, ConnectionWizardService, ComponentInstanceServiceNg2, - ServicePathGraphUtils); + ServicePathGraphUtils, + ModalsHandler); } } @@ -932,5 +1091,6 @@ CompositionGraph.factory.$inject = [ 'ModalServiceNg2', 'ConnectionWizardServiceNg2', 'ComponentInstanceServiceNg2', - 'ServicePathGraphUtils' + 'ServicePathGraphUtils', + 'ModalsHandler' ]; diff --git a/catalog-ui/src/app/directives/graphs-v2/composition-graph/composition-graph.html b/catalog-ui/src/app/directives/graphs-v2/composition-graph/composition-graph.html index b473f44628..e264381a99 100644 --- a/catalog-ui/src/app/directives/graphs-v2/composition-graph/composition-graph.html +++ b/catalog-ui/src/app/directives/graphs-v2/composition-graph/composition-graph.html @@ -26,6 +26,9 @@ create-relation="createLinkFromMenu" cancel="cancelRelationMenu()"></relation-menu> --> <menu-list-ng2 [props]="canvasMenuProps"></menu-list-ng2> +<div id="cyMenu" class="hidePaste"> + <div class="w-canvas-content-paste" data-ng-click="pasteComponentInstance($event)">paste</div> +</div> <div class="w-sdc-search-menu" data-ng-class="{'with-sidebar': withSidebar}"> diff --git a/catalog-ui/src/app/directives/graphs-v2/composition-graph/composition-graph.less b/catalog-ui/src/app/directives/graphs-v2/composition-graph/composition-graph.less index 7124a4b5a6..a310cd447e 100644 --- a/catalog-ui/src/app/directives/graphs-v2/composition-graph/composition-graph.less +++ b/catalog-ui/src/app/directives/graphs-v2/composition-graph/composition-graph.less @@ -16,6 +16,10 @@ composition-graph { background-color:rgb(248, 248, 248); } + .hidePaste { + display:none; + } + .sdc-canvas-zones__wrapper { position: absolute; bottom: 10px; diff --git a/catalog-ui/src/app/directives/graphs-v2/composition-graph/utils/composition-graph-links-utils.ts b/catalog-ui/src/app/directives/graphs-v2/composition-graph/utils/composition-graph-links-utils.ts index 705367c5f7..6c83810312 100644 --- a/catalog-ui/src/app/directives/graphs-v2/composition-graph/utils/composition-graph-links-utils.ts +++ b/catalog-ui/src/app/directives/graphs-v2/composition-graph/utils/composition-graph-links-utils.ts @@ -105,6 +105,46 @@ export class CompositionGraphLinkUtils { this.createLink(linkObg, cy, component); }; + public batchDeleteEdges(cy: Cy.Instance, component: Component, edgesToDelete: Cy.CollectionEdges, alreadyDeleteNodeIds?: Array<string>): void { + let toDeleteLinks: Array<RelationshipModel> = new Array<RelationshipModel>(); + if (alreadyDeleteNodeIds && alreadyDeleteNodeIds.length > 0) { + edgesToDelete.each((i: number, link: Cy.CollectionEdges) => { + if (alreadyDeleteNodeIds.indexOf(link.data().source) < 0 && alreadyDeleteNodeIds.indexOf(link.data().target) < 0) { + toDeleteLinks.push(link.data().relation); + } + }); + } + else { + edgesToDelete.each((i: number, link: Cy.CollectionEdges) => { + toDeleteLinks.push(link.data().relation); + }); + } + this.loaderService.showLoader('composition-graph'); + let onSuccessDeleteRelations = (response: Array<RelationshipModel>) => { + console.info('onSuccessDeleteRelations response is ', response); + //remove tempSimplePortNodes + if (alreadyDeleteNodeIds && alreadyDeleteNodeIds.length > 0) { + edgesToDelete.each((i: number, link: Cy.CollectionEdges) => { + if (alreadyDeleteNodeIds.indexOf(link.data().source) < 0 && alreadyDeleteNodeIds.indexOf(link.data().target) < 0) { + cy.remove(edgesToDelete); + } + }); + } + else { + edgesToDelete.each((i: number, link: Cy.CollectionEdges) => { + cy.remove(edgesToDelete); + }); + } + }; + + if (toDeleteLinks.length > 0) { + this.generalGraphUtils.getGraphUtilsServerUpdateQueue().addBlockingUIActionWithReleaseCallback( + () => component.batchDeleteRelation(toDeleteLinks).then(onSuccessDeleteRelations), + () => this.loaderService.hideLoader('composition-graph')); + + } + }; + public createLinkFromMenu = (cy:Cy.Instance, chosenMatch:Match, component:Component):void => { if (chosenMatch) { diff --git a/catalog-ui/src/app/directives/graphs-v2/composition-graph/utils/composition-graph-nodes-utils.ts b/catalog-ui/src/app/directives/graphs-v2/composition-graph/utils/composition-graph-nodes-utils.ts index c6c732b7df..7d08d7f9b5 100644 --- a/catalog-ui/src/app/directives/graphs-v2/composition-graph/utils/composition-graph-nodes-utils.ts +++ b/catalog-ui/src/app/directives/graphs-v2/composition-graph/utils/composition-graph-nodes-utils.ts @@ -122,6 +122,91 @@ export class CompositionGraphNodesUtils { }; /** + * Batch delete component instances on server and then removes them from the graph as well + * @param cy + * @param component + * @param nodesToDelete + */ + public batchDeleteNodes(cy:Cy.Instance, component:Component, nodesToDelete:Cy.CollectionNodes):Array<string> { + let nodesToDeleteIds:Array<string> = new Array<string>(); + if(nodesToDelete && nodesToDelete.size() > 0){ + nodesToDelete.each((i:number, node:Cy.CollectionNodes) => { + nodesToDeleteIds.push(node.data('id')); + }); + this.loaderService.showLoader('composition-graph'); + let componentInstances:Array<ComponentInstance> = component.componentInstances; + let onSuccess:(response:any) => void = (deleteFailedIds:any) => { + this.removeDeletedNodesOnGraph(cy, nodesToDelete, deleteFailedIds, componentInstances); + }; + + let onFailed:(response:any) => void = (response:any) => { + console.error('batchDeleteNodes failed error is', response); + }; + + this.GeneralGraphUtils.getGraphUtilsServerUpdateQueue().addBlockingUIActionWithReleaseCallback( + () => component.batchDeleteComponentInstance(nodesToDeleteIds).then(onSuccess, onFailed), + () => this.loaderService.hideLoader('composition-graph') + ); + } + + return nodesToDeleteIds; + }; + + private deleteNodeSuccess(cy:Cy.Instance, component:Component, nodeToDelete:Cy.CollectionNodes):void{ + //if node to delete is a UCPE, remove all children (except UCPE-CPs) and remove their "hostedOn" links + if (nodeToDelete.data().isUcpe) { + _.each(cy.nodes('[?isInsideGroup]'), (node)=> { + this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_REMOVE_NODE_FROM_UCPE, node, nodeToDelete); + }); + } + + //check whether the node is connected to any VLs that only have one other connection. If so, delete that VL as well + if (!(nodeToDelete.data() instanceof CompositionCiNodeVl)) { + let connectedVls:Array<Cy.CollectionFirstNode> = this.getConnectedVlToNode(nodeToDelete); + this.handleConnectedVlsToDelete(connectedVls); + } + + // check whether there is a service path going through this node, and if so clean it from the graph. + let nodeId = nodeToDelete.data().id; + let connectedPathLinks = cy.collection(`[type="${CompositionCiServicePathLink.LINK_TYPE}"][source="${nodeId}"], [type="${CompositionCiServicePathLink.LINK_TYPE}"][target="${nodeId}"]`); + _.forEach(connectedPathLinks, (link, key) => { + cy.remove(`[pathId="${link.data().pathId}"]`); + }); + + // update service path list + this.serviceService.getComponentCompositionData(component).subscribe((response:ServiceGenericResponse) => { + (<Service>component).forwardingPaths = response.forwardingPaths; + }); + + this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_DELETE_COMPONENT_INSTANCE_SUCCESS, nodeId); + + //update UI + cy.remove(nodeToDelete); + } + + private removeDeletedNodesOnGraph(cy:Cy.Instance, nodesToDelete:Cy.CollectionNodes, deleteFailedIds:Array<string>, componentInstances:Array<ComponentInstance>):void { + nodesToDelete.each((j:number, nodeToDelete:Cy.CollectionNodes) => { + if(deleteFailedIds.indexOf(nodeToDelete.data('id')) < 0) { + //if node to delete is a UCPE, remove all children (except UCPE-CPs) and remove their "hostedOn" links + if (nodeToDelete.data().isUcpe) { + _.each(cy.nodes('[?isInsideGroup]'), (node)=> { + this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_REMOVE_NODE_FROM_UCPE, node , nodeToDelete); + }); + } + + //check whether the node is connected to any VLs that only have one other connection. If so, delete that VL as well + if(!(nodeToDelete.data() instanceof CompositionCiNodeVl)) { + let connectedVls:Array<Cy.CollectionFirstNode> = this.getConnectedVlToNode(nodeToDelete); + this.handleConnectedVlsToDelete(connectedVls); + } + + + cy.remove(nodeToDelete); + } + }); + } + + /** * Finds all VLs connected to a single node * @param node * @returns {Array<Cy.CollectionFirstNode>} |