thingsboard-memoizeit

Details

diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js
index 9d35cd1..e7c0f58 100644
--- a/ui/src/app/locale/locale.constant.js
+++ b/ui/src/app/locale/locale.constant.js
@@ -1209,6 +1209,7 @@ export default angular.module('thingsboard.locale', [])
                     "rulenode-details": "Rule node details",
                     "debug-mode": "Debug mode",
                     "configuration": "Configuration",
+                    "link": "Link",
                     "link-details": "Rule node link details",
                     "add-link": "Add link",
                     "link-label": "Link label",
diff --git a/ui/src/app/rulechain/rulechain.controller.js b/ui/src/app/rulechain/rulechain.controller.js
index 9bd7960..eaa14fb 100644
--- a/ui/src/app/rulechain/rulechain.controller.js
+++ b/ui/src/app/rulechain/rulechain.controller.js
@@ -104,8 +104,89 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
 
     vm.triggerResize = triggerResize;
 
+    vm.openRuleChainContextMenu = openRuleChainContextMenu;
+
     initHotKeys();
 
+    function openRuleChainContextMenu($event, $mdOpenMousepointMenu) {
+        if (vm.canvasControl.modelservice && !$event.ctrlKey && !$event.metaKey) {
+            var x = $event.clientX;
+            var y = $event.clientY;
+            var item = vm.canvasControl.modelservice.getItemInfoAtPoint(x, y);
+            vm.contextInfo = prepareContextMenu(item);
+            if (vm.contextInfo.items && vm.contextInfo.items.length > 0) {
+                vm.contextMenuEvent = $event;
+                $mdOpenMousepointMenu($event);
+                return false;
+            }
+        }
+    }
+
+    function prepareContextMenu(item) {
+        if (objectsSelected() || (!item.node && !item.edge)) {
+            return prepareRuleChainContextMenu();
+        } else if (item.node) {
+            return prepareRuleNodeContextMenu(item.node);
+        } else if (item.edge) {
+            return prepareEdgeContextMenu(item.edge);
+        }
+    }
+
+    function prepareRuleChainContextMenu() {
+        var contextInfo = {
+            title: vm.ruleChain.name,
+            subtitle: $translate.instant('rulechain.rulechain')
+        };
+        contextInfo.items = [];
+        return contextInfo;
+    }
+
+    function prepareRuleNodeContextMenu(node) {
+        var contextInfo = {
+            headerClass: node.nodeClass,
+            icon: node.icon,
+            title: node.name,
+            subtitle: node.component.name
+        };
+        contextInfo.items = [];
+        if (!node.readonly) {
+            contextInfo.items.push(
+                {
+                    action: function () {
+                        vm.canvasControl.modelservice.nodes.delete(node);
+                    },
+                    enabled: true,
+                    value: "action.delete",
+                    icon: "clear",
+                    shortcut: "M-X"
+                }
+            );
+        }
+        return contextInfo;
+    }
+
+    function prepareEdgeContextMenu(edge) {
+        var contextInfo = {
+            headerClass: 'tb-link',
+            icon: 'trending_flat',
+            title: edge.label,
+            subtitle: $translate.instant('rulenode.link')
+        };
+        contextInfo.items = [];
+        contextInfo.items.push(
+            {
+                action: function () {
+                    vm.canvasControl.modelservice.edges.delete(edge);
+                },
+                enabled: true,
+                value: "action.delete",
+                icon: "clear",
+                shortcut: "M-X"
+            }
+        );
+        return contextInfo;
+    }
+
     function initHotKeys() {
         hotkeys.bindTo($scope)
             .add({
@@ -652,7 +733,7 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
         }
 
         if (vm.canvasControl.adjustCanvasSize) {
-            vm.canvasControl.adjustCanvasSize();
+            vm.canvasControl.adjustCanvasSize(true);
         }
 
         vm.isDirty = false;
diff --git a/ui/src/app/rulechain/rulechain.scss b/ui/src/app/rulechain/rulechain.scss
index 187a722..68f0423 100644
--- a/ui/src/app/rulechain/rulechain.scss
+++ b/ui/src/app/rulechain/rulechain.scss
@@ -105,12 +105,53 @@
   }
 }
 
