diff options
author | 2020-02-28 18:18:36 +0200 | |
---|---|---|
committer | 2020-02-28 18:20:27 +0000 | |
commit | 886d352ec99fe5281c281c16f8d4b9908fb0dcc3 (patch) | |
tree | ec279d227f4e58f6cfe60c6a2aa3f5b0d9c7df78 /cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/designer.component.ts | |
parent | dc3e41335d7ceb42901b8fd4ccace72825552302 (diff) |
add designer funcionality - declarative workflow
- save source editor to store
- generate graph based on json data from source editor
- make functions retrieved from server
- prevent multible functions inside action if the first fn is not dg-generic
- dg generic case (mutli functions inside single action)
- arrange elements that are generated automcatilly using DirectedGraph lib dagree
Issue-ID: CCSDK-1779
Issue-ID: CCSDK-1783
Issue-ID: CCSDK-2017
Signed-off-by: Ahmed Abbas <ahmad.helmy@orange.com>
Change-Id: Ief3579e4a9716475c9aaf85b5a349bc2af466cdb
Diffstat (limited to 'cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/designer.component.ts')
-rw-r--r-- | cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/designer.component.ts | 359 |
1 files changed, 155 insertions, 204 deletions
diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/designer.component.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/designer.component.ts index 130e0ae19..56b5dcbbc 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/designer.component.ts +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/designer.component.ts @@ -1,11 +1,39 @@ -import { Component, OnInit, ViewEncapsulation } from '@angular/core'; +/* +============LICENSE_START========================================== +=================================================================== +Copyright (C) 2019 Orange. All rights reserved. +=================================================================== + +Unless otherwise specified, all software contained herein is licensed +under the Apache License, Version 2.0 (the License); +you may not use this software except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +============LICENSE_END============================================ +*/ + +import dagre from 'dagre'; +import graphlib from 'graphlib'; +import { Component, OnInit, ViewEncapsulation, OnDestroy } from '@angular/core'; import * as joint from 'jointjs'; import './jointjs/elements/palette.function.element'; import './jointjs/elements/action.element'; import './jointjs/elements/board.function.element'; import { DesignerStore } from './designer.store'; import { ActionElementTypeName } from 'src/app/common/constants/app-constants'; - +import { GraphUtil } from './graph.util'; +import { GraphGenerator } from './graph.generator.util'; +import { FunctionsStore } from './functions.store'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { distinctUntilChanged } from 'rxjs/operators'; @Component({ @@ -14,23 +42,25 @@ import { ActionElementTypeName } from 'src/app/common/constants/app-constants'; styleUrls: ['./designer.component.css'], encapsulation: ViewEncapsulation.None }) -export class DesignerComponent implements OnInit { +export class DesignerComponent implements OnInit, OnDestroy { private controllerSideBar: boolean; private attributesSideBar: boolean; - //to generate Ids for dragged function elements - private fuctionIdCounter=0; - private actionIdCounter=0; boardGraph: joint.dia.Graph; boardPaper: joint.dia.Paper; paletteGraph: joint.dia.Graph; palettePaper: joint.dia.Paper; + private ngUnsubscribe = new Subject(); - constructor(private designerStore: DesignerStore) { + constructor(private designerStore: DesignerStore, + private functionStore: FunctionsStore, + private graphUtil: GraphUtil, + private graphGenerator: GraphGenerator) { this.controllerSideBar = true; this.attributesSideBar = false; + } private _toggleSidebar1() { this.controllerSideBar = !this.controllerSideBar; @@ -55,38 +85,65 @@ export class DesignerComponent implements OnInit { ngOnInit() { this.initializeBoard(); this.initializePalette(); - // this.createEditBarOverThePaper(); - - //functions list is contants for now - const list = [ - { modelName: 'component-netconf-executor'}, - { modelName: 'component-remote-ansible-executor' }, - { modelName: 'dg-generic' }, - { modelName: 'component-resource-resolution' }]; - const cells = this.buildPaletteGraphFromList(list); - this.paletteGraph.resetCells(cells); - - let idx = 0; - cells.forEach(cell => { - console.log(cell); - cell.translate(5, (cell.attributes.size.height + 5) * idx++); + this.stencilPaperEventListeners(); + + /** + * the code to retrieve from server is commented + */ + this.functionStore.state$ + .pipe(x => { console.log('value on way to distinct', x); return x; }) + .pipe( + distinctUntilChanged((a: any, b: any) => JSON.stringify(a) === JSON.stringify(b)), + takeUntil(this.ngUnsubscribe)) + .subscribe(state => { + + if (state.serverFunctions) { + console.log('inside subscriotn on functions store -->', state.serverFunctions); + console.log(state); + // this.viewedFunctions = state.functions; + const list = state.serverFunctions; + + const cells = this.graphUtil.buildPaletteGraphFromList(list); + this.paletteGraph.resetCells(cells); + + let idx = 0; + cells.forEach(cell => { + cell.translate(5, (cell.attributes.size.height + 5) * idx++); + }); + } + }); + this.designerStore.state$ + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe(state => { + if (state.sourceContent) { + console.log('inside desinger.component---> ', state); + // generate graph from store objects if exist + const topologtTemplate = JSON.parse(state.sourceContent); + console.log(topologtTemplate); + delete state.sourceContent; + this.graphGenerator.populate(topologtTemplate, this.boardGraph); + /** + * auto arrange elements in graph + * https://resources.jointjs.com/docs/jointjs/v3.1/joint.html#layout.DirectedGraph + */ + joint.layout.DirectedGraph.layout( this.boardGraph.getCells(), { + dagre, + graphlib, + // nodeSep: 50, + // setLinkVertices: false, + // rankDir: 'LR', + marginX: 100, + marginY: 100, + clusterPadding: { top: 100, left: 10, right: 10, bottom: 100 }, + rankDir: 'TB' + }); + } }); - this.stencilPaperEventListeners(); - /** - * the code to retrieve from server is commented - */ - // this.designerStore.state$.subscribe(state => { - // console.log(state); - // if (state.functions) { - // console.log('functions-->' , state.functions); - // // this.viewedFunctions = state.functions; - // const list = state.functions; - // } - // }); - //action triggering - // this.designerStore.getFuntions(); - + + // action triggering + this.functionStore.retrieveFuntions(); + } initializePalette() { @@ -95,27 +152,31 @@ export class DesignerComponent implements OnInit { this.palettePaper = new joint.dia.Paper({ el: $('#palette-paper'), model: this.paletteGraph, - height: 300, width: 300, - gridSize: 1, + height: $('#palette-paper').height(), + // background: { + // color: 'rgba(0, 255, 0, 0.3)' + // }, interactive: false + // elements in paletter need to be fixed, please refer to flying paper concept }); } } initializeBoard() { if (!this.boardGraph) { + console.log('initializeBoard...'); this.boardGraph = new joint.dia.Graph(); this.boardPaper = new joint.dia.Paper({ el: $('#board-paper'), model: this.boardGraph, height: 720, - width: 1200, + width: 1100, gridSize: 10, drawGrid: true, - // background: { - // color: 'rgba(0, 255, 0, 0.3)' - // }, + background: { + color: 'rgba(0, 255, 0, 0.3)' + }, cellViewNamespace: joint.shapes }); @@ -137,19 +198,20 @@ export class DesignerComponent implements OnInit { this.boardGraph.on('change:position', (cell) => { - var parentId = cell.get('parent'); - if (!parentId) return; + const parentId = cell.get('parent'); + if (!parentId) { + return; + } + + const parent = this.boardGraph.getCell(parentId); - var parent = this.boardGraph.getCell(parentId); - - var parentBbox = parent.getBBox(); - var cellBbox = cell.getBBox(); + const parentBbox = parent.getBBox(); + const cellBbox = cell.getBBox(); if (parentBbox.containsPoint(cellBbox.origin()) && parentBbox.containsPoint(cellBbox.topRight()) && parentBbox.containsPoint(cellBbox.corner()) && parentBbox.containsPoint(cellBbox.bottomLeft())) { - // All the four corners of the child are inside // the parent area. return; @@ -159,55 +221,16 @@ export class DesignerComponent implements OnInit { cell.set('position', cell.previous('position')); }); } + console.log('done initializing Board...'); } insertCustomActionIntoBoard() { - this.actionIdCounter++; - const actionId = "action_" + this.actionIdCounter; - const actionName = 'Action' + this.actionIdCounter; - const element = this.createCustomAction(actionId , actionName); - this.boardGraph.addCell(element); console.log('saving action to store action workflow....'); + const actionName = this.graphUtil.generateNewActionName(); + this.graphUtil.createCustomActionWithName(actionName, this.boardGraph); this.designerStore.addDeclarativeWorkFlow(actionName); } - createCustomAction(id: string, label: string) { - const element = new joint.shapes.app.ActionElement({ - id: id - }); - element.attr('#label/text', label); - return element; - } - - buildPaletteGraphFromList(list: any) { - const elements = []; - - console.log(list); - list.forEach(element => { - elements.push(this.createFuctionElementForPalette(element.modelName)); - }); - - return elements; - } - - createFuctionElementForPalette(label: string) { - const element = new joint.shapes.palette.FunctionElement({ - id: label - }); - element.attr('#label/text', label); - element.attr('type', label); - return element; - } - - createFuctionElementForBoard(id :String, label :string, type :string) { - const boardElement = new joint.shapes.board.FunctionElement({ - id: id - }); - boardElement.attr('#label/text', label); - boardElement.attr('#type/text', type); - return boardElement; - } - stencilPaperEventListeners() { this.palettePaper.on('cell:pointerdown', (draggedCell, pointerDownEvent, x, y) => { @@ -249,28 +272,49 @@ export class DesignerComponent implements OnInit { if (mouseupX > target.left && mouseupX < target.left + this.boardPaper.$el.width() && mouseupY > target.top && y < target.top + this.boardPaper.$el.height()) { - const functionType = flyShape.attributes.attrs.type; - console.log(functionType); - const functionElementForBoard = this.dropFunctionOverAction(functionType, mouseupX, target, offset, mouseupY); - - let parentCell = this.getParent(functionElementForBoard); - - console.log("parentCell -->", parentCell); + const functionType = this.graphUtil.getFunctionTypeFromPaletteFunction(flyShape); + // step name is CDS realted terminology, please refer to tosca types + const stepName = functionType; + const functionElementForBoard = this.graphUtil.dropFunctionOverActionWithPosition( + stepName, functionType, + mouseupX, mouseupY, + target, offset, + this.boardGraph); + + const parentCell = this.graphUtil.getParent(functionElementForBoard, this.boardPaper); + + if (parentCell && + parentCell.model.attributes.type === ActionElementTypeName && + this.graphUtil.canEmpedMoreChildern(parentCell.model, this.boardGraph)) { + + if (this.graphUtil.isEmptyParent(parentCell.model)) { + // first function in action + const actionName = parentCell.model.attributes.attrs['#label'].text; + this.designerStore.addStepToDeclarativeWorkFlow(actionName, stepName, functionType); + if (functionType === 'dg-generic') { + this.designerStore.addDgGenericNodeTemplate(stepName); + } else { + this.designerStore.addNodeTemplate(stepName, functionType); + } + } else { + // second action means there was a dg-generic node before + this.designerStore.addNodeTemplate(stepName, functionType); + // this will fail if multiple dg-generic were added + // TODO prevent multi functions of the same type inside the same action + const dgGenericNode = this.graphUtil.getDgGenericChild(parentCell.model, this.boardGraph)[0]; + const dgGenericNodeName = this.graphUtil.getFunctionNameFromBoardFunction(dgGenericNode); + this.designerStore.addDgGenericDependency(dgGenericNodeName, stepName); + } - if (parentCell && - parentCell.model.attributes.type === ActionElementTypeName){ - - const actionName = parentCell.model.attributes.attrs['#label'].text; - this.designerStore.addStepToDeclarativeWorkFlow(actionName, functionType); - this.designerStore.addNodeTemplate(functionType); // Prevent recursive embedding. if (parentCell && parentCell.model.get('parent') !== functionElementForBoard.id) { parentCell.model.embed(functionElementForBoard); } - }else{ - console.log('function dropped outside action, rolling back...'); + } else { + console.log('function dropped outside action or not allowed, rolling back...'); + alert('function dropped outside action or not allowed, rolling back...'); functionElementForBoard.remove(); } } @@ -279,104 +323,11 @@ export class DesignerComponent implements OnInit { $('#flyPaper').remove(); }); }); + console.log('done stencilPaperEventListeners()...'); } - private getParent(functionElementForBoard: joint.shapes.board.FunctionElement) { - const cellViewsBelow = this.boardPaper.findViewsFromPoint(functionElementForBoard.getBBox().center()); - let cellViewBelow; - if (cellViewsBelow.length) { - cellViewsBelow.forEach(cellItem => { - if (cellItem.model.id !== functionElementForBoard.id) { - cellViewBelow = cellItem; - } - }); - } - return cellViewBelow; - } - - /** - * trigger actions related to Function dropped over the board: - * - create board function element of the same type of palette function - * as board function element is different from the palette function element - * - save function to parent action in store - */ - private dropFunctionOverAction(functionType: any, mouseupX: number, target: JQuery.Coordinates, offset: { x: number; y: number; }, mouseupY: number) { - this.fuctionIdCounter++; - const functionElementForBoard = this.createFuctionElementForBoard("fucntion_" + this.fuctionIdCounter, 'execute', functionType); - functionElementForBoard.position(mouseupX - target.left - offset.x, mouseupY - target.top - offset.y); - this.boardGraph.addCell(functionElementForBoard); - return functionElementForBoard; + ngOnDestroy() { + this.ngUnsubscribe.next(); + this.ngUnsubscribe.complete(); } - /** - * this is a way to add the button like zoom in , zoom out , and source over jointjs paper - * may be used if no other way is found - */ - // createEditBarOverThePaper() { - // joint.shapes["html"] = {}; - // joint.shapes["html"].Element = joint.shapes.basic.Rect.extend({ - // defaults: joint.util.deepSupplement({ - // type: 'html.Element' - // }, joint.shapes.basic.Rect.prototype.defaults) - // }); - // joint.shapes["html"].ElementView = joint.dia.ElementView.extend({ - - // template: [ - // '<div>', - // '<div id="editbar" class="editBar text-center">', - // '<div class="btn-group mr-2" role="group" aria-label="First group">', - // '<button type="button" class="btn btn-secondary tooltip-bottom" data-tooltip="Undo">', - // '<img src="/assets/img/icon-undoActive.svg">', - // '</button>', - // '<button type="button" class="btn btn-secondary tooltip-bottom" data-tooltip="Redo">', - // '<img src="/assets/img/icon-redo.svg">', - // '</button>', - // '</div>', - // '<div class="btn-group mr-2" role="group" aria-label="Second group">', - // '<button type="button" class="btn btn-secondary tooltip-bottom" data-tooltip="Zoom Out">', - // '<img src="/assets/img/icon-zoomOut.svg">', - // '</button>', - // '<button type="button" class="btn btn-secondary pl-0 pr-0">100%</button>', - // '<button type="button" class="btn btn-secondary tooltip-bottom" data-tooltip="Zoom In">', - // '<img src="/assets/img/icon-zoomIn.svg">', - // '</button>', - // '</div>', - // '<div class="btn-group viewBtns" role="group" aria-label="Third group">', - // '<button type="button" class="btn btn-secondary topologySource active">View</button>', - // '<button type="button" class="btn btn-secondary topologyView">Source</button>', - // '</div>', - // '</div>', - // '</div>' - // ].join(''), - // initialize: function () { - // _.bindAll(this, 'updateBox'); - // joint.dia.ElementView.prototype.initialize.apply(this, arguments); - - // this.$box = $(_.template(this.template)()); - // // Prevent paper from handling pointerdown. - // this.$box.find('input,select').on('mousedown click', function (evt) { - // evt.stopPropagation(); - // }); - // this.model.on('change', this.updateBox, this); - // this.updateBox(); - // }, - // render: function () { - // joint.dia.ElementView.prototype.render.apply(this, arguments); - // this.paper.$el.prepend(this.$box); - // this.updateBox(); - // return this; - // }, - // updateBox: function () { - // // Set the position and dimension of the box so that it covers the JointJS element. - // var bbox = this.model.getBBox(); - // this.$box.css({ - // width: bbox.width, - // height: bbox.height, - // left: bbox.x, - // top: bbox.y, - // transform: 'rotate(' + (this.model.get('angle') || 0) + 'deg)' - // }); - // } - // }); - - // } } |