2 ============LICENSE_START==========================================
3 ===================================================================
4 Copyright (C) 2019 Orange. All rights reserved.
5 ===================================================================
6 Modification Copyright (c) 2020 IBM
7 ===================================================================
8 Modification Copyright (c) 2020 Orange
9 ===================================================================
11 Unless otherwise specified, all software contained herein is licensed
12 under the Apache License, Version 2.0 (the License);
13 you may not use this software except in compliance with the License.
14 You may obtain a copy of the License at
16 http://www.apache.org/licenses/LICENSE-2.0
18 Unless required by applicable law or agreed to in writing, software
19 distributed under the License is distributed on an "AS IS" BASIS,
20 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21 See the License for the specific language governing permissions and
22 limitations under the License.
23 ============LICENSE_END============================================
26 import dagre from 'dagre';
27 import graphlib from 'graphlib';
28 import {Component, OnDestroy, OnInit, ViewEncapsulation} from '@angular/core';
29 import * as joint from 'jointjs';
30 import './jointjs/elements/palette.function.element';
31 import './jointjs/elements/action.element';
32 import './jointjs/elements/board.function.element';
33 import {DesignerStore} from './designer.store';
34 import {ActionElementTypeName} from 'src/app/common/constants/app-constants';
35 import {GraphUtil} from './graph.util';
36 import {GraphGenerator} from './graph.generator.util';
37 import {FunctionsStore} from './functions.store';
38 import {Subject} from 'rxjs';
39 import {distinctUntilChanged, takeUntil} from 'rxjs/operators';
40 import {BluePrintDetailModel} from '../model/BluePrint.detail.model';
41 import {ActivatedRoute, Router} from '@angular/router';
42 import {DesignerService} from './designer.service';
43 import {FilesContent, FolderNodeElement} from '../package-creation/mapping-models/metadata/MetaDataTab.model';
44 import {PackageCreationModes} from '../package-creation/creationModes/PackageCreationModes';
45 import {PackageCreationBuilder} from '../package-creation/creationModes/PackageCreationBuilder';
46 import {PackageCreationStore} from '../package-creation/package-creation.store';
47 import {PackageCreationService} from '../package-creation/package-creation.service';
48 import {PackageCreationUtils} from '../package-creation/package-creation.utils';
49 import * as JSZip from 'jszip';
50 import {PackageCreationExtractionService} from '../package-creation/package-creation-extraction.service';
51 import {CBAPackage} from '../package-creation/mapping-models/CBAPacakge.model';
52 import {TopologyTemplate} from './model/designer.topologyTemplate.model';
53 import {ToastrService} from 'ngx-toastr';
54 import {DesignerDashboardState} from './model/designer.dashboard.state';
57 selector: 'app-designer',
58 templateUrl: './designer.component.html',
59 styleUrls: ['./designer.component.css'],
60 encapsulation: ViewEncapsulation.None
62 export class DesignerComponent implements OnInit, OnDestroy {
64 controllerSideBar: boolean;
65 actionAttributesSideBar: boolean;
66 functionAttributeSidebar: boolean;
67 viewedPackage: BluePrintDetailModel = new BluePrintDetailModel();
68 customActionName: string;
72 boardGraph: joint.dia.Graph;
73 boardPaper: joint.dia.Paper;
75 paletteGraph: joint.dia.Graph;
76 palettePaper: joint.dia.Paper;
77 ngUnsubscribe = new Subject();
78 opt = {tx: 100, ty: 100};
80 folder: FolderNodeElement = new FolderNodeElement();
81 zipFile: JSZip = new JSZip();
82 cbaPackage: CBAPackage;
83 actions: string[] = [];
86 designerState: DesignerDashboardState;
87 currentActionName: string;
90 private designerStore: DesignerStore,
91 private functionStore: FunctionsStore,
92 private packageCreationStore: PackageCreationStore,
93 private packageCreationUtils: PackageCreationUtils,
94 private graphUtil: GraphUtil,
95 private graphGenerator: GraphGenerator,
96 private route: ActivatedRoute,
97 private router: Router,
98 private designerService: DesignerService,
99 private packageCreationService: PackageCreationService,
100 private packageCreationExtractionService: PackageCreationExtractionService,
101 private toastService: ToastrService) {
102 this.controllerSideBar = true;
103 this.actionAttributesSideBar = false;
104 this.showAction = false;
105 this.functionAttributeSidebar = false;
110 this.controllerSideBar = !this.controllerSideBar;
111 if (this.controllerSideBar === false) {
112 this.cl = 'editBar2';
114 if (this.controllerSideBar === true) {
120 this.actionAttributesSideBar = !this.actionAttributesSideBar;
125 this.zipFile.generateAsync({type: 'blob'})
127 const formData = new FormData();
128 formData.append('file', blob);
129 this.designerService.publishBlueprint(formData).subscribe(res => {
130 console.log('Package Deployed...');
134 // this.deployBluePrint = false;
139 // private _toggleSidebar3() {
140 // this.functionAttributeSidebar = !this.functionAttributeSidebar;
145 * - There is a board (main paper) that will the action and function selected from the palette
146 * itmes in this board will be used to create tosca workflow and node templates
147 * - There is also palette , whis contains all the possible functions and actions
148 * that can be dragged into the board
149 * - There is also a fly paper , which is temporarliy paper created on the fly
150 * when items is dragged from the palette- and it's deleted when the item is dropped over
152 * for more info about the drag and drop algorithem used please visit the following link:
153 * https://stackoverflow.com/a/36932973/1340034
157 this.customActionName = this.route.snapshot.paramMap.get('actionName');
158 if (this.customActionName !== '') {
159 this.showAction = true;
161 this.initializeBoard();
162 this.initializePalette();
163 this.stencilPaperEventListeners();
164 const id = this.route.snapshot.paramMap.get('id');
165 this.designerService.getPagedPackages(id).subscribe(
166 (bluePrintDetailModels) => {
167 if (bluePrintDetailModels) {
168 this.viewedPackage = bluePrintDetailModels[0];
169 this.packageCreationService.downloadPackage(this.viewedPackage.artifactName + '/'
170 + this.viewedPackage.artifactVersion)
171 .subscribe(response => {
172 const blob = new Blob([response], {type: 'application/octet-stream'});
173 this.packageCreationExtractionService.extractBlobToStore(blob);
177 this.packageCreationStore.state$.subscribe(cba => {
178 this.cbaPackage = cba;
179 console.log(cba.templateTopology.content);
180 this.designerStore.saveSourceContent(cba.templateTopology.content);
185 * the code to retrieve from server is commented
187 this.functionStore.state$
189 distinctUntilChanged((a: any, b: any) => JSON.stringify(a) === JSON.stringify(b)),
190 takeUntil(this.ngUnsubscribe))
191 .subscribe(state => {
193 if (state.serverFunctions) {
194 console.log('inside subscriotn on functions store -->', state.serverFunctions);
196 // this.viewedFunctions = state.functions;
197 const list = state.serverFunctions;
199 const cells = this.graphUtil.buildPaletteGraphFromList(list);
200 this.paletteGraph.resetCells(cells);
203 cells.forEach(cell => {
204 cell.translate(5, (cell.attributes.size.height + 5) * idx++);
209 this.designerStore.state$
211 distinctUntilChanged((a: any, b: any) => JSON.stringify(a) === JSON.stringify(b)),
212 takeUntil(this.ngUnsubscribe))
213 .subscribe(state => {
214 this.designerState = state;
215 if (state.sourceContent) {
216 console.log('inside desinger.component---> ', state);
217 // generate graph from store objects if exist
218 const topologtTemplate: TopologyTemplate = JSON.parse(state.sourceContent);
219 console.log(topologtTemplate);
220 delete state.sourceContent;
221 this.graphGenerator.clear(this.boardGraph);
222 this.graphGenerator.populate(topologtTemplate, this.boardGraph);
224 console.log('all cells', this.boardGraph.getCells());
226 * auto arrange elements in graph
227 * https://resources.jointjs.com/docs/jointjs/v3.1/joint.html#layout.DirectedGraph
229 joint.layout.DirectedGraph.layout(this.boardGraph.getCells(), {
232 setLinkVertices: false,
235 clusterPadding: {top: 100, left: 30, right: 10, bottom: 100},
240 for (const workflowsKey in topologtTemplate.workflows) {
241 if (workflowsKey && !this.actions.includes(workflowsKey)) {
242 this.actions.push(workflowsKey);
249 this.functionStore.retrieveFuntions();
253 initializePalette() {
254 if (!this.paletteGraph) {
255 this.paletteGraph = new joint.dia.Graph();
256 this.palettePaper = new joint.dia.Paper({
257 el: $('#palette-paper'),
258 model: this.paletteGraph,
261 height: $('#palette-paper').height(),
263 // color: 'rgba(0, 255, 0, 0.3)'
266 // elements in paletter need to be fixed, please refer to flying paper concept
272 if (!this.boardGraph) {
273 console.log('initializeBoard...');
274 this.boardGraph = new joint.dia.Graph();
275 this.boardPaper = new joint.dia.Paper({
276 el: $('#board-paper'),
277 model: this.boardGraph,
283 // color: 'rgba(0, 255, 0, 0.3)'
285 cellViewNamespace: joint.shapes
288 this.boardPaper.on('all', element => {
289 // console.log(element);
292 this.boardPaper.on('link:pointerdown', link => {
296 this.boardPaper.on('element:pointerdown', element => {
297 // this.modelSelected.emit(element.model.get('model'));
300 this.boardPaper.on('blank:pointerclick', () => {
301 // this.selectedModel = undefined;
304 this.boardGraph.on('change:position', (cell) => {
306 const parentId = cell.get('parent');
312 const parent = this.boardGraph.getCell(parentId);
314 const parentBbox = parent.getBBox();
315 const cellBbox = cell.getBBox();
316 if (parentBbox.containsPoint(cellBbox.origin()) &&
317 parentBbox.containsPoint(cellBbox.topRight()) &&
318 parentBbox.containsPoint(cellBbox.corner()) &&
319 parentBbox.containsPoint(cellBbox.bottomLeft())) {
321 // All the four corners of the child are inside
326 // Revert the child position.
327 cell.set('position', cell.previous('position'));
330 console.log('done initializing Board...');
333 insertCustomActionIntoBoard() {
334 console.log('saving action to store action workflow....');
335 let actionName = this.graphUtil.generateNewActionName();
336 while (this.actions.includes(actionName)) {
337 actionName = this.graphUtil.generateNewActionName();
339 this.graphUtil.createCustomActionWithName(actionName, this.boardGraph);
340 this.designerStore.addDeclarativeWorkFlow(actionName);
341 this.actions.push(actionName);
344 stencilPaperEventListeners() {
345 this.palettePaper.on('cell:pointerdown', (draggedCell, pointerDownEvent, x, y) => {
349 style="position:fixed;z-index:100;opacity:.7;pointer-event:none;background-color: transparent !important;"></div>`
351 const flyGraph = new joint.dia.Graph();
352 const flyPaper = new joint.dia.Paper({
357 const flyShape = draggedCell.model.clone();
358 const pos = draggedCell.model.position();
364 flyShape.position(0, 0);
365 flyGraph.addCell(flyShape);
366 $('#flyPaper').offset({
367 left: pointerDownEvent.pageX - offset.x,
368 top: pointerDownEvent.pageY - offset.y
370 $('body').on('mousemove.fly', mouseMoveEvent => {
371 $('#flyPaper').offset({
372 left: mouseMoveEvent.pageX - offset.x,
373 top: mouseMoveEvent.pageY - offset.y
377 $('body').on('mouseup.fly', mouseupEvent => {
378 const mouseupX = mouseupEvent.pageX;
379 const mouseupY = mouseupEvent.pageY;
380 const target = this.boardPaper.$el.offset();
381 // Dropped over paper ?
382 if (mouseupX > target.left &&
383 mouseupX < target.left + this.boardPaper.$el.width() &&
384 mouseupY > target.top && y < target.top + this.boardPaper.$el.height()) {
385 const functionType = this.graphUtil.getFunctionTypeFromPaletteFunction(flyShape);
386 // step name is CDS realted terminology, please refer to tosca types
387 const stepName = functionType;
388 const functionElementForBoard = this.graphUtil.dropFunctionOverActionWithPosition(
389 stepName, functionType,
394 const parentCell = this.graphUtil.getParent(functionElementForBoard, this.boardPaper);
397 parentCell.model.attributes.type === ActionElementTypeName &&
398 this.graphUtil.canEmpedMoreChildern(parentCell.model, this.boardGraph)) {
400 if (this.graphUtil.isEmptyParent(parentCell.model)) {
401 // first function in action
402 const actionName = parentCell.model.attributes.attrs['#label'].text;
403 this.designerStore.addStepToDeclarativeWorkFlow(actionName, stepName, functionType);
404 if (functionType === 'dg-generic') {
405 this.designerStore.addDgGenericNodeTemplate(stepName);
407 this.designerStore.addNodeTemplate(stepName, functionType);
410 // second action means there was a dg-generic node before
411 this.designerStore.addNodeTemplate(stepName, functionType);
412 // this will fail if multiple dg-generic were added
413 // TODO prevent multi functions of the same type inside the same action
414 const dgGenericNode = this.graphUtil.getDgGenericChild(parentCell.model, this.boardGraph)[0];
415 const dgGenericNodeName = this.graphUtil.getFunctionNameFromBoardFunction(dgGenericNode);
416 this.designerStore.addDgGenericDependency(dgGenericNodeName, stepName);
420 // Prevent recursive embedding.
422 parentCell.model.get('parent') !== functionElementForBoard.id) {
423 parentCell.model.embed(functionElementForBoard);
426 console.log('function dropped outside action or not allowed, rolling back...');
427 alert('function dropped outside action or not allowed, rolling back...');
428 functionElementForBoard.remove();
431 $('body').off('mousemove.fly').off('mouseup.fly');
432 // flyShape.remove();
433 $('#flyPaper').remove();
436 console.log('done stencilPaperEventListeners()...');
440 this.ngUnsubscribe.next();
441 this.ngUnsubscribe.complete();
446 FilesContent.clear();
447 let packageCreationModes: PackageCreationModes;
448 this.cbaPackage = PackageCreationModes.mapModeType(this.cbaPackage);
449 this.cbaPackage.metaData = PackageCreationModes.setEntryPoint(this.cbaPackage.metaData);
450 packageCreationModes = PackageCreationBuilder.getCreationMode(this.cbaPackage);
451 this.designerStore.state$.subscribe(state => {
452 this.cbaPackage.templateTopology.content = this.packageCreationUtils.transformToJson(state.template);
454 packageCreationModes.execute(this.cbaPackage, this.packageCreationUtils);
455 this.filesData.push(this.folder.TREE_DATA);
456 this.saveBluePrintToDataBase();
461 this.zipFile = new JSZip();
462 FilesContent.getMapOfFilesNamesAndContent().forEach((value, key) => {
463 this.zipFile.folder(key.split('/')[0]);
464 this.zipFile.file(key, value);
469 saveBluePrintToDataBase() {
471 this.zipFile.generateAsync({type: 'blob'})
473 this.packageCreationService.savePackage(blob).subscribe(
474 bluePrintDetailModels => {
475 this.toastService.info('success updating the package');
476 const id = bluePrintDetailModels.toString().split('id')[1].split(':')[1].split('"')[1];
477 this.router.navigate(['/packages/designer/' + id]);
478 console.log('success');
480 this.toastService.error('error happened when editing ' + error.message);
481 console.log('Error -' + error.message);
486 openActionAttributes(customActionName: string) {
487 console.log('opening here action attributes');
488 this.currentActionName = customActionName;
489 this.actionAttributesSideBar = true;
490 this.functionAttributeSidebar = false;
491 this.designerStore.setCurrentAction(customActionName);
492 /* tslint:disable:no-string-literal */
493 this.steps = Object.keys(this.designerState.template.workflows[customActionName]['steps']);
496 openFunctionAttributes(customFunctionName: string) {
497 this.actionAttributesSideBar = false;
498 this.functionAttributeSidebar = true;
499 this.designerStore.setCurrentFunction(this.designerState.template.workflows[this.currentActionName]
500 ['steps'][customFunctionName]['target']);