+#tb-rule-chain-context-menu {
+  padding-top: 0px;
+  border-radius: 8px;
+  .tb-context-menu-header {
+    padding: 8px 5px 5px;
+    font-size: 14px;
+    display: flex;
+    flex-direction: row;
+    &.tb-link {
+      background-color: #aac7e4;
+    }
+    md-icon {
+      padding-left: 2px;
+      padding-right: 10px;
+    }
+    .tb-context-menu-title {
+      font-weight: 500;
+    }
+    .tb-context-menu-subtitle {
+    }
+  }
+}
+
 .fc-canvas {
   min-width: 100%;
   min-height: 100%;
   outline: none;
 }
 
+.tb-rule-node, #tb-rule-chain-context-menu .tb-context-menu-header {
+  &.tb-filter-type {
+    background-color: #f1e861;
+  }
+  &.tb-enrichment-type {
+    background-color: #cdf14e;
+  }
+  &.tb-transformation-type {
+    background-color: #79cef1;
+  }
+  &.tb-action-type {
+    background-color: #f1928f;
+  }
+  &.tb-rule-chain-type {
+    background-color: #d6c4f1;
+  }
+}
+
 .tb-rule-node {
   display: flex;
   flex-direction: row;
@@ -139,21 +180,6 @@
     background-color: #a3eaa9;
     user-select: none;
   }
-  &.tb-filter-type {
-    background-color: #f1e861;
-  }
-  &.tb-enrichment-type {
-    background-color: #cdf14e;
-  }
-  &.tb-transformation-type {
-    background-color: #79cef1;
-  }
-  &.tb-action-type {
-    background-color: #f1928f;
-  }
-  &.tb-rule-chain-type {
-    background-color: #d6c4f1;
-  }
   md-icon {
     font-size: 20px;
     width: 20px;
diff --git a/ui/src/app/rulechain/rulechain.tpl.html b/ui/src/app/rulechain/rulechain.tpl.html
index 37fcd9b..8a8dcc5 100644
--- a/ui/src/app/rulechain/rulechain.tpl.html
+++ b/ui/src/app/rulechain/rulechain.tpl.html
@@ -99,18 +99,37 @@
                     </md-expansion-panel>
                 </md-expansion-panel-group>
             </md-sidenav>
-            <div flex class="tb-rulechain-graph">
-                <fc-canvas id="tb-rulchain-canvas"
-                           model="vm.ruleChainModel"
-                           selected-objects="vm.selectedObjects"
-                           edge-style="curved"
-                           node-width="170"
-                           node-height="50"
-                           automatic-resize="true"
-                           control="vm.canvasControl"
-                           callbacks="vm.editCallbacks">
-                </fc-canvas>
-            </div>
+            <md-menu flex style="position: relative;" md-position-mode="target target" 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"
+                               selected-objects="vm.selectedObjects"
+                               edge-style="curved"
+                               node-width="170"
+                               node-height="50"
+                               automatic-resize="true"
+                               control="vm.canvasControl"
+                               callbacks="vm.editCallbacks">
+                    </fc-canvas>
+                </div>
+                <md-menu-content id="tb-rule-chain-context-menu" width="4" ng-mouseleave="$mdCloseMousepointMenu()">
+                    <div class="tb-context-menu-header {{vm.contextInfo.headerClass}}">
+                        <md-icon aria-label="node-type-icon"
+                                 class="material-icons">{{vm.contextInfo.icon}}</md-icon>
+                        <div flex>
+                            <div class="tb-context-menu-title">{{vm.contextInfo.title}}</div>
+                            <div class="tb-context-menu-subtitle">{{vm.contextInfo.subtitle}}</div>
+                        </div>
+                    </div>
+                    <md-menu-item ng-repeat="item in vm.contextInfo.items">
+                        <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>
+                            <span ng-if="item.shortcut" class="tb-alt-text"> {{ item.shortcut | keyboardShortcut }}</span>
+                        </md-button>
+                    </md-menu-item>
+                </md-menu-content>
+            </md-menu>
         </div>
         <tb-details-sidenav class="tb-rulenode-details-sidenav"
                             header-title="{{vm.editingRuleNode.name}}"