Merge "fix custom keys issues"
[ccsdk/cds.git] / cds-ui / designer-client / src / app / modules / feature-modules / packages / designer / designer.component.ts
1 /*
2 ============LICENSE_START==========================================
3 ===================================================================
4 Copyright (C) 2019 Orange. All rights reserved.
5 ===================================================================
6 Modification Copyright (c) 2020 IBM
7 ===================================================================
8
9 Unless otherwise specified, all software contained herein is licensed
10 under the Apache License, Version 2.0 (the License);
11 you may not use this software except in compliance with the License.
12 You may obtain a copy of the License at
13
14     http://www.apache.org/licenses/LICENSE-2.0
15
16 Unless required by applicable law or agreed to in writing, software
17 distributed under the License is distributed on an "AS IS" BASIS,
18 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 See the License for the specific language governing permissions and
20 limitations under the License.
21 ============LICENSE_END============================================
22 */
23
24 import dagre from 'dagre';
25 import graphlib from 'graphlib';
26 import { Component, OnInit, ViewEncapsulation, OnDestroy } from '@angular/core';
27 import * as joint from 'jointjs';
28 import './jointjs/elements/palette.function.element';
29 import './jointjs/elements/action.element';
30 import './jointjs/elements/board.function.element';
31 import { DesignerStore } from './designer.store';
32 import { ActionElementTypeName } from 'src/app/common/constants/app-constants';
33 import { GraphUtil } from './graph.util';
34 import { GraphGenerator } from './graph.generator.util';
35 import { FunctionsStore } from './functions.store';
36 import { Subject, empty } from 'rxjs';
37 import { takeUntil } from 'rxjs/operators';
38 import { distinctUntilChanged } from 'rxjs/operators';
39 import { BluePrintDetailModel } from '../model/BluePrint.detail.model';
40 import { ActivatedRoute } from '@angular/router';
41 import { DesignerService } from './designer.service';
42 import { isDefined } from '@angular/compiler/src/util';
43
44 @Component({
45   selector: 'app-designer',
46   templateUrl: './designer.component.html',
47   styleUrls: ['./designer.component.css'],
48   encapsulation: ViewEncapsulation.None
49 })
50 export class DesignerComponent implements OnInit, OnDestroy {
51
52   private controllerSideBar: boolean;
53   private attributesSideBar: boolean;
54   functionAttributeSidebar: boolean;
55   viewedPackage: BluePrintDetailModel = new BluePrintDetailModel();
56   customActionName: string;
57   showAction: boolean;
58   cl = 'editBar';
59
60   boardGraph: joint.dia.Graph;
61   boardPaper: joint.dia.Paper;
62
63   paletteGraph: joint.dia.Graph;
64   palettePaper: joint.dia.Paper;
65   private ngUnsubscribe = new Subject();
66   private opt = { tx: 100, ty: 100 };
67
68   constructor(private designerStore: DesignerStore,
69               private functionStore: FunctionsStore,
70               private graphUtil: GraphUtil,
71               private graphGenerator: GraphGenerator,
72               private route: ActivatedRoute,
73               private designerService: DesignerService) {
74     this.controllerSideBar = true;
75     this.attributesSideBar = false;
76     this.showAction = false;
77     this.functionAttributeSidebar = false;
78
79   }
80   private _toggleSidebar1() {
81     this.controllerSideBar = !this.controllerSideBar;
82     if (this.controllerSideBar === false) {
83       this.cl = 'editBar2';
84    }
85     if (this.controllerSideBar === true) {
86     this.cl = 'editBar';
87    }
88   }
89   private _toggleSidebar2() {
90     this.attributesSideBar = !this.attributesSideBar;
91   }
92   // private _toggleSidebar3() {
93   //   this.functionAttributeSidebar = !this.functionAttributeSidebar;
94   // }
95
96
97   /**
98    * - There is a board (main paper) that will the action and function selected from the palette
99    * itmes in this board will be used to create tosca workflow and node templates
100    * - There is also palette , whis contains all the possible functions and actions
101    * that can be dragged into the board
102    * - There is also a fly paper , which is temporarliy paper created on the fly
103    * when items is dragged from the palette- and it's deleted when the item is dropped over
104    * the board.
105    * for more info about the drag and drop algorithem used please visit the following link:
106    * https://stackoverflow.com/a/36932973/1340034
107    */
108
109   ngOnInit() {
110     this.customActionName = this.route.snapshot.paramMap.get('actionName');
111     if (this.customActionName !== '') {
112       this.showAction = true;
113     }
114     this.initializeBoard();
115     this.initializePalette();
116     this.stencilPaperEventListeners();
117     const id = this.route.snapshot.paramMap.get('id');
118     this.designerService.getPagedPackages(id).subscribe(
119       (bluePrintDetailModels) => {
120         if (bluePrintDetailModels) {
121           this.viewedPackage = bluePrintDetailModels[0];
122         }
123       });
124     /**
125      * the code to retrieve from server is commented
126      */
127     this.functionStore.state$
128       .pipe(
129         distinctUntilChanged((a: any, b: any) => JSON.stringify(a) === JSON.stringify(b)),
130         takeUntil(this.ngUnsubscribe))
131       .subscribe(state => {
132
133         if (state.serverFunctions) {
134           console.log('inside subscriotn on functions store -->', state.serverFunctions);
135           console.log(state);
136           // this.viewedFunctions = state.functions;
137           const list = state.serverFunctions;
138
139           const cells = this.graphUtil.buildPaletteGraphFromList(list);
140           this.paletteGraph.resetCells(cells);
141
142           let idx = 0;
143           cells.forEach(cell => {
144             cell.translate(5, (cell.attributes.size.height + 5) * idx++);
145           });
146         }
147       });
148
149     this.designerStore.state$
150       .pipe(
151         distinctUntilChanged((a: any, b: any) => JSON.stringify(a) === JSON.stringify(b)),
152         takeUntil(this.ngUnsubscribe))
153       .subscribe(state => {
154         if (state.sourceContent) {
155           console.log('inside desinger.component---> ', state);
156           // generate graph from store objects if exist
157           const topologtTemplate = JSON.parse(state.sourceContent);
158           console.log(topologtTemplate);
159           delete state.sourceContent;
160           this.graphGenerator.populate(topologtTemplate, this.boardGraph);
161
162           console.log('all cells', this.boardGraph.getCells());
163           /**
164            * auto arrange elements in graph
165            * https://resources.jointjs.com/docs/jointjs/v3.1/joint.html#layout.DirectedGraph
166            */
167           joint.layout.DirectedGraph.layout( this.boardGraph.getCells(), {
168             dagre,
169             graphlib,
170             setLinkVertices: false,
171             marginX: 10,
172             marginY: 10,
173             clusterPadding: { top: 100, left: 30, right: 10, bottom: 100 },
174             rankDir: 'TB'
175           });
176         }
177       });
178
179     // action triggering
180     this.functionStore.retrieveFuntions();
181
182   }
183
184   initializePalette() {
185     if (!this.paletteGraph) {
186       this.paletteGraph = new joint.dia.Graph();
187       this.palettePaper = new joint.dia.Paper({
188         el: $('#palette-paper'),
189         model: this.paletteGraph,
190         width: 318,
191         height: $('#palette-paper').height(),
192         // background: {
193         //   color: 'rgba(0, 255, 0, 0.3)'
194         // },
195         interactive: false
196         // elements in paletter need to be fixed, please refer to flying paper concept
197       });
198     }
199   }
200
201   initializeBoard() {
202     if (!this.boardGraph) {
203       console.log('initializeBoard...');
204       this.boardGraph = new joint.dia.Graph();
205       this.boardPaper = new joint.dia.Paper({
206           el: $('#board-paper'),
207           model: this.boardGraph,
208           // height: 720,
209           // width: 1100,
210           gridSize: 10,
211           drawGrid: true,
212           // background: {
213           //   color: 'rgba(0, 255, 0, 0.3)'
214           // },
215           cellViewNamespace: joint.shapes
216         });
217
218       this.boardPaper.on('all', element => {
219         // console.log(element);
220       });
221
222       this.boardPaper.on('link:pointerdown', link => {
223         console.log(link);
224       });
225
226       this.boardPaper.on('element:pointerdown', element => {
227         // this.modelSelected.emit(element.model.get('model'));
228       });
229
230       this.boardPaper.on('blank:pointerclick', () => {
231         // this.selectedModel = undefined;
232       });
233
234       this.boardGraph.on('change:position', (cell) => {
235
236         const parentId = cell.get('parent');
237         if (!parentId) {
238           // this is action
239           return;
240         }
241
242         const parent = this.boardGraph.getCell(parentId);
243
244         const parentBbox = parent.getBBox();
245         const cellBbox = cell.getBBox();
246         if (parentBbox.containsPoint(cellBbox.origin()) &&
247           parentBbox.containsPoint(cellBbox.topRight()) &&
248           parentBbox.containsPoint(cellBbox.corner()) &&
249           parentBbox.containsPoint(cellBbox.bottomLeft())) {
250
251           // All the four corners of the child are inside
252           // the parent area.
253           return;
254         }
255
256         // Revert the child position.
257         cell.set('position', cell.previous('position'));
258       });
259     }
260     console.log('done initializing Board...');
261   }
262
263   insertCustomActionIntoBoard() {
264     console.log('saving action to store action workflow....');
265     const actionName = this.graphUtil.generateNewActionName();
266     this.graphUtil.createCustomActionWithName(actionName, this.boardGraph);
267     this.designerStore.addDeclarativeWorkFlow(actionName);
268   }
269
270   stencilPaperEventListeners() {
271     this.palettePaper.on('cell:pointerdown', (draggedCell, pointerDownEvent, x, y) => {
272
273       $('body').append(`
274         <div id="flyPaper"
275             style="position:fixed;z-index:100;opacity:.7;pointer-event:none;background-color: transparent !important;"></div>`
276         );
277       const flyGraph = new joint.dia.Graph();
278       const flyPaper = new joint.dia.Paper({
279           el: $('#flyPaper'),
280           model: flyGraph,
281           interactive: true
282         });
283       const flyShape = draggedCell.model.clone();
284       const pos = draggedCell.model.position();
285       const offset = {
286         x: x - pos.x,
287         y: y - pos.y
288       };
289
290       flyShape.position(0, 0);
291       flyGraph.addCell(flyShape);
292       $('#flyPaper').offset({
293         left: pointerDownEvent.pageX - offset.x,
294         top: pointerDownEvent.pageY - offset.y
295       });
296       $('body').on('mousemove.fly', mouseMoveEvent => {
297         $('#flyPaper').offset({
298           left: mouseMoveEvent.pageX - offset.x,
299           top: mouseMoveEvent.pageY - offset.y
300         });
301       });
302
303       $('body').on('mouseup.fly', mouseupEvent => {
304         const mouseupX = mouseupEvent.pageX;
305         const mouseupY = mouseupEvent.pageY;
306         const target = this.boardPaper.$el.offset();
307         // Dropped over paper ?
308         if (mouseupX > target.left &&
309           mouseupX < target.left + this.boardPaper.$el.width() &&
310           mouseupY > target.top && y < target.top + this.boardPaper.$el.height()) {
311           const functionType = this.graphUtil.getFunctionTypeFromPaletteFunction(flyShape);
312           // step name is CDS realted terminology, please refer to tosca types
313           const stepName = functionType;
314           const functionElementForBoard = this.graphUtil.dropFunctionOverActionWithPosition(
315               stepName, functionType,
316               mouseupX, mouseupY,
317               target, offset,
318               this.boardGraph);
319
320           const parentCell = this.graphUtil.getParent(functionElementForBoard, this.boardPaper);
321
322           if (parentCell &&
323               parentCell.model.attributes.type === ActionElementTypeName &&
324             this.graphUtil.canEmpedMoreChildern(parentCell.model, this.boardGraph)) {
325
326             if (this.graphUtil.isEmptyParent(parentCell.model)) {
327               // first function in action
328               const actionName = parentCell.model.attributes.attrs['#label'].text;
329               this.designerStore.addStepToDeclarativeWorkFlow(actionName, stepName, functionType);
330               if (functionType === 'dg-generic') {
331                 this.designerStore.addDgGenericNodeTemplate(stepName);
332               } else {
333                 this.designerStore.addNodeTemplate(stepName, functionType);
334               }
335             } else {
336               // second action means there was a dg-generic node before
337               this.designerStore.addNodeTemplate(stepName, functionType);
338               // this will fail if multiple dg-generic were added
339               // TODO prevent multi functions of the same type inside the same action
340               const dgGenericNode = this.graphUtil.getDgGenericChild(parentCell.model, this.boardGraph)[0];
341               const dgGenericNodeName = this.graphUtil.getFunctionNameFromBoardFunction(dgGenericNode);
342               this.designerStore.addDgGenericDependency(dgGenericNodeName, stepName);
343             }
344
345
346             // Prevent recursive embedding.
347             if (parentCell &&
348               parentCell.model.get('parent') !== functionElementForBoard.id) {
349               parentCell.model.embed(functionElementForBoard);
350             }
351           } else {
352             console.log('function dropped outside action or not allowed, rolling back...');
353             alert('function dropped outside action or not allowed, rolling back...');
354             functionElementForBoard.remove();
355           }
356         }
357         $('body').off('mousemove.fly').off('mouseup.fly');
358         // flyShape.remove();
359         $('#flyPaper').remove();
360       });
361     });
362     console.log('done stencilPaperEventListeners()...');
363   }
364
365   ngOnDestroy() {
366     this.ngUnsubscribe.next();
367     this.ngUnsubscribe.complete();
368   }
369 }