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 18 .name;
19 19
20 20 /*@ngInject*/
21   -function ConfirmOnExit($state, $mdDialog, $window, $filter, userService) {
  21 +function ConfirmOnExit($state, $mdDialog, $window, $filter, $parse, userService) {
22 22 return {
23   - link: function ($scope) {
24   -
  23 + link: function ($scope, $element, $attributes) {
  24 + $scope.confirmForm = $scope.$eval($attributes.confirmForm);
25 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 27 return $filter('translate')('confirm-on-exit.message');
28 28 }
29 29 }
30 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 32 event.preventDefault();
33 33 var confirm = $mdDialog.confirm()
34 34 .title($filter('translate')('confirm-on-exit.title'))
... ... @@ -40,7 +40,9 @@ function ConfirmOnExit($state, $mdDialog, $window, $filter, userService) {
40 40 if ($scope.confirmForm) {
41 41 $scope.confirmForm.$setPristine();
42 42 } else {
43   - $scope.isDirty = false;
  43 + var remoteSetter = $parse($attributes.isDirty).assign;
  44 + remoteSetter($scope, false);
  45 + //$scope.isDirty = false;
44 46 }
45 47 $state.go(next.name, params);
46 48 }, function () {
... ... @@ -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 }
\ No newline at end of file
... ...
... ... @@ -1177,6 +1177,9 @@ export default angular.module('thingsboard.locale', [])
1177 1177 "type": "Type",
1178 1178 "description": "Description",
1179 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 1183 "rulenode-details": "Rule node details",
1181 1184 "debug-mode": "Debug mode",
1182 1185 "configuration": "Configuration",
... ...
... ... @@ -27,15 +27,10 @@ import addRuleNodeLinkTemplate from './add-link.tpl.html';
27 27
28 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 30 /*@ngInject*/
37 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 35 var vm = this;
41 36
... ... @@ -76,39 +71,62 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
76 71
77 72 vm.modelservice = Modelfactory(vm.ruleChainModel, vm.selectedObjects);
78 73
79   - vm.ctrlDown = false;
80   -
81 74 vm.saveRuleChain = saveRuleChain;
82 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 131 vm.onEditRuleNodeClosed = function() {
114 132 vm.editingRuleNode = null;
... ... @@ -286,44 +304,40 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
286 304 loadRuleChainLibrary();
287 305
288 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 343 function prepareRuleChain() {
... ... @@ -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 659 /*@ngInject*/
... ...
... ... @@ -68,6 +68,11 @@ export default function RuleChainRoutes($stateProvider, NodeTemplatePathProvider
68 68 /*@ngInject*/
69 69 function($stateParams, ruleChainService) {
70 70 return ruleChainService.getRuleChainMetaData($stateParams.ruleChainId);
  71 + },
  72 + ruleNodeComponents:
  73 + /*@ngInject*/
  74 + function($stateParams, ruleChainService) {
  75 + return ruleChainService.getRuleNodeComponents();
71 76 }
72 77 },
73 78 data: {
... ...
... ... @@ -75,6 +75,7 @@
75 75 padding: 5px 10px;
76 76 border-radius: 5px;
77 77 background-color: #F15B26;
  78 + pointer-events: none;
78 79 color: #333;
79 80 border: solid 1px #777;
80 81 font-size: 12px;
... ... @@ -121,10 +122,6 @@
121 122 .fc-node {
122 123 z-index: 1;
123 124 outline: none;
124   - &.fc-hover, &.fc-selected {
125   - -webkit-filter: brightness(70%);
126   - filter: brightness(70%);
127   - }
128 125 &.fc-dragging {
129 126 z-index: 10;
130 127 }
... ... @@ -132,6 +129,26 @@
132 129 padding: 0 15px;
133 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 154 .fc-leftConnectors, .fc-rightConnectors {
... ... @@ -170,17 +187,33 @@
170 187 margin: 10px;
171 188 border-radius: 5px;
172 189 background-color: #ccc;
  190 + pointer-events: all;
173 191 }
174 192
175 193 .fc-connector.fc-hover {
176 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 211 .fc-edge {
180 212 outline: none;
181 213 stroke: gray;
182 214 stroke-width: 4;
183 215 fill: transparent;
  216 + transition: stroke-width .2s;
184 217 &.fc-selected {
185 218 stroke: red;
186 219 stroke-width: 4;
... ... @@ -229,24 +262,53 @@
229 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 275 .fc-edge-label {
233 276 position: absolute;
234   - user-select: none;
235   - pointer-events: none;
  277 + transition: transform .2s;
236 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 302 .fc-edge-label-text {
240 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 306 white-space: nowrap;
245 307 text-align: center;
246 308 font-size: 14px;
247 309 font-weight: 600;
248   - top: 5px;
249 310 span {
  311 + cursor: default;
250 312 border: solid 2px #003a79;
251 313 border-radius: 10px;
252 314 color: #003a79;
... ... @@ -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 327 @keyframes dash {
259 328 from {
260 329 stroke-dashoffset: 500;
... ...
... ... @@ -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 23 <section class="tb-rulechain-container" flex layout="column">
22 24 <div class="tb-rulechain-layout" flex layout="row">
23 25 <div class="tb-rulechain-library">
... ... @@ -50,8 +52,6 @@
50 52 </div>
51 53 <div flex class="tb-rulechain-graph">
52 54 <fc-canvas id="tb-rulchain-canvas"
53   - ng-keydown="vm.keyDown($event)"
54   - ng-keyup="vm.keyUp($event)"
55 55 model="vm.ruleChainModel"
56 56 selected-objects="vm.selectedObjects"
57 57 edge-style="curved"
... ... @@ -112,6 +112,13 @@
112 112 </tb-details-sidenav>
113 113 </section>
114 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 122 <md-button ng-disabled="$root.loading || !vm.isDirty"
116 123 class="tb-btn-footer md-accent md-hue-2 md-fab"
117 124 aria-label="{{ 'action.apply' | translate }}"
... ...
... ... @@ -22,6 +22,7 @@
22 22 ng-mousedown="callbacks.mouseDown($event, node)"
23 23 ng-mouseenter="callbacks.mouseEnter($event, node)"
24 24 ng-mouseleave="callbacks.mouseLeave($event, node)">
  25 + <div class="{{flowchartConstants.nodeOverlayClass}}"></div>
25 26 <div class="tb-rule-node {{node.nodeClass}}">
26 27 <md-icon aria-label="node-type-icon" flex="15"
27 28 class="material-icons">{{node.icon}}</md-icon>
... ...