thingsboard-memoizeit

Rule Chain UI: Copy/Paste support.

4/11/2018 4:12:52 PM

Details

diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js
index bd5858f..c515e3e 100644
--- a/ui/src/app/locale/locale.constant.js
+++ b/ui/src/app/locale/locale.constant.js
@@ -1208,6 +1208,7 @@ export default angular.module('thingsboard.locale', [])
                     "delete-selected-objects": "Delete selected nodes and connections",
                     "delete-selected": "Delete selected",
                     "select-all": "Select all",
+                    "copy-selected": "Copy selected",
                     "deselect-all": "Deselect all",
                     "rulenode-details": "Rule node details",
                     "debug-mode": "Debug mode",
diff --git a/ui/src/app/rulechain/rulechain.controller.js b/ui/src/app/rulechain/rulechain.controller.js
index f657216..d77d644 100644
--- a/ui/src/app/rulechain/rulechain.controller.js
+++ b/ui/src/app/rulechain/rulechain.controller.js
@@ -29,7 +29,7 @@ import addRuleNodeLinkTemplate from './add-link.tpl.html';
 
 /*@ngInject*/
 export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $timeout, $mdExpansionPanel, $window, $document, $mdDialog,
-                                    $filter, $translate, hotkeys, types, ruleChainService, Modelfactory, flowchartConstants,
+                                    $filter, $translate, hotkeys, types, ruleChainService, itembuffer, Modelfactory, flowchartConstants,
                                     ruleChain, ruleChainMetaData, ruleNodeComponents) {
 
     var vm = this;
@@ -140,6 +140,22 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
             subtitle: $translate.instant('rulechain.rulechain')
         };
         contextInfo.items = [];
