Commit bb0533c5286c817623b69c8620eb5ec4fa3293ea
Merge branch 'develop/1.5' of github.com:thingsboard/thingsboard into develop/1.5
Showing
7 changed files
with
200 additions
and
94 deletions
... | ... | @@ -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> | ... | ... |