From f96340e651b01f77107249e29d2e62cb770ebd3b Mon Sep 17 00:00:00 2001 From: Ahmed Abbas Date: Fri, 27 Dec 2019 09:55:08 +0200 Subject: create palette on the right side menu draw function elements to the palette (static functions for now) enable drap elements from palette to main board Issue-ID: CCSDK-1783 Signed-off-by: Ahmed Abbas Change-Id: I5e3382369dd86fa9b748e8b9b922eead39c8dc42 --- .../packages/designer/designer.component.html | 33 ++- .../packages/designer/designer.component.ts | 328 +++++++++++++++------ .../packages/designer/palette.function.element.ts | 108 +++++++ cds-ui/designer-client/src/styles.css | 8 +- 4 files changed, 383 insertions(+), 94 deletions(-) create mode 100644 cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/palette.function.element.ts (limited to 'cds-ui/designer-client') 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 9021bf5e9..c0ea41dbc 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 @@ -73,7 +73,7 @@ - - - + +

Functions

+
+ Drag and drop function to Action’s box +
    + +
  • +

    component-netconf-executor

    +
  • +
  • +

    component-remote-ansible-executor

    +
  • +
  • +

    dg-generic

    +
  • +
  • +

    component-resource-resolution

    +
  • +
+
-
+
-
+
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 6d36a961f..f3e592cdb 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,4 +1,7 @@ import { Component, OnInit, ViewEncapsulation } from '@angular/core'; +import * as _ from 'lodash'; +import * as joint from 'jointjs'; +import './palette.function.element'; @Component({ selector: 'app-designer', @@ -10,6 +13,13 @@ export class DesignerComponent implements OnInit { private controllerSideBar: boolean; private attributesSideBar: boolean; + + boardGraph: joint.dia.Graph; + boardPaper: joint.dia.Paper; + + paletteGraph: joint.dia.Graph; + palettePaper: joint.dia.Paper; + constructor() { this.controllerSideBar = true; this.attributesSideBar = false; @@ -22,101 +32,247 @@ export class DesignerComponent implements OnInit { } + /** + * - There is a board (main paper) that will the action and function selected from the palette + * itmes in this board will be used to create tosca workflow and node templates + * - There is also palette , whis contains all the possible functions and actions + * that can be dragged into the board + * - There is also a fly paper , which is temporarliy paper created on the fly + * when items is dragged from the palette- and it's deleted when the item is dropped over + * the board. + * for more info about the drag and drop algorithem used please visit the following link: + * https://stackoverflow.com/a/36932973/1340034 + */ + ngOnInit() { + this.initializeBoard(); + this.initializePalette(); + // this.createEditBarOverThePaper(); + 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(); + } + + initializePalette() { + if (!this.paletteGraph) { + this.paletteGraph = new joint.dia.Graph(); + this.palettePaper = new joint.dia.Paper({ + el: $('#palette-paper'), + model: this.paletteGraph, + height: 300, + width: 300, + gridSize: 1, + interactive: false + }); + } + } + + initializeBoard() { + if (!this.boardGraph) { + this.boardGraph = new joint.dia.Graph(); + this.boardPaper = new joint.dia.Paper({ + el: $('#board-paper'), + model: this.boardGraph, + height: 720, + width: 1200, + gridSize: 10, + drawGrid: true, + background: { + color: 'rgba(0, 255, 0, 0.3)' + }, + cellViewNamespace: joint.shapes + }); + + this.boardPaper.on('all', element => { + // console.log(element); + }); + + this.boardPaper.on('link:pointerdown', link => { + console.log(link); + }); + + this.boardPaper.on('element:pointerdown', element => { + // this.modelSelected.emit(element.model.get('model')); + }); + + this.boardPaper.on('blank:pointerclick', () => { + // this.selectedModel = undefined; + }); + } + } + + buildPaletteGraphFromList(list: any) { + const elements = []; + + console.log(list); + list.forEach(element => { + elements.push(this.createFuctionElementForPalette(element.modelName)); + }); + + return elements; + } - this.attachEditorBarToCanvas(); + + createFuctionElementForPalette(label: string) { + const element = new joint.shapes.app.FunctionElement({ + id: label}); + element.attr('#label/text', label); + return element; } - attachEditorBarToCanvas() { - this.graph = new joint.dia.Graph, - this.paper = new joint.dia.Paper({ - el: $('#paper'), - model: this.graph, - height: 720, - width: 1200, - gridSize: 2, - drawGrid: true, - cellViewNamespace: joint.shapes + stencilPaperEventListeners() { + this.palettePaper.on('cell:pointerdown', (draggedCell, pointerDownEvent, x, y) => { + console.log('pointerdown 2'); + + $('body').append(` +
` + ); + const flyGraph = new joint.dia.Graph(); + const flyPaper = new joint.dia.Paper({ + el: $('#flyPaper'), + model: flyGraph, + interactive: true + }); + const flyShape = draggedCell.model.clone(); + const pos = draggedCell.model.position(); + const offset = { + x: x - pos.x, + y: y - pos.y + }; + + flyShape.position(0, 0); + flyGraph.addCell(flyShape); + $('#flyPaper').offset({ + left: pointerDownEvent.pageX - offset.x, + top: pointerDownEvent.pageY - offset.y + }); + $('body').on('mousemove.fly', mouseMoveEvent => { + $('#flyPaper').offset({ + left: mouseMoveEvent.pageX - offset.x, + top: mouseMoveEvent.pageY - offset.y + }); }); - this.paper.setGrid({ - name: 'dot', - args: - { color: 'black', thickness: 2, scaleFactor: 8 } + $('body').on('mouseup.fly', mouseupEvent => { + const mouseupX = mouseupEvent.pageX; + const mouseupY = mouseupEvent.pageY; + const target = this.boardPaper.$el.offset(); + // Dropped over paper ? + if (mouseupX > target.left && + mouseupX < target.left + this.boardPaper.$el.width() && + mouseupY > target.top && y < target.top + this.boardPaper.$el.height()) { + const clonedShape = flyShape.clone(); - }).drawGrid(); + clonedShape.position(mouseupX - target.left - offset.x, mouseupY - target.top - offset.y); + this.boardGraph.addCell(clonedShape); + const cellViewsBelow = + this.boardPaper.findViewsFromPoint(clonedShape.getBBox().center()); + if (cellViewsBelow.length) { + let cellViewBelow; + cellViewsBelow.forEach( cellItem => { + if (cellItem.model.id !== clonedShape.id) { + cellViewBelow = cellItem; + } + }); + // Prevent recursive embedding. + if (cellViewBelow && cellViewBelow.model.get('parent') !== clonedShape.id) { + cellViewBelow.model.embed(clonedShape); + } + } - 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) + } + $('body').off('mousemove.fly').off('mouseup.fly'); + // flyShape.remove(); + $('#flyPaper').remove(); + }); }); + } + + /** + * 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: [ + // '
', + // '
', + // '
', + // '', + // '', + // '
', + // '
', + // '', + // '', + // '', + // '
', + // '
', + // '', + // '', + // '
', + // '
', + // '
' + // ].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)' + // }); + // } + // }); - joint.shapes["html"].ElementView = joint.dia.ElementView.extend({ - - template: [ - '
', - '
', - '
', - '', - '', - '
', - '
', - '', - '', - '', - '
', - '
', - '', - '', - '
', - '
', - '
' - ].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)' - }); - } - }); - - var el1 = new joint.shapes["html"].Element({}); - this.graph.addCells([el1]); - } + // } } diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/palette.function.element.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/palette.function.element.ts new file mode 100644 index 000000000..6f0ba8b5d --- /dev/null +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/palette.function.element.ts @@ -0,0 +1,108 @@ +import * as joint from 'jointjs'; + +/** + * The function element in the palette should contain image and text with style- that is why + * it requires a custom element. + * The following code declares a type for typescript to accept "app.FunctionElement" + * and create an element implementation , then joins the app module inside shapes module for javascript to work. + * please refer to the official example : + * https://github.com/clientIO/joint/blob/master/demo/ts-demo/custom.ts + */ + +declare module 'jointjs' { + namespace shapes { + // add new module called "app" under the already existing "shapes" modeule inside jointjs + export namespace app { + class FunctionElement extends joint.shapes.standard.Rectangle { + } + } + } +} + +const rectWidth = 260; +const rectHeight = 60; +// custom element implementation +// https://resources.jointjs.com/tutorials/joint/tutorials/custom-elements.html#markup +const FunctionElement = joint.shapes.standard.Rectangle.define('app.FunctionElement', { + size: { width: rectWidth, height: rectHeight }, + attrs: { + icon: { + 'xlink:href': 'http://placehold.it/16x16' + } + } +}, { + markup: + ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ` + // [{ + // tagName: 'rect', + // selector: 'body', + // }, { + // tagName: 'text', + // selector: 'label' + // }, { + // tagName: 'image', + // selector: 'icon' + // }, { + // tagName: 'image', + // selector: 'drag-handle' + // } + // ] +}); + +Object.assign(joint.shapes, { + app: { + FunctionElement + } +}); diff --git a/cds-ui/designer-client/src/styles.css b/cds-ui/designer-client/src/styles.css index b6fc4eea7..838adeb69 100644 --- a/cds-ui/designer-client/src/styles.css +++ b/cds-ui/designer-client/src/styles.css @@ -6,7 +6,8 @@ body{ /* background-image: linear-gradient(-45deg, #000 10%, #fff 0); background-size: 6px 6px; */ margin: 0; - font-family: 'Nunito' !important; + /* font-family: 'Nunito' !important; */ + font-family: Arial, Helvetica, sans-serif !important; color: #1B3E6F !important; } *:focus{ @@ -1391,6 +1392,11 @@ height: 40px; margin-left: 0; margin-bottom: -16px; } + +/*JOINT JS*/ +/* .joint-paper.joint-theme-default{ */ + /* background-color: transparent !important; */ +/* } */ /* Extra small devices (portrait phones, less than 576px) */ @media (max-width: 575.98px) { .page-title{ -- cgit 1.2.3-korg