+        contextInfo.items.push(
+            {
+                action: function ($event) {
+                    pasteRuleNodes($event);
+                },
+                enabled: itembuffer.hasRuleNodes(),
+                value: "action.paste",
+                icon: "content_paste",
+                shortcut: "M-V"
+            }
+        );
+        contextInfo.items.push(
+            {
+                divider: true
+            }
+        );
         if (objectsSelected()) {
             contextInfo.items.push(
                 {
@@ -154,6 +170,17 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
             );
             contextInfo.items.push(
                 {
+                    action: function (event) {
+                        copyRuleNodes(event);
+                    },
+                    enabled: true,
+                    value: "rulenode.copy-selected",
+                    icon: "content_copy",
+                    shortcut: "M-C"
+                }
+            );
+            contextInfo.items.push(
+                {
                     action: function () {
                         vm.modelservice.deleteSelected();
                     },
@@ -178,6 +205,11 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
         }
         contextInfo.items.push(
             {
+                divider: true
+            }
+        );
+        contextInfo.items.push(
+            {
                 action: function () {
                     vm.saveRuleChain();
                 },
@@ -222,6 +254,16 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
             );
             contextInfo.items.push(
                 {
+                    action: function (event) {
+                        copyNode(event, node);
+                    },
+                    enabled: true,
+                    value: "action.copy",
+                    icon: "content_copy"
+                }
+            );
+            contextInfo.items.push(
+                {
                     action: function () {
                         vm.canvasControl.modelservice.nodes.delete(node);
                     },
@@ -526,7 +568,7 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
                 if (destNode.component.type == types.ruleNodeType.RULE_CHAIN.value) {
                     deferred.reject();
                 } else {
-                    var res = $filter('filter')(vm.ruleChainModel.edges, {source: vm.inputConnectorId});
+                    var res = $filter('filter')(vm.ruleChainModel.edges, {source: vm.inputConnectorId}, true);
                     if (res && res.length) {
                         vm.modelservice.edges.delete(res[0]);
                     }
@@ -582,6 +624,112 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
         }
     }
 
+    function copyNode(event, node) {
+        var offset = angular.element(vm.canvasControl.modelservice.getCanvasHtmlElement()).offset();
+        var x = Math.round(event.clientX - offset.left);
+        var y = Math.round(event.clientY - offset.top);
+        itembuffer.copyRuleNodes(x, y, [node], []);
+    }
+
+    function copyRuleNodes(event) {
+        var offset = angular.element(vm.canvasControl.modelservice.getCanvasHtmlElement()).offset();
+        var x = Math.round(event.clientX - offset.left);
+        var y = Math.round(event.clientY - offset.top);
+        var nodes = vm.modelservice.nodes.getSelectedNodes();
+        var edges = vm.modelservice.edges.getSelectedEdges();
+        var connections = [];
+        for (var i=0;i<edges.length;i++) {
+            var edge = edges[i];
+            var sourceNode = vm.modelservice.nodes.getNodeByConnectorId(edge.source);
+            var destNode = vm.modelservice.nodes.getNodeByConnectorId(edge.destination);
+            var isInputSource = sourceNode.component.type == types.ruleNodeType.INPUT.value;
+            var fromIndex = nodes.indexOf(sourceNode);
+            var toIndex = nodes.indexOf(destNode);
+            if ( (isInputSource || fromIndex > -1) && toIndex > -1 ) {
+                var connection = {
+                    isInputSource: isInputSource,
+                    fromIndex: fromIndex,
+                    toIndex: toIndex,
+                    label: edge.label
+                };
+                connections.push(connection);
+            }
+        }
+        itembuffer.copyRuleNodes(x, y, nodes, connections);
+    }
+
+    function pasteRuleNodes(event) {
+        var offset = angular.element(vm.canvasControl.modelservice.getCanvasHtmlElement()).offset();
+        var x = Math.round(event.clientX - offset.left);
+        var y = Math.round(event.clientY - offset.top);
+        var ruleNodes = itembuffer.pasteRuleNodes(x, y, event);
+        if (ruleNodes) {
+            vm.modelservice.deselectAll();
+            var nodes = [];
+            for (var i=0;i<ruleNodes.nodes.length;i++) {
+                var node = ruleNodes.nodes[i];
+                node.id = 'rule-chain-node-' + vm.nextNodeID++;
+                var component = node.component;
+                if (component.configurationDescriptor.nodeDefinition.inEnabled) {
+                    node.connectors.push(
+                        {
+                            type: flowchartConstants.leftConnectorType,
+                            id: vm.nextConnectorID++
+                        }
+                    );
+                }
+                if (component.configurationDescriptor.nodeDefinition.outEnabled) {
+                    node.connectors.push(
+                        {
+                            type: flowchartConstants.rightConnectorType,
+                            id: vm.nextConnectorID++
+                        }
+                    );
+                }
+                nodes.push(node);
+                vm.ruleChainModel.nodes.push(node);
+                vm.modelservice.nodes.select(node);
+            }
+            for (i=0;i<ruleNodes.connections.length;i++) {
+                var connection = ruleNodes.connections[i];
+                var sourceNode = nodes[connection.fromIndex];
+                var destNode = nodes[connection.toIndex];
+                if ( (connection.isInputSource || sourceNode) &&  destNode ) {
+                    var source, destination;
+                    if (connection.isInputSource) {
+                        source = vm.inputConnectorId;
+                    } else {
+                        var sourceConnectors = vm.modelservice.nodes.getConnectorsByType(sourceNode, flowchartConstants.rightConnectorType);
+                        if (sourceConnectors && sourceConnectors.length) {
+                            source = sourceConnectors[0].id;
+                        }
+                    }
+                    var destConnectors = vm.modelservice.nodes.getConnectorsByType(destNode, flowchartConstants.leftConnectorType);
+                    if (destConnectors && destConnectors.length) {
+                        destination = destConnectors[0].id;
+                    }
+                    if (source && destination) {
+                        var edge = {
+                            source: source,
+                            destination: destination,
+                            label: connection.label
+                        };
+                        vm.ruleChainModel.edges.push(edge);
+                        vm.modelservice.edges.select(edge);
+                    }
+                }
+            }
+
+            if (vm.canvasControl.adjustCanvasSize) {
+                vm.canvasControl.adjustCanvasSize();
+            }
+
+            updateRuleNodesHighlight();
+
+            validate();
+        }
+    }
+
     loadRuleChainLibrary(ruleNodeComponents, true);
 
     $scope.$watch('vm.ruleNodeSearch',
diff --git a/ui/src/app/rulechain/rulechain.scss b/ui/src/app/rulechain/rulechain.scss
index b89557b..257a97d 100644
--- a/ui/src/app/rulechain/rulechain.scss
+++ b/ui/src/app/rulechain/rulechain.scss
@@ -108,11 +108,14 @@
 #tb-rule-chain-context-menu {
   padding-top: 0px;
   border-radius: 8px;
+  max-height: 404px;
   .tb-context-menu-header {
     padding: 8px 5px 5px;
     font-size: 14px;
     display: flex;
     flex-direction: row;
+    height: 30px;
+    min-height: 30px;
     &.tb-rulechain {
       background-color: #aac7e4;
     }
diff --git a/ui/src/app/rulechain/rulechain.tpl.html b/ui/src/app/rulechain/rulechain.tpl.html
index 51d8d71..81e8aba 100644
--- a/ui/src/app/rulechain/rulechain.tpl.html
+++ b/ui/src/app/rulechain/rulechain.tpl.html
@@ -122,7 +122,8 @@
                         </div>
                     </div>
                     <div ng-repeat="item in vm.contextInfo.items">
-                        <md-menu-item>
+                        <md-divider ng-if="item.divider"></md-divider>
+                        <md-menu-item ng-if="!item.divider">
                             <md-button ng-disabled="!item.enabled" ng-click="item.action(vm.contextMenuEvent)">
                                 <md-icon ng-if="item.icon" md-menu-align-target aria-label="{{ item.value | translate }}" class="material-icons">{{item.icon}}</md-icon>
                                 <span translate>{{item.value}}</span>
diff --git a/ui/src/app/services/item-buffer.service.js b/ui/src/app/services/item-buffer.service.js
index 9fce811..d6a9ce7 100644
--- a/ui/src/app/services/item-buffer.service.js
+++ b/ui/src/app/services/item-buffer.service.js
@@ -24,10 +24,11 @@ export default angular.module('thingsboard.itembuffer', [angularStorage])
     .name;
 
 /*@ngInject*/
-function ItemBuffer($q, bufferStore, types, utils, dashboardUtils) {
+function ItemBuffer($q, bufferStore, types, utils, dashboardUtils, ruleChainService) {
 
     const WIDGET_ITEM = "widget_item";
     const WIDGET_REFERENCE = "widget_reference";
+    const RULE_NODES = "rule_nodes";
 
     var service = {
         prepareWidgetItem: prepareWidgetItem,
@@ -37,7 +38,10 @@ function ItemBuffer($q, bufferStore, types, utils, dashboardUtils) {
         canPasteWidgetReference: canPasteWidgetReference,
         pasteWidget: pasteWidget,
         pasteWidgetReference: pasteWidgetReference,
-        addWidgetToDashboard: addWidgetToDashboard
+        addWidgetToDashboard: addWidgetToDashboard,
+        copyRuleNodes: copyRuleNodes,
+        hasRuleNodes: hasRuleNodes,
+        pasteRuleNodes: pasteRuleNodes
     }
 
     return service;
@@ -151,6 +155,69 @@ function ItemBuffer($q, bufferStore, types, utils, dashboardUtils) {
         };
     }
 
+    function copyRuleNodes(x, y, nodes, connections) {
+        var ruleNodes = {
+            nodes: [],
+            connections: [],
+            originX: x,
+            originY: y
+        };
+        for (var i=0;i<nodes.length;i++) {
+            var origNode = nodes[i];
+            var node = {
+                additionalInfo: origNode.additionalInfo,
+                configuration: origNode.configuration,
+                debugMode: origNode.debugMode,
+                x: origNode.x,
+                y: origNode.y,
+                name: origNode.name,
+                componentClazz: origNode.component.clazz,
+            };
+            if (origNode.targetRuleChainId) {
+                node.targetRuleChainId = origNode.targetRuleChainId;
+            }
+            if (origNode.error) {
+                node.error = origNode.error;
+            }
+            ruleNodes.nodes.push(node);
+        }
+        for (i=0;i<connections.length;i++) {
+            var connection = connections[i];
+            ruleNodes.connections.push(connection);
+        }
+        bufferStore.set(RULE_NODES, angular.toJson(ruleNodes));
+    }
+
+    function hasRuleNodes() {
+        return bufferStore.get(RULE_NODES);
+    }
+
+    function pasteRuleNodes(x, y) {
+        var ruleNodesJson = bufferStore.get(RULE_NODES);
+        if (ruleNodesJson) {
+            var ruleNodes = angular.fromJson(ruleNodesJson);
+            var deltaX = x - ruleNodes.originX;
+            var deltaY = y - ruleNodes.originY;
+            for (var i=0;i<ruleNodes.nodes.length;i++) {
+                var node = ruleNodes.nodes[i];
+                var component = ruleChainService.getRuleNodeComponentByClazz(node.componentClazz);
+                if (component) {
+                    delete node.componentClazz;
+                    node.component = component;
+                    node.nodeClass = types.ruleNodeType[component.type].nodeClass;
+                    node.icon = types.ruleNodeType[component.type].icon;
+                    node.connectors = [];
+                    node.x = Math.round(node.x + deltaX);
+                    node.y = Math.round(node.y + deltaY);
+                } else {
+                    return null;
+                }
+            }
+            return ruleNodes;
+        }
+        return null;
+    }
+
     function copyWidget(dashboard, sourceState, sourceLayout, widget) {
         var widgetItem = prepareWidgetItem(dashboard, sourceState, sourceLayout, widget);
         bufferStore.set(WIDGET_ITEM, angular.toJson(widgetItem));