Commit bb0533c5286c817623b69c8620eb5ec4fa3293ea

Authored by Andrew Shvayka
2 parents 02c4d6b0 b578402f

Merge branch 'develop/1.5' of github.com:thingsboard/thingsboard into develop/1.5

@@ -18,17 +18,17 @@ export default angular.module('thingsboard.directives.confirmOnExit', []) @@ -18,17 +18,17 @@ export default angular.module('thingsboard.directives.confirmOnExit', [])
18 .name; 18 .name;
19 19
20 /*@ngInject*/ 20 /*@ngInject*/
21 -function ConfirmOnExit($state, $mdDialog, $window, $filter, userService) { 21 +function ConfirmOnExit($state, $mdDialog, $window, $filter, $parse, userService) {
22 return { 22 return {
23 - link: function ($scope) {  
24 - 23 + link: function ($scope, $element, $attributes) {
  24 + $scope.confirmForm = $scope.$eval($attributes.confirmForm);
25 $window.onbeforeunload = function () { 25 $window.onbeforeunload = function () {
26 - if (userService.isAuthenticated() && (($scope.confirmForm && $scope.confirmForm.$dirty) || $scope.isDirty)) { 26 + if (userService.isAuthenticated() && (($scope.confirmForm && $scope.confirmForm.$dirty) || $scope.$eval($attributes.isDirty))) {
27 return $filter('translate')('confirm-on-exit.message'); 27 return $filter('translate')('confirm-on-exit.message');
28 } 28 }
29 } 29 }
30 $scope.$on('$stateChangeStart', function (event, next, current, params) { 30 $scope.$on('$stateChangeStart', function (event, next, current, params) {
31 - if (userService.isAuthenticated() && (($scope.confirmForm && $scope.confirmForm.$dirty) || $scope.isDirty)) { 31 + if (userService.isAuthenticated() && (($scope.confirmForm && $scope.confirmForm.$dirty) || $scope.$eval($attributes.isDirty))) {
32 event.preventDefault(); 32 event.preventDefault();
33 var confirm = $mdDialog.confirm() 33 var confirm = $mdDialog.confirm()
34 .title($filter('translate')('confirm-on-exit.title')) 34 .title($filter('translate')('confirm-on-exit.title'))
@@ -40,7 +40,9 @@ function ConfirmOnExit($state, $mdDialog, $window, $filter, userService) { @@ -40,7 +40,9 @@ function ConfirmOnExit($state, $mdDialog, $window, $filter, userService) {
40 if ($scope.confirmForm) { 40 if ($scope.confirmForm) {
41 $scope.confirmForm.$setPristine(); 41 $scope.confirmForm.$setPristine();
42 } else { 42 } else {
43 - $scope.isDirty = false; 43 + var remoteSetter = $parse($attributes.isDirty).assign;
  44 + remoteSetter($scope, false);
  45 + //$scope.isDirty = false;
44 } 46 }
45 $state.go(next.name, params); 47 $state.go(next.name, params);
46 }, function () { 48 }, function () {
@@ -48,9 +50,6 @@ function ConfirmOnExit($state, $mdDialog, $window, $filter, userService) { @@ -48,9 +50,6 @@ function ConfirmOnExit($state, $mdDialog, $window, $filter, userService) {
48 } 50 }
49 }); 51 });
50 }, 52 },
51 - scope: {  
52 - confirmForm: '=',  
53 - isDirty: '='  
54 - } 53 + scope: false
55 }; 54 };
56 } 55 }
@@ -1177,6 +1177,9 @@ export default angular.module('thingsboard.locale', []) @@ -1177,6 +1177,9 @@ export default angular.module('thingsboard.locale', [])
1177 "type": "Type", 1177 "type": "Type",
1178 "description": "Description", 1178 "description": "Description",
1179 "delete": "Delete rule node", 1179 "delete": "Delete rule node",
  1180 + "select-all": "Select all nodes and connections",
  1181 + "deselect-all": "Deselect all nodes and connections",
  1182 + "delete-selected-objects": "Delete selected nodes and connections",
1180 "rulenode-details": "Rule node details", 1183 "rulenode-details": "Rule node details",
1181 "debug-mode": "Debug mode", 1184 "debug-mode": "Debug mode",
1182 "configuration": "Configuration", 1185 "configuration": "Configuration",
@@ -27,15 +27,10 @@ import addRuleNodeLinkTemplate from './add-link.tpl.html'; @@ -27,15 +27,10 @@ import addRuleNodeLinkTemplate from './add-link.tpl.html';
27 27
28 /* eslint-enable import/no-unresolved, import/default */ 28 /* eslint-enable import/no-unresolved, import/default */
29 29
30 -  
31 -const deleteKeyCode = 46;  
32 -const ctrlKeyCode = 17;  
33 -const aKeyCode = 65;  
34 -const escKeyCode = 27;  
35 -  
36 /*@ngInject*/ 30 /*@ngInject*/
37 export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, $timeout, $mdExpansionPanel, $document, $mdDialog, 31 export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, $timeout, $mdExpansionPanel, $document, $mdDialog,
38 - $filter, $translate, types, ruleChainService, Modelfactory, flowchartConstants, ruleChain, ruleChainMetaData) { 32 + $filter, $translate, hotkeys, types, ruleChainService, Modelfactory, flowchartConstants,
  33 + ruleChain, ruleChainMetaData, ruleNodeComponents) {
39 34
40 var vm = this; 35 var vm = this;
41 36
@@ -76,39 +71,62 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, @@ -76,39 +71,62 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
76 71
77 vm.modelservice = Modelfactory(vm.ruleChainModel, vm.selectedObjects); 72 vm.modelservice = Modelfactory(vm.ruleChainModel, vm.selectedObjects);
78 73
79 - vm.ctrlDown = false;  
80 -  
81 vm.saveRuleChain = saveRuleChain; 74 vm.saveRuleChain = saveRuleChain;
82 vm.revertRuleChain = revertRuleChain; 75 vm.revertRuleChain = revertRuleChain;
83 76
84 - vm.keyDown = function (evt) {  
85 - if (evt.keyCode === ctrlKeyCode) {  
86 - vm.ctrlDown = true;  
87 - evt.stopPropagation();  
88 - evt.preventDefault();  
89 - }  
90 - };  
91 -  
92 - vm.keyUp = function (evt) {  
93 -  
94 - if (evt.keyCode === deleteKeyCode) {  
95 - vm.modelservice.deleteSelected();  
96 - }  
97 -  
98 - if (evt.keyCode == aKeyCode && vm.ctrlDown) {  
99 - vm.modelservice.selectAll();  
100 - } 77 + vm.objectsSelected = objectsSelected;
  78 + vm.deleteSelected = deleteSelected;
101 79
102 - if (evt.keyCode == escKeyCode) {  
103 - vm.modelservice.deselectAll();  
104 - } 80 + initHotKeys();
105 81
106 - if (evt.keyCode === ctrlKeyCode) {  
107 - vm.ctrlDown = false;  
108 - evt.stopPropagation();  
109 - evt.preventDefault();  
110 - }  
111 - }; 82 + function initHotKeys() {
  83 + hotkeys.bindTo($scope)
  84 + .add({
  85 + combo: 'ctrl+a',
  86 + description: $translate.instant('rulenode.select-all'),
  87 + allowIn: ['INPUT', 'SELECT', 'TEXTAREA'],
  88 + callback: function (event) {
  89 + event.preventDefault();
  90 + vm.modelservice.selectAll();
  91 + }
  92 + })
  93 + .add({
  94 + combo: 'esc',
  95 + description: $translate.instant('rulenode.deselect-all'),
  96 + allowIn: ['INPUT', 'SELECT', 'TEXTAREA'],
  97 + callback: function (event) {
  98 + event.preventDefault();
  99 + vm.modelservice.deselectAll();
  100 + }
  101 + })
  102 + .add({
  103 + combo: 'ctrl+s',
  104 + description: $translate.instant('action.apply'),
  105 + allowIn: ['INPUT', 'SELECT', 'TEXTAREA'],
  106 + callback: function (event) {
  107 + event.preventDefault();
  108 + vm.saveRuleChain();
  109 + }
  110 + })
  111 + .add({
  112 + combo: 'ctrl+z',
  113 + description: $translate.instant('action.decline-changes'),
  114 + allowIn: ['INPUT', 'SELECT', 'TEXTAREA'],
  115 + callback: function (event) {
  116 + event.preventDefault();
  117 + vm.revertRuleChain();
  118 + }
  119 + })
  120 + .add({
  121 + combo: 'del',
  122 + description: $translate.instant('rulenode.delete-selected-objects'),
  123 + allowIn: ['INPUT', 'SELECT', 'TEXTAREA'],
  124 + callback: function (event) {
  125 + event.preventDefault();
  126 + vm.modelservice.deleteSelected();
  127 + }
  128 + })
  129 + }
112 130
113 vm.onEditRuleNodeClosed = function() { 131 vm.onEditRuleNodeClosed = function() {
114 vm.editingRuleNode = null; 132 vm.editingRuleNode = null;
@@ -286,44 +304,40 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, @@ -286,44 +304,40 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
286 loadRuleChainLibrary(); 304 loadRuleChainLibrary();
287 305
288 function loadRuleChainLibrary() { 306 function loadRuleChainLibrary() {
289 - ruleChainService.getRuleNodeComponents().then(  
290 - (ruleNodeComponents) => {  
291 - for (var i=0;i<ruleNodeComponents.length;i++) {  
292 - var ruleNodeComponent = ruleNodeComponents[i];  
293 - var componentType = ruleNodeComponent.type;  
294 - var model = vm.ruleNodeTypesModel[componentType].model;  
295 - var node = {  
296 - id: model.nodes.length,  
297 - component: ruleNodeComponent,  
298 - name: '',  
299 - nodeClass: vm.types.ruleNodeType[componentType].nodeClass,  
300 - icon: vm.types.ruleNodeType[componentType].icon,  
301 - x: 30,  
302 - y: 10+50*model.nodes.length,  
303 - connectors: []  
304 - };  
305 - if (ruleNodeComponent.configurationDescriptor.nodeDefinition.inEnabled) {  
306 - node.connectors.push(  
307 - {  
308 - type: flowchartConstants.leftConnectorType,  
309 - id: model.nodes.length * 2  
310 - }  
311 - ); 307 + for (var i=0;i<ruleNodeComponents.length;i++) {
  308 + var ruleNodeComponent = ruleNodeComponents[i];
  309 + var componentType = ruleNodeComponent.type;
  310 + var model = vm.ruleNodeTypesModel[componentType].model;
  311 + var node = {
  312 + id: model.nodes.length,
  313 + component: ruleNodeComponent,
  314 + name: '',
  315 + nodeClass: vm.types.ruleNodeType[componentType].nodeClass,
  316 + icon: vm.types.ruleNodeType[componentType].icon,
  317 + x: 30,
  318 + y: 10+50*model.nodes.length,
  319 + connectors: []
  320 + };
  321 + if (ruleNodeComponent.configurationDescriptor.nodeDefinition.inEnabled) {
  322 + node.connectors.push(
  323 + {
  324 + type: flowchartConstants.leftConnectorType,
  325 + id: model.nodes.length * 2
312 } 326 }
313 - if (ruleNodeComponent.configurationDescriptor.nodeDefinition.outEnabled) {  
314 - node.connectors.push(  
315 - {  
316 - type: flowchartConstants.rightConnectorType,  
317 - id: model.nodes.length * 2 + 1  
318 - }  
319 - ); 327 + );
  328 + }
  329 + if (ruleNodeComponent.configurationDescriptor.nodeDefinition.outEnabled) {
  330 + node.connectors.push(
  331 + {
  332 + type: flowchartConstants.rightConnectorType,
  333 + id: model.nodes.length * 2 + 1
320 } 334 }
321 - model.nodes.push(node);  
322 - }  
323 - vm.ruleChainLibraryLoaded = true;  
324 - prepareRuleChain(); 335 + );
325 } 336 }
326 - ); 337 + model.nodes.push(node);
  338 + }
  339 + vm.ruleChainLibraryLoaded = true;
  340 + prepareRuleChain();
327 } 341 }
328 342
329 function prepareRuleChain() { 343 function prepareRuleChain() {
@@ -632,6 +646,14 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, @@ -632,6 +646,14 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
632 }); 646 });
633 } 647 }
634 648
  649 + function objectsSelected() {
  650 + return vm.modelservice.nodes.getSelectedNodes().length > 0 ||
  651 + vm.modelservice.edges.getSelectedEdges().length > 0
  652 + }
  653 +
  654 + function deleteSelected() {
  655 + vm.modelservice.deleteSelected();
  656 + }
635 } 657 }
636 658
637 /*@ngInject*/ 659 /*@ngInject*/
@@ -68,6 +68,11 @@ export default function RuleChainRoutes($stateProvider, NodeTemplatePathProvider @@ -68,6 +68,11 @@ export default function RuleChainRoutes($stateProvider, NodeTemplatePathProvider
68 /*@ngInject*/ 68 /*@ngInject*/
69 function($stateParams, ruleChainService) { 69 function($stateParams, ruleChainService) {
70 return ruleChainService.getRuleChainMetaData($stateParams.ruleChainId); 70 return ruleChainService.getRuleChainMetaData($stateParams.ruleChainId);
  71 + },
  72 + ruleNodeComponents:
  73 + /*@ngInject*/
  74 + function($stateParams, ruleChainService) {
  75 + return ruleChainService.getRuleNodeComponents();
71 } 76 }
72 }, 77 },
73 data: { 78 data: {
@@ -75,6 +75,7 @@ @@ -75,6 +75,7 @@
75 padding: 5px 10px; 75 padding: 5px 10px;
76 border-radius: 5px; 76 border-radius: 5px;
77 background-color: #F15B26; 77 background-color: #F15B26;
  78 + pointer-events: none;
78 color: #333; 79 color: #333;
79 border: solid 1px #777; 80 border: solid 1px #777;
80 font-size: 12px; 81 font-size: 12px;
@@ -121,10 +122,6 @@ @@ -121,10 +122,6 @@
121 .fc-node { 122 .fc-node {
122 z-index: 1; 123 z-index: 1;
123 outline: none; 124 outline: none;
124 - &.fc-hover, &.fc-selected {  
125 - -webkit-filter: brightness(70%);  
126 - filter: brightness(70%);  
127 - }  
128 &.fc-dragging { 125 &.fc-dragging {
129 z-index: 10; 126 z-index: 10;
130 } 127 }
@@ -132,6 +129,26 @@ @@ -132,6 +129,26 @@
132 padding: 0 15px; 129 padding: 0 15px;
133 text-align: center; 130 text-align: center;
134 } 131 }
  132 + .fc-node-overlay {
  133 + position: absolute;
  134 + pointer-events: none;
  135 + left: 0;
  136 + top: 0;
  137 + right: 0;
  138 + bottom: 0;
  139 + background-color: #000;
  140 + opacity: 0;
  141 + }
  142 + &.fc-hover {
  143 + .fc-node-overlay {
  144 + opacity: 0.25;
  145 + }
  146 + }
  147 + &.fc-selected {
  148 + .fc-node-overlay {
  149 + opacity: 0.25;
  150 + }
  151 + }
135 } 152 }
136 153
137 .fc-leftConnectors, .fc-rightConnectors { 154 .fc-leftConnectors, .fc-rightConnectors {
@@ -170,17 +187,33 @@ @@ -170,17 +187,33 @@
170 margin: 10px; 187 margin: 10px;
171 border-radius: 5px; 188 border-radius: 5px;
172 background-color: #ccc; 189 background-color: #ccc;
  190 + pointer-events: all;
173 } 191 }
174 192
175 .fc-connector.fc-hover { 193 .fc-connector.fc-hover {
176 background-color: #000; 194 background-color: #000;
177 } 195 }
178 196
  197 +.fc-arrow-marker {
  198 + polygon {
  199 + stroke: gray;
  200 + fill: gray;
  201 + }
  202 +}
  203 +
  204 +.fc-arrow-marker-selected {
  205 + polygon {
  206 + stroke: red;
  207 + fill: red;
  208 + }
  209 +}
  210 +
