add designer funcionality - declarative workflow 18/102618/2
authorAhmed Abbas <ahmad.helmy@orange.com>
Fri, 28 Feb 2020 16:18:36 +0000 (18:18 +0200)
committerKAPIL SINGAL <ks220y@att.com>
Fri, 28 Feb 2020 18:20:27 +0000 (18:20 +0000)
- 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

22 files changed:
cds-ui/designer-client/package.json
cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/designer.component.css
cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/designer.component.html
cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/designer.component.ts
cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/designer.store.ts
cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/functions.store.ts [new file with mode: 0644]
cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/functions/functions.component.css [deleted file]
cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/functions/functions.component.html [deleted file]
cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/functions/functions.component.spec.ts [deleted file]
cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/functions/functions.component.ts [deleted file]
cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/graph.generator.util.ts [new file with mode: 0644]
cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/graph.util.ts [new file with mode: 0644]
cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/model/designer.dashboard.state.ts
cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/model/designer.topologyTemplate.model.ts
cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/model/desinger.nodeTemplate.model.ts
cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/model/functions.state.ts [new file with mode: 0644]
cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/source-view/source-view.component.css [new file with mode: 0644]
cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/source-view/source-view.component.html [new file with mode: 0644]
cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/source-view/source-view.component.ts [new file with mode: 0644]
cds-ui/designer-client/src/app/modules/feature-modules/packages/packages.module.ts
cds-ui/designer-client/src/app/modules/feature-modules/packages/packages.routing.module.ts
cds-ui/designer-client/tslint.json

index 5149300..89e107f 100644 (file)
     "angular-material-expansion-panel": "^0.7.2",
     "backbone": "^1.4.0",
     "bootstrap": "^4.3.1",
+    "dagre": "^0.8.5",
     "datatables.net": "^1.10.20",
     "datatables.net-dt": "^1.10.20",
     "file-saver": "^2.0.2",
     "font-awesome": "^4.7.0",
+    "graphlib": "^2.1.8",
     "jointjs": "^3.0.4",
     "jquery": "^3.4.1",
     "json2typescript": "^1.2.3",
