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