179 .fc-edge { 211 .fc-edge {
180 outline: none; 212 outline: none;
181 stroke: gray; 213 stroke: gray;
182 stroke-width: 4; 214 stroke-width: 4;
183 fill: transparent; 215 fill: transparent;
  216 + transition: stroke-width .2s;
184 &.fc-selected { 217 &.fc-selected {
185 stroke: red; 218 stroke: red;
186 stroke-width: 4; 219 stroke-width: 4;
@@ -229,24 +262,53 @@ @@ -229,24 +262,53 @@
229 cursor: pointer; 262 cursor: pointer;
230 } 263 }
231 264
  265 +.fc-noselect {
  266 + -webkit-touch-callout: none; /* iOS Safari */
  267 + -webkit-user-select: none; /* Safari */
  268 + -khtml-user-select: none; /* Konqueror HTML */
  269 + -moz-user-select: none; /* Firefox */
  270 + -ms-user-select: none; /* Internet Explorer/Edge */
  271 + user-select: none; /* Non-prefixed version, currently
  272 + supported by Chrome and Opera */
  273 +}
  274 +
232 .fc-edge-label { 275 .fc-edge-label {
233 position: absolute; 276 position: absolute;
234 - user-select: none;  
235 - pointer-events: none; 277 + transition: transform .2s;
236 opacity: 0.8; 278 opacity: 0.8;
  279 + &.ng-leave {
  280 + transition: 0s none;
  281 + }
  282 + &.fc-hover {
  283 + transform: scale(1.25);
  284 + }
  285 + &.fc-selected {
  286 + .fc-edge-label-text {
  287 + span {
  288 + border: solid red;
  289 + color: red;
  290 + }
  291 + }
  292 + }
  293 + .fc-nodedelete {
  294 + right: -13px;
  295 + top: -30px;
  296 + }
  297 + &:focus {
  298 + outline: 0;
  299 + }
237 } 300 }
238 301
239 .fc-edge-label-text { 302 .fc-edge-label-text {
240 position: absolute; 303 position: absolute;
241 - left: 50%;  
242 - -webkit-transform: translateX(-50%);  
243 - transform: translateX(-50%); 304 + -webkit-transform: translate(-50%, -50%);
  305 + transform: translate(-50%, -50%);
244 white-space: nowrap; 306 white-space: nowrap;
245 text-align: center; 307 text-align: center;
246 font-size: 14px; 308 font-size: 14px;
247 font-weight: 600; 309 font-weight: 600;
248 - top: 5px;  
249 span { 310 span {
  311 + cursor: default;
250 border: solid 2px #003a79; 312 border: solid 2px #003a79;
251 border-radius: 10px; 313 border-radius: 10px;
252 color: #003a79; 314 color: #003a79;
@@ -255,6 +317,13 @@ @@ -255,6 +317,13 @@
255 } 317 }
256 } 318 }
257 319
  320 +.fc-select-rectangle {
  321 + border: 2px dashed #5262ff;
  322 + position: absolute;
  323 + background: rgba(20,125,255,0.1);
  324 + z-index: 2;
  325 +}
  326 +
258 @keyframes dash { 327 @keyframes dash {
259 from { 328 from {
260 stroke-dashoffset: 500; 329 stroke-dashoffset: 500;
@@ -16,8 +16,10 @@ @@ -16,8 +16,10 @@
16 16
17 --> 17 -->
18 18
19 -<md-content flex tb-expand-fullscreen  
20 - expand-tooltip-direction="bottom" layout="column" class="tb-rulechain"> 19 +<md-content flex tb-expand-fullscreen tb-confirm-on-exit is-dirty="vm.isDirty"
  20 + expand-tooltip-direction="bottom" layout="column" class="tb-rulechain"
  21 + ng-keydown="vm.keyDown($event)"
  22 + ng-keyup="vm.keyUp($event)">
21 <section class="tb-rulechain-container" flex layout="column"> 23 <section class="tb-rulechain-container" flex layout="column">
22 <div class="tb-rulechain-layout" flex layout="row"> 24 <div class="tb-rulechain-layout" flex layout="row">
23 <div class="tb-rulechain-library"> 25 <div class="tb-rulechain-library">
@@ -50,8 +52,6 @@ @@ -50,8 +52,6 @@
50 </div> 52 </div>
51 <div flex class="tb-rulechain-graph"> 53 <div flex class="tb-rulechain-graph">
52 <fc-canvas id="tb-rulchain-canvas" 54 <fc-canvas id="tb-rulchain-canvas"
53 - ng-keydown="vm.keyDown($event)"  
54 - ng-keyup="vm.keyUp($event)"  
55 model="vm.ruleChainModel" 55 model="vm.ruleChainModel"
56 selected-objects="vm.selectedObjects" 56 selected-objects="vm.selectedObjects"
57 edge-style="curved" 57 edge-style="curved"
@@ -112,6 +112,13 @@ @@ -112,6 +112,13 @@
112 </tb-details-sidenav> 112 </tb-details-sidenav>
113 </section> 113 </section>
114 <section layout="row" layout-wrap class="tb-footer-buttons md-fab" layout-align="start end"> 114 <section layout="row" layout-wrap class="tb-footer-buttons md-fab" layout-align="start end">
  115 + <md-button ng-disabled="$root.loading" ng-show="vm.objectsSelected()" class="tb-btn-footer md-accent md-hue-2 md-fab"
  116 + ng-click="vm.deleteSelected()" aria-label="{{ 'action.delete' | translate }}">
  117 + <md-tooltip md-direction="top">
  118 + {{ 'rulenode.delete-selected-objects' | translate }}
  119 + </md-tooltip>
  120 + <ng-md-icon icon="delete"></ng-md-icon>
  121 + </md-button>
115 <md-button ng-disabled="$root.loading || !vm.isDirty" 122 <md-button ng-disabled="$root.loading || !vm.isDirty"
116 class="tb-btn-footer md-accent md-hue-2 md-fab" 123 class="tb-btn-footer md-accent md-hue-2 md-fab"
117 aria-label="{{ 'action.apply' | translate }}" 124 aria-label="{{ 'action.apply' | translate }}"
@@ -22,6 +22,7 @@ @@ -22,6 +22,7 @@
22 ng-mousedown="callbacks.mouseDown($event, node)" 22 ng-mousedown="callbacks.mouseDown($event, node)"
23 ng-mouseenter="callbacks.mouseEnter($event, node)" 23 ng-mouseenter="callbacks.mouseEnter($event, node)"
24 ng-mouseleave="callbacks.mouseLeave($event, node)"> 24 ng-mouseleave="callbacks.mouseLeave($event, node)">
  25 + <div class="{{flowchartConstants.nodeOverlayClass}}"></div>
25 <div class="tb-rule-node {{node.nodeClass}}"> 26 <div class="tb-rule-node {{node.nodeClass}}">
26 <md-icon aria-label="node-type-icon" flex="15" 27 <md-icon aria-label="node-type-icon" flex="15"
27 class="material-icons">{{node.icon}}</md-icon> 28 class="material-icons">{{node.icon}}</md-icon>