index 7994070..37a6f92 100644 (file)
@@ -268,7 +268,7 @@ p.compType-4{
   color: #fff;
 }
 .actionBtns .btn:last-child{
-  padding-left: 34px;
+  padding-left: 34px !important;
   background: url(src/assets/img/icon-import-blue.svg) 12px center #fff no-repeat;
   border: solid 1px #D0DFF1;
   color: #1B3E6F;
@@ -279,6 +279,8 @@ p.compType-4{
 }
 .componentsList{
   padding-bottom: 0;
+  height: calc( 100vh - 218px)!important;
+  overflow: scroll;
 }
 .custom-control.custom-checkbox:hover,
 .custom-control-label:hover{
@@ -342,11 +344,11 @@ p.compType-4{
  
 /*CANVAS*/
 .editBar{
-  width: 350px;
+  width: 200px;
   margin: 0 auto 0;
   padding: 6px 10px;
   background:#F4F9FE;
-  border: solid 1px #E8EFF8;
+  /* border: solid 1px #E8EFF8; */
   box-shadow: 0 2px 6px rgba(47, 83, 151, .1);
 }
 .editBar .btn-group{
@@ -366,7 +368,7 @@ p.compType-4{
 }
 .viewBtns .btn{
   background-position: 10px center;
-  padding-left: 30px;
+  padding-left: 30px!important;
 }
 .viewBtns .topologySource{
   background-image: url(src/assets/img/icon-topologyView-active.svg);
@@ -548,3 +550,30 @@ p.compType-4{
   font-size: 10px;
 
 }
+.source-button{
+  position: absolute;
+    z-index: 9999999;
+    top: 69px;
+    left: 50%;
+}
+/*jointjs paper*/
+/* #board-paper {
+    position: relative;
+    border: 1px solid gray;
+    display: inline-block;
+    background: transparent;
+    overflow: hidden;
+}
+#board-paper svg {
+    background: transparent;
+}
+#board-paper svg .link {
+    z-index: 2;
+}
+.html-element {
+    position: absolute;
+    background: #F4F9FE;
+    pointer-events: none;
+    -webkit-user-select: none;
+    z-index: 2;
+} */
index 311ce7a..1a2219b 100644 (file)
         </div>
     </div>
 </header>
+<div class="source-button editBar">
+    <div class="btn-group viewBtns" role="group">
+        <button type="button" class="btn btn-secondary topologySource active">Designer</button>
+        <button [routerLink]="['/designer/source']" type="button" class="btn btn-secondary topologyView">Scripting</button>
+    </div>
+</div>
 <ng-sidebar-container class="sidebar-container">
     <!-- Controller SideBar -->
     <ng-sidebar [(opened)]="controllerSideBar" [sidebarClass]="'demo-sidebar controllerSidebar container-fluid'"
                 [mode]="'push'"
                 #sidebarLeft>
         <div class="row">
-            <!-- <div class="col-12 p-0">
-                <form>
-                    <input type="text" class="form-control input-search-controller"
-                           placeholder="Search actions and functions">
-                </form>
-            </div> -->
+            
             <h1 class="col-12">Actions</h1>
             <div class="col-12 text-center p-0">
                 <div class="btn-group actionBtns" role="group">
       </div> -->
 
         <h1 class="col-12">Functions</h1>
+        <b>Drag and drop function to Action’s box</b>
         <div id="palette-paper" class="col-12 componentsList">
-            <b>Drag and drop function to Action’s box</b>
-            <ul class="list-group actions-scroll">
-                <!-- <li class="list-group-item" *ngFor="let function of viewedFunctions">
-                    <p class="compType-1">{{function.modelName}}</p>
-                </li> -->
-                <li class="list-group-item">
-                    <p class="compType-2">component-netconf-executor</p>
-                </li>
-                <li class="list-group-item">
-                    <p class="compType-3">component-remote-ansible-executor</p>
-                </li>
-                <li class="list-group-item">
-                    <p class="compType-4">dg-generic</p>
-                </li>
-                <li class="list-group-item">
-                    <p class="compType-1">component-resource-resolution</p>
-                </li>
-            </ul>
         </div>
         </div>
     </ng-sidebar>
     <!-- Page content -->
+    
     <div ng-sidebar-content id="board-paper">
         <button class="rotate" (click)="_toggleSidebar1()">
       <span>
         <i class="fa fa-angle-double-left"></i>
       </span>
         </button>
+        
         <!-- Canvas -->
         <div class="editBar text-center">
             <div class="btn-group mr-2" role="group" aria-label="First group">
index 130e0ae..56b5dcb 100644 (file)
@@ -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)'
-  //       });
-  //     }
-  //   });
-
-  // }
 }
index f2972d0..c37accd 100644 (file)
@@ -22,7 +22,6 @@ limitations under the License.
 import {Injectable} from '@angular/core';
 import {Store} from '../../../../common/core/stores/Store';
 import {DesignerService} from './designer.service';
-import {ModelType} from './model/ModelType.model';
 import {DesignerDashboardState} from './model/designer.dashboard.state';
 import { DeclarativeWorkflow } from './model/designer.workflow';
 import { NodeTemplate } from './model/desinger.nodeTemplate.model';
@@ -37,18 +36,6 @@ export class DesignerStore extends Store<DesignerDashboardState> {
         super(new DesignerDashboardState());
     }
 
-    public retrieveFuntions() {
-        const modelDefinitionType = 'node_type';
-        this.designerService.getFunctions(modelDefinitionType).subscribe(
-            (modelTypeList: ModelType[]) => {
-                console.log(modelTypeList);
-                this.setState({
-                    ...this.state,
-                    serverFunctions: modelTypeList,
-                });
-            });
-    }
-
     /**
      * adds empty workflow with name only.
      * called when blank action is added to the board
@@ -59,37 +46,98 @@ export class DesignerStore extends Store<DesignerDashboardState> {
             ...this.state,
             template: {
                 ...this.state.template,
-                workflows:
-                    this.state.template.workflows.set(workflowName, new DeclarativeWorkflow())
+                workflows: {
+                    ...this.state.template.workflows,
+                    [workflowName]: new DeclarativeWorkflow()
+                }
             }
         });
     }
 
-    addStepToDeclarativeWorkFlow(workflowName: string, stepType: string) {
-        const currentWorkflow: DeclarativeWorkflow = this.state.template.workflows.get(workflowName);
-        currentWorkflow.steps = {
-                target: stepType,
-                description: ''
-            };
-        const allNewWorkflowsMap =
-            this.state.template.workflows.set(workflowName, currentWorkflow);
+    addStepToDeclarativeWorkFlow(workflowName: string, stepName: string,  stepType: string) {
         this.setState({
             ...this.state,
             template: {
                 ...this.state.template,
-                workflows: allNewWorkflowsMap
+                workflows: {
+                    ...this.state.template.workflows,
+                    [workflowName]: {
+                        ...this.state.template.workflows[workflowName],
+                        steps: {
+                            [stepName]: {
+                                target: stepType,
+                                description: ''
+                            }
+                        }
+                    }
+                }
             }
         });
     }
 
+    saveSourceContent(code: string) {
+        const topologtTemplate = JSON.parse(code);
+        this.setState({
+            ...this.state,
+            sourceContent: code,
+            template: topologtTemplate
+        });
+    }
+
+
+    /**
+     * adding node tempates is a separate action of adding the steps to the workflow
+     * you can add node template and don't add workflow step when you add dependencies for the
+     * dg-generic function for example
+     */
+    addNodeTemplate(nodeTemplateName: string, type: string) {
+        this.setState({
+            ...this.state,
+            template: {
+                ...this.state.template,
+                node_templates: {
+                    ...this.state.template.node_templates,
+                    [nodeTemplateName]: new NodeTemplate(type)
+                }
+            }
+        });
+    }
+
+    addDgGenericNodeTemplate(nodeTemplateName: string) {
+        const node = new NodeTemplate('dg-generic');
+        node.properties = {
+            'dependency-node-template': []
+        };
+        this.setState({
+            ...this.state,
+            template: {
+                ...this.state.template,
+                node_templates: {
+                    ...this.state.template.node_templates,
+                    [nodeTemplateName]: node
+                }
+            }
+        });
+    }
 
-    addNodeTemplate(nodeTemplateName: string) {
+    addDgGenericDependency(dgGenericNodeName: string, dependency: string) {
+        const props = this.state.template.node_templates[dgGenericNodeName].properties;
         this.setState({
             ...this.state,
             template: {
                 ...this.state.template,
-                node_templates:
-                    this.state.template.node_templates.set(nodeTemplateName, new NodeTemplate())
+                node_templates: {
+                    ...this.state.template.node_templates,
+                    [dgGenericNodeName]: {
+                        ...this.state.template.node_templates[dgGenericNodeName],
+                        properties: {
+                            'dependency-node-template': [
+                                ...props['dependency-node-template'],
+                                dependency
+                            ]
+                        }
+                    }
+                }
             }
         });
     }
diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/functions.store.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/functions.store.ts
new file mode 100644 (file)
index 0000000..8681417
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+============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 {Injectable} from '@angular/core';
+import {Store} from '../../../../common/core/stores/Store';
+import {DesignerService} from './designer.service';
+import {ModelType} from './model/ModelType.model';
+import { FunctionsState } from './model/functions.state';
+
+
+@Injectable({
+    providedIn: 'root'
+})
+export class FunctionsStore extends Store<FunctionsState> {
+
+    constructor(private designerService: DesignerService) {
+        super(new FunctionsState());
+    }
+
+    public retrieveFuntions() {
+        const modelDefinitionType = 'node_type';
+        this.designerService.getFunctions(modelDefinitionType).subscribe(
+            (modelTypeList: ModelType[]) => {
+                this.setState({
+                    ...this.state,
+                    serverFunctions: modelTypeList,
+                });
+            });
+    }
+}
diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/functions/functions.component.css b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/functions/functions.component.css
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/functions/functions.component.html b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/functions/functions.component.html
deleted file mode 100644 (file)
index b27f91f..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-<h1 class="col-12">Functions</h1>
-<div class="col-12 componentsList">
-    <b>Drag and drop function to Action’s box</b>
-    <ul class="list-group actions-scroll" >
-        <li class="list-group-item" *ngFor="let function of viewedFunctions">
-            <p class="compType-1">{{function.modelName}}</p>
-        </li>
-        <!--<li class="list-group-item">
-            <p class="compType-2">component-netconf-executor</p>
-        </li>
-        <li class="list-group-item">
-            <p class="compType-3">component-remote-ansible-executor</p>
-        </li>
-        <li class="list-group-item">
-            <p class="compType-4">dg-generic</p>
-        </li>
-        <li class="list-group-item">
-            <p class="compType-1">component-resource-resolution</p>
-        </li>-->
-    </ul>
-</div>
diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/functions/functions.component.spec.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/functions/functions.component.spec.ts
deleted file mode 100644 (file)
index eec909b..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-import { async, ComponentFixture, TestBed } from '@angular/core/testing';
-
-import { FunctionsComponent } from './functions.component';
-
-describe('FunctionsComponent', () => {
-  let component: FunctionsComponent;
-  let fixture: ComponentFixture<FunctionsComponent>;
-
-  beforeEach(async(() => {
-    TestBed.configureTestingModule({
-      declarations: [ FunctionsComponent ]
-    })
-    .compileComponents();
-  }));
-
-  beforeEach(() => {
-    fixture = TestBed.createComponent(FunctionsComponent);
-    component = fixture.componentInstance;
-    fixture.detectChanges();
-  });
-
-  it('should create', () => {
-    expect(component).toBeTruthy();
-  });
-});
diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/functions/functions.component.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/functions/functions.component.ts
deleted file mode 100644 (file)
index e1e980e..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-import {Component, OnInit} from '@angular/core';
-import {DesignerStore} from '../designer.store';
-import {ModelType} from '../model/ModelType.model';
-
-
-@Component({
-    selector: 'app-functions',
-    templateUrl: './functions.component.html',
-    styleUrls: ['./functions.component.css']
-})
-export class FunctionsComponent implements OnInit {
-    viewedFunctions: ModelType[] = [];
-
-    constructor(private designerStore: DesignerStore) {
-
-        this.designerStore.state$.subscribe(state => {
-            console.log(state);
-            if (state.serverFunctions) {
-                this.viewedFunctions = state.serverFunctions;
-            }
-        });
-    }
-
-    ngOnInit() {
-        this.designerStore.retrieveFuntions();
-    }
-
-}
diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/graph.generator.util.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/graph.generator.util.ts
new file mode 100644 (file)
index 0000000..17596bd
--- /dev/null
@@ -0,0 +1,79 @@
+import { TopologyTemplate } from './model/designer.topologyTemplate.model';
+import { Injectable } from '@angular/core';
+import { GraphUtil } from './graph.util';
+
+/*
+============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============================================
+*/
+
+@Injectable({
+    providedIn: 'root'
+})
+export class GraphGenerator {
+
+    constructor(private graphUtil: GraphUtil) {
+    }
+
+    /**
+     * loops over workflows
+     * create action element
+     * from steps --> create function element
+     * add function element to action element
+     */
+    public populate(topologyTempalte: TopologyTemplate,
+                    boardGraph: joint.dia.Graph) {
+
+        Object.keys(topologyTempalte.workflows).forEach(workFlowName => {
+            console.log('drawing workflow item --> ', workFlowName);
+
+            // create action element
+            const actionElement =
+                    this.graphUtil.createCustomActionWithName(workFlowName, boardGraph);
+
+            // create board function elements
+            const workflow = topologyTempalte.workflows[workFlowName].steps;
+            const stepName = Object.keys(workflow)[0];
+            if (stepName) {
+                const functionType = workflow[stepName].target;
+                console.log('draw function with ', stepName, functionType);
+
+                const functionElementForBoard = this.graphUtil.dropFunctionOverActionRelativeToParent(
+                    actionElement,
+                    stepName , functionType, boardGraph);
+
+                // TODO handle dg-generic case (multi-step in the same action)
+                if (functionType === 'dg-generic') {
+                    const props = topologyTempalte.node_templates[stepName].properties;
+                    console.log('dg props', props);
+                    props['dependency-node-template'].forEach(dependencyStepName => {
+                        const dependencyType = topologyTempalte.node_templates[dependencyStepName].type;
+                        console.log('dependencyType', dependencyType);
+                        this.graphUtil.dropFunctionOverActionRelativeToParent(
+                            actionElement,
+                            dependencyStepName, dependencyType, boardGraph);
+
+                    });
+                }
+            }
+        });
+
+    }
+
+}
diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/graph.util.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/graph.util.ts
new file mode 100644 (file)
index 0000000..9ba7271
--- /dev/null
@@ -0,0 +1,214 @@
+/*
+============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 * as joint from 'jointjs';
+import { Injectable } from '@angular/core';
+
+@Injectable({
+    providedIn: 'root'
+})
+export class GraphUtil {
+
+    actionIdCounter = 0;
+    // to generate Ids for dragged function elements
+    private fuctionIdCounter = 0;
+
+    createCustomAction(boardGraph: joint.dia.Graph) {
+        const actionName = this.generateNewActionName();
+        const actionId = this.generateNewActionId();
+        const element = new joint.shapes.app.ActionElement({
+            id: actionId
+        });
+        element.attr('#label/text', actionName);
+        boardGraph.addCell(element);
+        return element;
+    }
+
+    generateNewActionName() {
+        this.actionIdCounter++;
+        const actionName = 'Action' + this.actionIdCounter;
+        return actionName;
+    }
+
+    private generateNewActionId() {
+        const actionName =
+                (Date.now().toString(36) + Math.random().toString(36).substr(2, 5))
+                .toUpperCase();
+        return actionName;
+    }
+
+    createCustomActionWithName(actionName: string, boardGraph: joint.dia.Graph) {
+        const actionId = this.generateNewActionId();
+        const element = new joint.shapes.app.ActionElement({
+            id: actionId
+        });
+        element.attr('#label/text', actionName);
+        boardGraph.addCell(element);
+        return element;
+    }
+
+    buildPaletteGraphFromList(list: any) {
+        const elements = [];
+        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( label: string, type: string) {
+        this.fuctionIdCounter++;
+        const id = 'fucntion_' + this.fuctionIdCounter;
+        const boardElement = new joint.shapes.board.FunctionElement({
+            id
+        });
+        boardElement.attr('#label/text', label);
+        boardElement.attr('#type/text', type);
+        return boardElement;
+    }
+
+    getParent(functionElementForBoard: joint.shapes.board.FunctionElement, boardPaper: joint.dia.Paper) {
+        const cellViewsBelow = 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
+     */
+    dropFunctionOverActionWithPosition(
+        label: string, type: string,
+        mouseupX: number, mouseupY: number,
+        target: JQuery.Coordinates, offset: { x: number; y: number; },
+        boardGraph: joint.dia.Graph) {
+
+        const functionElementForBoard = this.dropFunctionOverAction(label, type, boardGraph);
+        functionElementForBoard.position(mouseupX - target.left - offset.x, mouseupY - target.top - offset.y);
+
+        return functionElementForBoard;
+    }
+
+
+    dropFunctionOverActionRelativeToParent(
+        parent: joint.shapes.app.ActionElement,
+        label: string, type: string,
+        boardGraph: joint.dia.Graph) {
+
+        const functionElementForBoard = this.dropFunctionOverAction(label, type, boardGraph);
+        parent.embed(functionElementForBoard);
+        functionElementForBoard.position({ parentRelative: true });
+
+        return functionElementForBoard;
+    }
+
+
+    dropFunctionOverAction(
+        label: string, type: string,
+        boardGraph: joint.dia.Graph) {
+
+        // function name is the same as function type
+        // actually functionName here refers step name in CDS tosca model
+        // and function type is the nodeTempalteName
+        const functionElementForBoard =
+            this.createFuctionElementForBoard(label, type);
+        boardGraph.addCell(functionElementForBoard);
+        return functionElementForBoard;
+    }
+
+    getFunctionTypeFromPaletteFunction(cell: joint.shapes.palette.FunctionElement) {
+        return cell.attributes.attrs.type;
+    }
+
+    getFunctionTypeFromBoardFunction(cell: joint.shapes.board.FunctionElement) {
+        return cell.attributes.attrs['#type'].text;
+    }
+
+    getFunctionNameFromBoardFunction(cell: joint.shapes.board.FunctionElement) {
+        return cell.attributes.attrs['#label'].text;
+    }
+
+    canEmpedMoreChildern(parentCell: joint.shapes.app.ActionElement, boardGraph: joint.dia.Graph): boolean {
+        if (!parentCell.get('embeds')) {
+            return true;
+        }
+        const types = this.getChildernTypes(parentCell, boardGraph);
+        return parentCell.get('embeds').length < 1 ||
+            types.includes('dg-generic');
+    }
+
+
+    getChildernTypes(parentCell: joint.shapes.app.ActionElement,
+                     boardGraph: joint.dia.Graph): string[] {
+        if (parentCell.get('embeds')) {
+            return parentCell.get('embeds').map((cellName) => {
+                const child = boardGraph.getCell(cellName) as joint.shapes.board.FunctionElement;
+                const functionType = this.getFunctionTypeFromBoardFunction(child);
+                console.log('functionType', functionType);
+                return functionType;
+            });
+        } else {
+            return [];
+        }
+    }
+
+    getDgGenericChild(parentCell: joint.shapes.app.ActionElement,
+                      boardGraph: joint.dia.Graph):
+        joint.shapes.board.FunctionElement[] {
+        if (parentCell.get('embeds')) {
+            return parentCell.get('embeds')
+                .filter((cellName) => {
+                    const child = boardGraph.getCell(cellName) as joint.shapes.board.FunctionElement;
+                    const functionType = this.getFunctionTypeFromBoardFunction(child);
+                    return functionType === 'dg-generic';
+                })
+                .map((cellName) => {
+                    const child = boardGraph.getCell(cellName) as joint.shapes.board.FunctionElement;
+                    return child;
+                });
+        } else {
+            return [];
+        }
+    }
+
+    isEmptyParent(parentCell: joint.shapes.app.ActionElement): boolean {
+        return !parentCell.get('embeds') || parentCell.get('embeds').length < 1;
+    }
+
+}
index 5ae62d8..1a14021 100644 (file)
@@ -24,11 +24,10 @@ import { TopologyTemplate } from './designer.topologyTemplate.model';
 
 export class DesignerDashboardState {
 
-    serverFunctions: ModelType[];
     template: TopologyTemplate;
+    sourceContent: string;
 
     constructor() {
-        this.serverFunctions = [];
         this.template = new TopologyTemplate();
     }
 }
index 4e73c79..b85a613 100644 (file)
@@ -3,11 +3,11 @@ import { NodeTemplate } from './desinger.nodeTemplate.model';
 
 export class TopologyTemplate {
 
-    workflows: Map<string, DeclarativeWorkflow>;
-    'node_templates': Map<string, NodeTemplate>;
+    workflows: {};
+    'node_templates': {};
 
     constructor() {
-        this.workflows = new Map();
-        this.node_templates = new Map();
+        this.workflows = {};
+        this.node_templates = {};
     }
 }
diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/model/functions.state.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/model/functions.state.ts
new file mode 100644 (file)
index 0000000..329c38d
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+============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 {ModelType} from './ModelType.model';
+
+export class FunctionsState {
+
+    serverFunctions: ModelType[];
+
+    constructor() {
+        this.serverFunctions = [];
+    }
+}
diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/source-view/source-view.component.css b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/source-view/source-view.component.css
new file mode 100644 (file)
index 0000000..01ae599
--- /dev/null
@@ -0,0 +1,580 @@
+.dsl-editor {
+    height: 500px;  
+}
+
+body{
+  background-image: linear-gradient(-45deg, #000 9%, #fff 0) !important;
+  background-size: 6px 6px !important;  
+}
+
+
+/*Header*/
+header{
+  height: 60px;
+  background-color: #1B3E6F;
+  box-shadow: 0 4px 10px rgba(238, 240, 245, 1.0);
+}
+.logo{
+  float: left;
+  width: 50px;
+  height: 60px;
+  background: url(/assets/img/logo-icon.svg) center center #fff no-repeat;
+}
+
+/**Bread Crumb**/
+.breadcrumb{
+  padding: 9px 20px;
+  background: transparent;
+  line-height: 40px;
+}
+.breadcrumb a,
+.breadcrumb a:hover{
+  color: #fff;
+}
+.breadcrumb .breadcrumb-item{
+  font-size: 12px;
+  font-weight: bold;
+}
+.breadcrumb .breadcrumb-item:first-child{
+  font-size: 16px;
+}
+.breadcrumb-item + .breadcrumb-item::before{
+  color: #fff;
+}
+.breadcrumb .breadcrumb-item.active p{
+  display: inline;
+  padding: 4px 10px;
+  background: #F4F9FE;
+  border-radius: 10px;
+  color: #C3CDDB;
+  font-size: 10px;
+}
+.sidebar-container{
+  height: calc(100vh - 60px) !important;
+}
+/**Topology Actions**/
+.topology-actions{
+  margin: 0;
+  height: 60px;
+}
+.topology-actions > li{
+  height: 59px;
+  display: inline-block;
+  padding: 0 20px;
+}
+.topology-actions > li:first-child{
+  border-right: solid 1px #16396A;
+}
+.topology-actions .btn-group{
+  margin-top: 11px;
+}
+.btn-topology-action,
+.btn-topology-action:hover{
+  margin: 0 6px;
+  padding: 6px 10px;
+  color: #fff;
+  border-radius: 50%;
+  border: solid .5px #fff;
+}
+.btn-topology-action:last-child{
+  margin-right: 0;
+}
+.btn-topology-action .fa{
+  width: 16px;
+  height: 16px;
+  text-align: center;
+}
+.topology-actions .dropdown-text,
+.dropdown-toggle:hover ~ .dropdown-text, 
+.dropdown-toggle:focus ~ .dropdown-text{
+  top: 7px;
+  text-indent: 15px;
+  background: #1273EB;
+  border-radius: 15px;
+  border: 0;
+  box-shadow: none;
+  color: #fff;
+  font-weight: bold;
+  font-size: 13px;
+}
+.topology-actions .dropdown-text::after{
+  right: 15px;
+  top: 13px;
+  border-width: 6px 6px 0 6px;
+  border-color: #fff transparent transparent transparent;
+}
+.topology-actions .dropdown-toggle:focus ~ .dropdown-text::after{
+  top: 13px;
+  border-width: 0 6px 6px 6px;
+  border-color: transparent transparent #fff transparent
+}
+.topology-actions .dropdown-content:hover, 
+.topology-actions .dropdown-toggle:focus ~ .dropdown-content{
+  padding: 12px 0;
+  text-indent: 0;
+  background: #fff;
+  border: 0;
+  border-radius: 2px;
+  box-shadow: 0 2px 6px rgba(47, 83, 151, .15)
+}
+.topology-actions .dropdown-content a{
+  padding: 0 20px;
+  color: #1B3E6F;
+  font-size: 13px;
+}
+.topology-actions .dropdown-content a:hover{
+  background: #F4F9FE;
+  text-decoration: none;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/*Rotated Text*/
+button.rotate{
+  position: absolute;
+  margin-top: 1px;
+  padding: 0;
+  background: transparent;
+  border: 0;
+}
+.rotate{
+  vertical-align: bottom;
+  /* text-align: center; */
+}
+.rotate span{
+  display: inline-table !important;
+  -ms-writing-mode: tb-rl;
+  -webkit-writing-mode: vertical-rl;
+  writing-mode: vertical-rl !important;
+  transform: rotate(180deg);
+  white-space: nowrap;
+  background: #1B3E6F; 
+  padding: 15px 12px;
+  font-weight: bold;
+  font-size: 12px;
+  color:#fff;
+  /* border-bottom-left-radius: 2px; */
+  border-top-left-radius: 2px;
+}
+.rotate i{
+  margin-right: 3px;
+  margin-top: 9px;
+  font-size: 15px;
+}
+.rotate span:first-child{
+  margin-bottom: 0;
+}
+.rotate a:hover{
+  text-decoration: none;
+}
+
+/*ACTIONS & COMPONENTS MENU*/
+.input-search-controller{
+  height: 50px;
+  padding-left: 30px;
+  background: url(src/assets/img/icon-search-light.svg) #fff 10px center no-repeat;
+  border-radius: 0;
+  border: 0;
+  border-bottom: solid 1px #D7E7F9;
+  color: #1B3E6F;
+  font-size: 13px;
+}
+.input-search-controller::placeholder{
+  color: #D0D7E4;
+  font-size: 11px;
+}
+.input-search-controller:focus{
+  
+  box-shadow: 0 2px 6px 0 rgba(47, 83, 151, .15);
+  border-color: #DEE8F3;
+}
+.actions-scroll{
+  max-height: 29vh;
+  overflow-y: auto;
+  margin-top: 12px;
+  margin-bottom: 20px;
+}
+.componentsList p{
+  margin-bottom: 0;
+  padding-left: 30px;
+  background-position: left center;
+  background-repeat: no-repeat;
+}
+p.compType-1{
+  background-image: url(/assets/img/icon-comType1-sm.svg);
+}
+p.compType-2{
+  background-image: url(/assets/img/icon-comType2-sm.svg);
+}
+p.compType-3{
+  background-image: url(/assets/img/icon-comType3-sm.svg);
+}
+p.compType-4{
+  background-image: url(/assets/img/icon-comType4-sm.svg);
+}
+/*Actions Wrapper*/
+.actions-wrapper{
+  position: absolute;
+  width: 100%;
+  top: 0;
+}
+.actions-container{
+  width: 92%;
+  margin: 0 auto;
+  background: red;
+}
+
+.controllerSidebar{
+  width: 320px;
+  background: #F4F9FE;
+  border: solid 1px #C1CDDD;
+  box-shadow: 0 2px 6px rgba(47, 83, 151, .10);
+}
+.controllerSidebar h1{
+  margin-bottom: 15px;
+  padding: 12px 0 12px 12px;
+  background: #fff;
+  font-size: 12px;
+  font-weight: bold;
+  text-transform: uppercase;
+  color: #C3CDDB;
+}
+.controllerSidebar b{
+  font-size: 12px;
+  color: #C3CDDB;
+}
+.actionBtns .btn{
+  margin: 0 15px 12px;
+  padding: 9px 20px;
+  border-radius: 2px !important;
+  font-size: 12px;
+  font-weight: bold;
+}
+.actionBtns .btn:first-child{
+  background: #1B3E6F;
+  border: solid 1px #1B3E6F;
+  color: #fff;
+}
+.actionBtns .btn:last-child{
+  padding-left: 34px !important;
+  background: url(src/assets/img/icon-import-blue.svg) 12px center #fff no-repeat;
+  border: solid 1px #D0DFF1;
+  color: #1B3E6F;
+}
+.actionsList,
+.componentsList{
+  padding: 0 12px 20px;
+}
+.componentsList{
+  padding-bottom: 0;
+}
+.custom-control.custom-checkbox:hover,
+.custom-control-label:hover{
+  cursor: pointer;
+}
+.actionsList .custom-checkbox,
+.componentsList .list-group-item{
+  margin-bottom: 10px;
+  padding-left: 40px;
+  background: #fff;
+  box-shadow: 0 2px 6px rgba(47, 83, 151, .15);
+  border-radius: 2px;
+}
+.actionsList .custom-control-label{
+  width: 100%;
+  padding: 6px;
+  vertical-align: unset;
+  color: #1B3E6F;
+  font-size: 14px;
+  line-height: 20px;
+  border-top-right-radius: 2px;
+  border-bottom-right-radius: 2px;
+}
+.actionsList .custom-control-label::before,
+.actionsList .custom-control-label::after{
+  top: 1.25rem;
+}
+.actionsList .custom-control-label p{
+  color: #C7D0DD;
+  font-size: 12px;
+}
+.custom-control-input:checked ~ .custom-control-label{
+  background-color: #1B3E6F !important;
+  color: #fff;
+}
+.inserActionBtns .btn{
+  border-radius: 15px !important;
+  padding: 6px 20px;
+  font-size: 12px;
+  font-weight: bold;
+  border: 0;
+
+}
+.inserActionBtns .btn:first-child{
+  background: #1273EB;
+  border: solid 1px #1273EB;
+  color: #fff;
+}
+.inserActionBtns .btn:last-child{
+  background: #fff;
+  border: solid 1px #D9E6F2;
+  color: #C3CDDB;
+}
+/*Components List*/
+.componentsList .list-group-item{
+  padding-left: 36px;
+  border: 0;
+  font-size: 14px;
+  background: url(src/assets/img/icon-drag.svg) #fff 20px center no-repeat;
+}
+/*CANVAS*/
+.editBar{
+  width: 200px;
+  margin: 0 auto 0;
+  padding: 6px 10px;
+  background:#F4F9FE;
+  /* border: solid 1px #E8EFF8; */
+  box-shadow: 0 2px 6px rgba(47, 83, 151, .1);
+}
+.editBar .btn-group{
+  box-shadow: 0 2px 6px rgba(47, 83, 151, .15);
+}
+.editBar .btn{
+  background-color: #fff;
+  background-repeat: no-repeat;
+  background-position: left center;
+  border: 0;
+  color: #1B3E6F;
+  font-size: 10px;
+}
+.editBar .btn.active{
+  background-color: #1B3E6F !important;
+  color: #fff;
+}
+.viewBtns .btn{
+  background-position: 10px center;
+  padding-left: 30px!important;
+}
+.viewBtns .topologySource{
+  background-image: url(src/assets/img/icon-topologyView-active.svg);
+}
+.viewBtns .topologyView{
+  background-image: url(src/assets/img/icon-topologySource.svg);
+}
+.card.actionContainer{
+  margin: 20px 20px 40px 60px;
+  background: transparent;
+  border: 0;
+}
+.actionContainer .card-header{
+  padding: 0;
+  background: transparent;
+  border: 0;
+}
+.actionContainer .card-header span{
+  padding: 12px 20px;
+  border-top-left-radius: 2px;
+  border-top-right-radius: 2px;
+  font-size: 12px;
+  line-height: 38px;
+  font-weight: bold;
+  color: #1B3E6F;
+  background: #C3CDDB;
+}
+.actionContainer .card-body{
+  min-height: 170px;
+  padding: 15px 20px !important;
+  border: solid 1px #C3CDDB;
+  background: #fff;
+  box-shadow: 0 2px 6px rgba(18, 115, 235, .1);
+}
+.actionContainer a{
+  display: inline-block;
+  width: 230px;
+  height: 130px;
+  margin: 20px;
+  padding: 24px;
+  background: #1B3E6F;
+  color: #fff !important;
+  text-align: center;
+  border-radius: 2px;
+  border: solid 1px #1B3E6F;
+}
+.actionContainer a:hover{
+  cursor: pointer;
+  border: dashed 1px #E9FCC6;
+}.componentContainer img{
+  height: 38px;
+}
+.componentContainer h2{
+  margin-top: 9px;
+  font-size: 14px;
+  font-weight: bold;
+}
+.componentContainer p{
+  font-size: 12px;
+}
+
+/*ATTRIBUTES SIDE BAR*/
+.attributesSideBar{
+  width: 396px;
+  padding: 0;
+}
+.attributesSideBar .attributesContainer{
+  background: #fff;
+  border: solid 1px #C1CDDD;
+  box-shadow: 0 2px 6px rgba(47, 83, 151, .1);
+}
+.closeBar{
+  float: right;
+  width: 90%;
+  height: 40px;
+  background: url(/assets/img/icon-close.svg) center center #DCE8F4 no-repeat ;
+  border: 0;
+  outline: 0;
+}
+.closeBar:focus{
+  outline: none;
+}
+.attributesContainer h1{
+  margin-bottom: 10px;
+  padding: 12px 0 12px 15px;
+  background: #DEE8F3;
+  font-size: 12px;
+  font-weight: bold;
+  text-transform: uppercase;
+  color: #1B3E6F;
+}
+.actionName{
+  margin-bottom: 21px;
+}
+.attributesContainer label{
+  color: #1B3E6F;
+  text-transform: uppercase;
+  font-size: 11px;
+  font-weight: bold;
+}
+.attributesContainer .form-group{
+  margin-bottom: 9px;
+}
+.attributesContainer .form-control{
+  border-color: #F0F5FC;
+  border-radius: 2px;
+  box-shadow: 0 2px 6px rgba(47, 83, 151, .1);
+  color: #103D73;
+  font-size: 13px;
+}
+.attributesContainer .form-control:focus{
+  border-color: #66bfff;
+  box-shadow: 0 0 0 4px rgba(0,149,255,0.15);
+}
+.attributesContainer .form-control::placeholder{
+  color: #CFD7E5;
+}
+.scrolll{
+  max-height: 88.75vh;
+  overflow-y: auto;
+}
+.accordion > .card{
+  margin-bottom: 0 !important;
+  border: 0;
+}
+.accordion > .card .card-header{
+  margin: 0;
+  padding: 0;
+  background-color: #F4F9FE;
+  border: 0;
+  border-radius: 0;
+}
+.accordion > .card .card-body{
+  padding-bottom: 10px !important; 
+}
+.accordion .btn-link{
+  padding: 0;
+  color: #C3CDDB;
+  font-weight: bold;
+  font-size: 13px;
+  text-transform: uppercase;
+  line-height: 38px;
+}
+.accordion .btn-link:hover{
+  color: #103D73;
+  text-decoration: unset;
+}
+.accordion .card-header .btn-link[aria-expanded="true"]:after,
+.accordion .card-header .btn-link[aria-expanded="false"]:after{
+  margin-right: 9px;
+  font-family: 'FontAwesome';
+  float: left;
+  font-weight: normal;
+  font-size: 12px;
+}
+.accordion .card-header .btn-link[aria-expanded="true"]:after{
+  content: "\f078";    
+}
+.accordion .card-header .btn-link[aria-expanded="false"]:after{    
+  content: "\f054";    
+}
+.btn-addAttribute{
+  width: 20px;
+  height: 20px;
+  background-image: url(/assets/img/icon-add.svg);
+  background-position: center center;
+  background-repeat: no-repeat; 
+  vertical-align: sub;
+}
+.btn-addAttribute:hover{
+  background-image: url(/assets/img/icon-add-hover.svg);
+}
+.btn-deleteAttribute{
+  padding: 5px 10px;
+  background: #FFE6E7;
+  border: solid .5px #FFC9CB;
+  border-radius: 2px;
+  color: #FF6469;
+  font-size: 10px;
+
+}
+.source-button{
+  position: absolute;
+    z-index: 9999999;
+    top: 69px;
+    left: 50%;
+}
+/*jointjs paper*/
+/* #board-paper {
+    position: relative;
+    border: 1px solid gray;
+    display: inline-block;
+    background: transparent;
+    overflow: hidden;
+}
+#board-paper svg {
+    background: transparent;
+}
+#board-paper svg .link {
+    z-index: 2;
+}
+.html-element {
+    position: absolute;
+    background: #F4F9FE;
+    pointer-events: none;
+    -webkit-user-select: none;
+    z-index: 2;
+} */
diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/source-view/source-view.component.html b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/source-view/source-view.component.html
new file mode 100644 (file)
index 0000000..2a55851
--- /dev/null
@@ -0,0 +1,80 @@
+<header>
+    <div class="row m-0">
+        <div class="col pl-0">
+            <p class="logo mb-0"></p>
+            <nav aria-label="breadcrumb">
+                <ol class="breadcrumb mb-0">
+                    <li class="breadcrumb-item">
+                        <a href="#">CBA Packages</a>
+                    </li>
+                    <li class="breadcrumb-item">
+                        <a href="#">Package Name</a>
+                    </li>
+                    <li class="breadcrumb-item active" aria-current="page">
+                        <p class="mb-0">Topology View</p>
+                    </li>
+                </ol>
+            </nav>
+        </div>
+        <div class="col pr-0 text-right">
+            <ul class="topology-actions">
+                <li>
+                    <div class="btn-group" role="group" aria-label="Basic example">
+                        <a href="#" role="button" aria-pressed="true" class="btn-topology-action float tooltip-bottom"
+                            data-tooltip="Preview">
+                            <i class="fa fa-eye"></i>
+                        </a>
+                        <a href="#" role="button" aria-pressed="true" class="btn-topology-action float tooltip-bottom"
+                            data-tooltip="Download">
+                            <i class="fa fa-download"></i>
+                        </a>
+                        <a href="#" role="button" aria-pressed="true" class="btn-topology-action float tooltip-bottom"
+                            data-tooltip="Share">
+                            <i class="fa fa-share-square"></i>
+                        </a>
+                    </div>
+                </li>
+                <li>
+                    <div class="dropdown">
+                        <input class="dropdown-toggle" type="text">
+                        <div class="dropdown-text">Save</div>
+                        <ul class="dropdown-content">
+                            <li>
+                                <a href="">Save</a>
+                            </li>
+                            <li>
+                                <a href="">Save &amp; Deploy</a>
+                            </li>
+                        </ul>
+                    </div>
+                </li>
+            </ul>
+
+
+        </div>
+    </div>
+</header>
+<div class="source-button editBar">
+    <div class="btn-group viewBtns" role="group">
+        <button (click)="convertAndOpenInDesingerView()" type="button" class="btn btn-secondary topologySource">Designer</button>
+        <button type="button"
+            class="btn btn-secondary topologyView active">Scripting</button>
+    </div>
+</div>
+<ng-sidebar-container class="sidebar-container">
+    <!-- Controller SideBar -->
+    <ng-sidebar [(opened)]="controllerSideBar" [sidebarClass]="'demo-sidebar controllerSidebar container-fluid'"
+        [mode]="'push'" #sidebarLeft>
+        <div class="row">
+
+            <h1 class="col-12">Actions</h1>
+           
+            
+        </div>
+    </ng-sidebar>
+<div ng-sidebar-content id="board-paper">
+    <ace-editor [(text)]="content" [mode]="'json'" [autoUpdateContent]="true" [durationBeforeCallback]="1000"
+        [theme]="'tomorrow_night_bright'" #editor style="height:500px">
+    </ace-editor>
+
+   </div>
diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/source-view/source-view.component.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/source-view/source-view.component.ts
new file mode 100644 (file)
index 0000000..34194e4
--- /dev/null
@@ -0,0 +1,45 @@
+import { Component, OnInit, OnDestroy } from '@angular/core';
+import { DesignerStore } from '../designer.store';
+import { PackageCreationUtils } from '../../package-creation/package-creation.utils';
+import { RouterLink, Router } from '@angular/router';
+import { Subject } from 'rxjs';
+
+@Component({
+    selector: 'app-designer-source-view',
+    templateUrl: './source-view.component.html',
+    styleUrls: ['./source-view.component.css']
+})
+export class DesignerSourceViewComponent implements OnInit, OnDestroy {
+
+    content = '';
+    lang = 'json';
+    private controllerSideBar: boolean;
+    private ngUnsubscribe = new Subject();
+
+    constructor(private store: DesignerStore,
+                private packageCreationUtils: PackageCreationUtils,
+                private router: Router) {
+        this.controllerSideBar = true;
+    }
+
+    ngOnInit() {
+        this.store.state$.subscribe(
+            state => {
+                console.log(state);
+                this.content = this.packageCreationUtils.transformToJson(state.template);
+            });
+
+    }
+
+    convertAndOpenInDesingerView() {
+        // TODO validate json against scheme
+        console.log('convertAndOpenInDesingerView ...', this.content);
+        this.store.saveSourceContent(this.content);
+        this.router.navigateByUrl('/packages/designer');
+    }
+
+    ngOnDestroy() {
+        this.ngUnsubscribe.next();
+        this.ngUnsubscribe.complete();
+    }
+}
index 0d56014..66c7b49 100644 (file)
@@ -14,7 +14,6 @@ import { PackagesHeaderComponent } from './packages-dashboard/packages-header/pa
 import { PackagesSearchComponent } from './packages-dashboard/search-by-packages/search-by-packages.component';
 import { TagsFilteringComponent } from './packages-dashboard/filter-by-tags/filter-by-tags.component';
 import { ConfigurationDashboardComponent } from './configuration-dashboard/configuration-dashboard.component';
-import { FunctionsComponent } from './designer/functions/functions.component';
 import { ActionsComponent } from './designer/actions/actions.component';
 import { PackageCreationComponent } from './package-creation/package-creation.component';
 import { FormsModule } from '@angular/forms';
@@ -29,6 +28,7 @@ import { DslDefinitionsTabComponent } from './package-creation/dsl-definitions-t
 import { TemplMappCreationComponent } from './package-creation/template-mapping/templ-mapp-creation/templ-mapp-creation.component';
 import { TemplMappListingComponent } from './package-creation/template-mapping/templ-mapp-listing/templ-mapp-listing.component';
 import { DataTablesModule } from 'angular-datatables';
+import { DesignerSourceViewComponent } from './designer/source-view/source-view.component';
 
 @NgModule({
     declarations: [PackagesDashboardComponent,
@@ -40,7 +40,6 @@ import { DataTablesModule } from 'angular-datatables';
         SortPackagesComponent,
         ConfigurationDashboardComponent,
         PackagesHeaderComponent,
-        FunctionsComponent,
         ActionsComponent,
         PackageCreationComponent,
         ImportsTabComponent,
@@ -51,6 +50,7 @@ import { DataTablesModule } from 'angular-datatables';
         ScriptsTabComponent,
         MetadataTabComponent,
         DslDefinitionsTabComponent,
+        DesignerSourceViewComponent,
     ],
     imports: [
         CommonModule,
index 913bb10..ad06cf1 100644 (file)
@@ -4,6 +4,7 @@ import {PackagesDashboardComponent} from './packages-dashboard/packages-dashboar
 import {DesignerComponent} from './designer/designer.component';
 import {PackageCreationComponent} from './package-creation/package-creation.component';
 import {ConfigurationDashboardComponent} from './configuration-dashboard/configuration-dashboard.component';
+import { DesignerSourceViewComponent } from './designer/source-view/source-view.component';
 
 
 const routes: Routes = [
@@ -12,6 +13,7 @@ const routes: Routes = [
         component: PackagesDashboardComponent
     },
     {path: 'designer', component: DesignerComponent},
+    { path: 'designer/source', component: DesignerSourceViewComponent },
     {path: 'package/:id', component: ConfigurationDashboardComponent},
     {path: 'createPackage', component: PackageCreationComponent},
 ];
index ecbd7cf..f85fc68 100644 (file)
   },
   "rulesDirectory": [
     "codelyzer"
-  ],
-  "linterOptions": {
-    "exclude": [
-      "src/app/modules/feature-modules/packages/designer/designer.component.ts"
-    ]
-  }
+  ]
 }