thingsboard-developers

Details

diff --git a/ui/src/app/components/mousepoint-menu.directive.js b/ui/src/app/components/mousepoint-menu.directive.js
index 2a0ab52..9015268 100644
--- a/ui/src/app/components/mousepoint-menu.directive.js
+++ b/ui/src/app/components/mousepoint-menu.directive.js
@@ -27,7 +27,12 @@ function MousepointMenu() {
                 var offset = $element.offset();
                 var x = $event.pageX - offset.left;
                 var y = $event.pageY - offset.top;
-
+                if ($attrs.tbOffsetX) {
+                    x += Number($attrs.tbOffsetX);
+                }
+                if ($attrs.tbOffsetY) {
+                    y += Number($attrs.tbOffsetY);
+                }
                 var offsets = {
                     left: x,
                     top: y
diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js
index e7c0f58..bd5858f 100644
--- a/ui/src/app/locale/locale.constant.js
+++ b/ui/src/app/locale/locale.constant.js
@@ -1203,9 +1203,12 @@ export default angular.module('thingsboard.locale', [])
                     "type": "Type",
                     "description": "Description",
                     "delete": "Delete rule node",
-                    "select-all": "Select all nodes and connections",
-                    "deselect-all": "Deselect all nodes and connections",
+                    "select-all-objects": "Select all nodes and connections",
+                    "deselect-all-objects": "Deselect all nodes and connections",
                     "delete-selected-objects": "Delete selected nodes and connections",
+                    "delete-selected": "Delete selected",
+                    "select-all": "Select all",
+                    "deselect-all": "Deselect all",
                     "rulenode-details": "Rule node details",
                     "debug-mode": "Debug mode",
                     "configuration": "Configuration",
diff --git a/ui/src/app/rulechain/rulechain.controller.js b/ui/src/app/rulechain/rulechain.controller.js
index eaa14fb..f657216 100644
--- a/ui/src/app/rulechain/rulechain.controller.js
+++ b/ui/src/app/rulechain/rulechain.controller.js
@@ -134,10 +134,70 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
 
     function prepareRuleChainContextMenu() {
         var contextInfo = {
+            headerClass: 'tb-rulechain',
+            icon: 'settings_ethernet',
             title: vm.ruleChain.name,
             subtitle: $translate.instant('rulechain.rulechain')
         };
         contextInfo.items = [];
+        if (objectsSelected()) {
+            contextInfo.items.push(
+                {
+                    action: function () {
+                        vm.modelservice.deselectAll();
+                    },
+                    enabled: true,
+                    value: "rulenode.deselect-all",
+                    icon: "tab_unselected",
+                    shortcut: "Esc"
+                }
+            );
+            contextInfo.items.push(
+                {
+                    action: function () {
+                        vm.modelservice.deleteSelected();
+                    },
+                    enabled: true,
+                    value: "rulenode.delete-selected",
+                    icon: "clear",
+                    shortcut: "Del"
+                }
+            );
+        } else {
+            contextInfo.items.push(
+                {
+                    action: function () {
+                        vm.modelservice.selectAll();
+                    },
+                    enabled: true,
+                    value: "rulenode.select-all",
+                    icon: "select_all",
+                    shortcut: "M-A"
+                }
+            );
+        }
+        contextInfo.items.push(
+            {
+                action: function () {
+                    vm.saveRuleChain();
+                },
+                enabled: !(vm.isInvalid || (!vm.isDirty && !vm.isImport)),
+                value: "action.apply-changes",
+                icon: "done",
+                shortcut: "M-S"
+            }
+        );
+        contextInfo.items.push(
+            {
+                action: function () {
+                    vm.revertRuleChain();
+                },
+                enabled: vm.isDirty,
+                value: "action.decline-changes",
+                icon: "close",
+                shortcut: "M-Z"
+            }
+        );
         return contextInfo;
     }
 
@@ -153,6 +213,16 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
             contextInfo.items.push(
                 {
                     action: function () {
+                        openNodeDetails(node);
+                    },
+                    enabled: true,
+                    value: "rulenode.details",
+                    icon: "menu"
+                }
+            );
+            contextInfo.items.push(
+                {
+                    action: function () {
                         vm.canvasControl.modelservice.nodes.delete(node);
                     },
                     enabled: true,
@@ -173,6 +243,19 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
             subtitle: $translate.instant('rulenode.link')
         };
         contextInfo.items = [];
+        var sourceNode = vm.modelservice.nodes.getNodeByConnectorId(edge.source);
+        if (sourceNode.component.type != types.ruleNodeType.INPUT.value) {
+            contextInfo.items.push(
+                {
+                    action: function () {
+                        openLinkDetails(edge);
+                    },
+                    enabled: true,
+                    value: "rulenode.details",
+                    icon: "menu"
+                }
+            );
+        }
         contextInfo.items.push(
             {
                 action: function () {
@@ -191,7 +274,7 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
         hotkeys.bindTo($scope)
             .add({
                 combo: 'ctrl+a',
-                description: $translate.instant('rulenode.select-all'),
+                description: $translate.instant('rulenode.select-all-objects'),
                 allowIn: ['INPUT', 'SELECT', 'TEXTAREA'],
                 callback: function (event) {
                     event.preventDefault();
@@ -200,7 +283,7 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
             })
             .add({
                 combo: 'esc',
-                description: $translate.instant('rulenode.deselect-all'),
+                description: $translate.instant('rulenode.deselect-all-objects'),
                 allowIn: ['INPUT', 'SELECT', 'TEXTAREA'],
                 callback: function (event) {
                     event.preventDefault();
@@ -425,35 +508,11 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
 
     vm.editCallbacks = {
         edgeDoubleClick: function (event, edge) {
-            var sourceNode = vm.modelservice.nodes.getNodeByConnectorId(edge.source);
-            if (sourceNode.component.type != types.ruleNodeType.INPUT.value) {
-                vm.isEditingRuleNode = false;
-                vm.editingRuleNode = null;
-                vm.editingRuleNodeLinkLabels = ruleChainService.getRuleNodeSupportedLinks(sourceNode.component);
-                vm.isEditingRuleNodeLink = true;
-                vm.editingRuleNodeLinkIndex = vm.ruleChainModel.edges.indexOf(edge);
-                vm.editingRuleNodeLink = angular.copy(edge);
-                $mdUtil.nextTick(() => {
-                    if (vm.ruleNodeLinkForm) {
-                        vm.ruleNodeLinkForm.$setPristine();
-                    }
-                });
-            }
+            openLinkDetails(edge);
         },
         nodeCallbacks: {
             'doubleClick': function (event, node) {
-                if (node.component.type != types.ruleNodeType.INPUT.value) {
-                    vm.isEditingRuleNodeLink = false;
-                    vm.editingRuleNodeLink = null;
-                    vm.isEditingRuleNode = true;
-                    vm.editingRuleNodeIndex = vm.ruleChainModel.nodes.indexOf(node);
-                    vm.editingRuleNode = angular.copy(node);
-                    $mdUtil.nextTick(() => {
-                        if (vm.ruleNodeForm) {
-                            vm.ruleNodeForm.$setPristine();
-                        }
-                    });
-                }
+                openNodeDetails(node);
             }
         },
         isValidEdge: function (source, destination) {
@@ -491,6 +550,38 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
         }
     };
 
+    function openNodeDetails(node) {
+        if (node.component.type != types.ruleNodeType.INPUT.value) {
+            vm.isEditingRuleNodeLink = false;
+            vm.editingRuleNodeLink = null;
+            vm.isEditingRuleNode = true;
+            vm.editingRuleNodeIndex = vm.ruleChainModel.nodes.indexOf(node);
+            vm.editingRuleNode = angular.copy(node);
+            $mdUtil.nextTick(() => {
+                if (vm.ruleNodeForm) {
+                    vm.ruleNodeForm.$setPristine();
+                }
+            });
+        }
+    }
+
+    function openLinkDetails(edge) {
+        var sourceNode = vm.modelservice.nodes.getNodeByConnectorId(edge.source);
+        if (sourceNode.component.type != types.ruleNodeType.INPUT.value) {
+            vm.isEditingRuleNode = false;
+            vm.editingRuleNode = null;
+            vm.editingRuleNodeLinkLabels = ruleChainService.getRuleNodeSupportedLinks(sourceNode.component);
+            vm.isEditingRuleNodeLink = true;
+            vm.editingRuleNodeLinkIndex = vm.ruleChainModel.edges.indexOf(edge);
+            vm.editingRuleNodeLink = angular.copy(edge);
+            $mdUtil.nextTick(() => {
+                if (vm.ruleNodeLinkForm) {
+                    vm.ruleNodeLinkForm.$setPristine();
+                }
+            });
+        }
+    }
+
     loadRuleChainLibrary(ruleNodeComponents, true);
 
     $scope.$watch('vm.ruleNodeSearch',
diff --git a/ui/src/app/rulechain/rulechain.scss b/ui/src/app/rulechain/rulechain.scss
index 68f0423..b89557b 100644
--- a/ui/src/app/rulechain/rulechain.scss
+++ b/ui/src/app/rulechain/rulechain.scss
@@ -113,6 +113,9 @@
     font-size: 14px;
     display: flex;
     flex-direction: row;
+    &.tb-rulechain {
+      background-color: #aac7e4;
+    }
     &.tb-link {
       background-color: #aac7e4;
     }
@@ -124,6 +127,7 @@
       font-weight: 500;
     }
     .tb-context-menu-subtitle {
+      font-size: 12px;
     }
   }
 }
diff --git a/ui/src/app/rulechain/rulechain.tpl.html b/ui/src/app/rulechain/rulechain.tpl.html
index 8a8dcc5..59fdbad 100644
--- a/ui/src/app/rulechain/rulechain.tpl.html
+++ b/ui/src/app/rulechain/rulechain.tpl.html
@@ -99,7 +99,7 @@
                     </md-expansion-panel>
                 </md-expansion-panel-group>
             </md-sidenav>
-            <md-menu flex style="position: relative;" md-position-mode="target target" tb-mousepoint-menu>
+            <md-menu flex style="position: relative;" md-position-mode="target target" tb-offset-x="-20" tb-offset-y="-45" tb-mousepoint-menu>
                 <div class="tb-absolute-fill tb-rulechain-graph" ng-click="" tb-contextmenu="vm.openRuleChainContextMenu($event, $mdOpenMousepointMenu)">
                     <fc-canvas id="tb-rulchain-canvas"
                                model="vm.ruleChainModel"