Merge "Update css file name in conf.py"
[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
59   boardGraph: joint.dia.Graph;
60   boardPaper: joint.dia.Paper;
61
62   paletteGraph: joint.dia.Graph;
63   palettePaper: joint.dia.Paper;
64   private ngUnsubscribe = new Subject();
65   private opt = { tx: 100, ty: 100 };
66
67   constructor(private designerStore: DesignerStore,
68               private functionStore: FunctionsStore,
69               private graphUtil: GraphUtil,
70               private graphGenerator: GraphGenerator,
71               private route: ActivatedRoute,
72               private designerService: DesignerService) {
73     this.controllerSideBar = true;
74     this.attributesSideBar = false;
75     this.showAction = false;
76     this.functionAttributeSidebar = true;
77
78   }
79   private _toggleSidebar1() {
80     this.controllerSideBar = !this.controllerSideBar;
81   }
82   private _toggleSidebar2() {
83     this.attributesSideBar = !this.attributesSideBar;
84   }
85   // private _toggleSidebar3() {
86   //   this.functionAttributeSidebar = !this.functionAttributeSidebar;
87   // }
88
89
90   /**
91    * - There is a board (main paper) that will the action and function selected from the palette
92    * itmes in this board will be used to create tosca workflow and node templates
93    * - There is also palette , whis contains all the possible functions and actions
94    * that can be dragged into the board
95    * - There is also a fly paper , which is temporarliy paper created on the fly
96    * when items is dragged from the palette- and it's deleted when the item is dropped over
97    * the board.
98    * for more info about the drag and drop algorithem used please visit the following link:
99    * https://stackoverflow.com/a/36932973/1340034
100    */
101
102   ngOnInit() {
103     this.customActionName = this.route.snapshot.paramMap.get('actionName');
104     if (this.customActionName !== '') {
105       this.showAction = true;
106     }
107     this.initializeBoard();
108     this.initializePalette();
109     this.stencilPaperEventListeners();
110     const id = this.route.snapshot.paramMap.get('id');
111     this.designerService.getPagedPackages(id).subscribe(
112       (bluePrintDetailModels) => {
113         if (bluePrintDetailModels) {
114           this.viewedPackage = bluePrintDetailModels[0];
115         }
116       });
117     /**
118      * the code to retrieve from server is commented
119      */
120     this.functionStore.state$
121       .pipe(
122         distinctUntilChanged((a: any, b: any) => JSON.stringify(a) === JSON.stringify(b)),
123         takeUntil(this.ngUnsubscribe))
124       .subscribe(state => {
125
126         if (state.serverFunctions) {
127           console.log('inside subscriotn on functions store -->', state.serverFunctions);
128           console.log(state);
129           // this.viewedFunctions = state.functions;
130           const list = state.serverFunctions;
131
132           const cells = this.graphUtil.buildPaletteGraphFromList(list);
133           this.paletteGraph.resetCells(cells);
134
135           let idx = 0;
136           cells.forEach(cell => {
137             cell.translate(5, (cell.attributes.size.height + 5) * idx++);
138           });
139         }
140       });
141
142     this.designerStore.state$
143       .pipe(
144         distinctUntilChanged((a: any, b: any) => JSON.stringify(a) === JSON.stringify(b)),
145         takeUntil(this.ngUnsubscribe))
146       .subscribe(state => {
147         if (state.sourceContent) {
148           console.log('inside desinger.component---> ', state);
149           // generate graph from store objects if exist
150           const topologtTemplate = JSON.parse(state.sourceContent);
151           console.log(topologtTemplate);
152           delete state.sourceContent;
153           this.graphGenerator.populate(topologtTemplate, this.boardGraph);
154
155           console.log('all cells', this.boardGraph.getCells());
156           /**
157            * auto arrange elements in graph
158            * https://resources.jointjs.com/docs/jointjs/v3.1/joint.html#layout.DirectedGraph
159            */
160           joint.layout.DirectedGraph.layout( this.boardGraph.getCells(), {
161             dagre,
162             graphlib,
163             setLinkVertices: false,
164             marginX: 10,
165             marginY: 10,
166             clusterPadding: { top: 100, left: 30, right: 10, bottom: 100 },
167             rankDir: 'TB'
168           });
169         }
170       });
171
172     // action triggering
173     this.functionStore.retrieveFuntions();
174
175   }
176
177   initializePalette() {
178     if (!this.paletteGraph) {
179       this.paletteGraph = new joint.dia.Graph();
180       this.palettePaper = new joint.dia.Paper({
181         el: $('#palette-paper'),
182         model: this.paletteGraph,
183         width: 318,
184         height: $('#palette-paper').height(),
185         // background: {
186         //   color: 'rgba(0, 255, 0, 0.3)'
187         // },
188         interactive: false
189         // elements in paletter need to be fixed, please refer to flying paper concept
190       });
191     }
192   }
193
194   initializeBoard() {
195     if (!this.boardGraph) {
196       console.log('initializeBoard...');
197       this.boardGraph = new joint.dia.Graph();
198       this.boardPaper = new joint.dia.Paper({
199           el: $('#board-paper'),
200           model: this.boardGraph,
201           height: 720,
202           width: 1100,
203           gridSize: 10,
204           drawGrid: true,
205           // background: {
206           //   color: 'rgba(0, 255, 0, 0.3)'
207           // },
208           cellViewNamespace: joint.shapes
209         });
210
211       this.boardPaper.on('all', element => {
212         // console.log(element);
213       });
214
215       this.boardPaper.on('link:pointerdown', link => {
216         console.log(link);
217       });
218
219       this.boardPaper.on('element:pointerdown', element => {
220         // this.modelSelected.emit(element.model.get('model'));
221       });
222
223       this.boardPaper.on('blank:pointerclick', () => {
224         // this.selectedModel = undefined;
225       });
226
227       this.boardGraph.on('change:position', (cell) => {
228
229         const parentId = cell.get('parent');
230         if (!parentId) {
231           // this is action
232           return;
233         }
234
235         const parent = this.boardGraph.getCell(parentId);
236
237         const parentBbox = parent.getBBox();
238         const cellBbox = cell.getBBox();
239         if (parentBbox.containsPoint(cellBbox.origin()) &&
240           parentBbox.containsPoint(cellBbox.topRight()) &&
241           parentBbox.containsPoint(cellBbox.corner()) &&
242           parentBbox.containsPoint(cellBbox.bottomLeft())) {
243
244           // All the four corners of the child are inside
245           // the parent area.
246           return;
247         }
248
249         // Revert the child position.
250         cell.set('position', cell.previous('position'));
251       });
252     }
253     console.log('done initializing Board...');
254   }
255
256   insertCustomActionIntoBoard() {
257     console.log('saving action to store action workflow....');
258     const actionName = this.graphUtil.generateNewActionName();
259     this.graphUtil.createCustomActionWithName(actionName, this.boardGraph);
260     this.designerStore.addDeclarativeWorkFlow(actionName);
261   }
262
263   stencilPaperEventListeners() {
264     this.palettePaper.on('cell:pointerdown', (draggedCell, pointerDownEvent, x, y) => {
265
266       $('body').append(`
267         <div id="flyPaper"
268             style="position:fixed;z-index:100;opacity:.7;pointer-event:none;background-color: transparent !important;"></div>`
269         );
270       const flyGraph = new joint.dia.Graph();
271       const flyPaper = new joint.dia.Paper({
272           el: $('#flyPaper'),
273           model: flyGraph,
274           interactive: true
275         });
276       const flyShape = draggedCell.model.clone();
277       const pos = draggedCell.model.position();
278       const offset = {
279         x: x - pos.x,
280         y: y - pos.y
281       };
282
283       flyShape.position(0, 0);
284       flyGraph.addCell(flyShape);
285       $('#flyPaper').offset({
286         left: pointerDownEvent.pageX - offset.x,
287         top: pointerDownEvent.pageY - offset.y
288       });
289       $('body').on('mousemove.fly', mouseMoveEvent => {
290         $('#flyPaper').offset({
291           left: mouseMoveEvent.pageX - offset.x,
292           top: mouseMoveEvent.pageY - offset.y
293         });
294       });
295
296       $('body').on('mouseup.fly', mouseupEvent => {
297         const mouseupX = mouseupEvent.pageX;
298         const mouseupY = mouseupEvent.pageY;
299         const target = this.boardPaper.$el.offset();
300         // Dropped over paper ?
301         if (mouseupX > target.left &&
302           mouseupX < target.left + this.boardPaper.$el.width() &&
303           mouseupY > target.top && y < target.top + this.boardPaper.$el.height()) {
304           const functionType = this.graphUtil.getFunctionTypeFromPaletteFunction(flyShape);
305           // step name is CDS realted terminology, please refer to tosca types
306           const stepName = functionType;
307           const functionElementForBoard = this.graphUtil.dropFunctionOverActionWithPosition(
308               stepName, functionType,
309               mouseupX, mouseupY,
310               target, offset,
311               this.boardGraph);
312
313           const parentCell = this.graphUtil.getParent(functionElementForBoard, this.boardPaper);
314
315           if (parentCell &&
316               parentCell.model.attributes.type === ActionElementTypeName &&
317             this.graphUtil.canEmpedMoreChildern(parentCell.model, this.boardGraph)) {
318
319             if (this.graphUtil.isEmptyParent(parentCell.model)) {
320               // first function in action
321               const actionName = parentCell.model.attributes.attrs['#label'].text;
322               this.designerStore.addStepToDeclarativeWorkFlow(actionName, stepName, functionType);
323               if (functionType === 'dg-generic') {
324                 this.designerStore.addDgGenericNodeTemplate(stepName);
325               } else {
326                 this.designerStore.addNodeTemplate(stepName, functionType);
327               }
328             } else {
329               // second action means there was a dg-generic node before
330               this.designerStore.addNodeTemplate(stepName, functionType);
331               // this will fail if multiple dg-generic were added
332               // TODO prevent multi functions of the same type inside the same action
333               const dgGenericNode = this.graphUtil.getDgGenericChild(parentCell.model, this.boardGraph)[0];
334               const dgGenericNodeName = this.graphUtil.getFunctionNameFromBoardFunction(dgGenericNode);
335               this.designerStore.addDgGenericDependency(dgGenericNodeName, stepName);
336             }
337
338
339             // Prevent recursive embedding.
340             if (parentCell &&
341               parentCell.model.get('parent') !== functionElementForBoard.id) {
342               parentCell.model.embed(functionElementForBoard);
343             }
344           } else {
345             console.log('function dropped outside action or not allowed, rolling back...');
346             alert('function dropped outside action or not allowed, rolling back...');
347             functionElementForBoard.remove();
348           }
349         }
350         $('body').off('mousemove.fly').off('mouseup.fly');
351         // flyShape.remove();
352         $('#flyPaper').remove();
353       });
354     });
355     console.log('done stencilPaperEventListeners()...');
356   }
357
358   ngOnDestroy() {
359     this.ngUnsubscribe.next();
360     this.ngUnsubscribe.complete();
361   }
362 }