Showing
6 changed files
with
140 additions
and
87 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,8 @@ 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", | |
1180 | 1182 | "delete-selected-objects": "Delete selected nodes and connections", |
1181 | 1183 | "rulenode-details": "Rule node details", |
1182 | 1184 | "debug-mode": "Debug mode", | ... | ... |
... | ... | @@ -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,42 +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 | 77 | vm.objectsSelected = objectsSelected; |
85 | 78 | vm.deleteSelected = deleteSelected; |
86 | 79 | |
87 | - vm.keyDown = function (evt) { | |
88 | - if (evt.keyCode === ctrlKeyCode) { | |
89 | - vm.ctrlDown = true; | |
90 | - evt.stopPropagation(); | |
91 | - evt.preventDefault(); | |
92 | - } | |
93 | - }; | |
94 | - | |
95 | - vm.keyUp = function (evt) { | |
96 | - | |
97 | - if (evt.keyCode === deleteKeyCode) { | |
98 | - vm.modelservice.deleteSelected(); | |
99 | - } | |
100 | - | |
101 | - if (evt.keyCode == aKeyCode && vm.ctrlDown) { | |
102 | - vm.modelservice.selectAll(); | |
103 | - } | |
104 | - | |
105 | - if (evt.keyCode == escKeyCode) { | |
106 | - vm.modelservice.deselectAll(); | |
107 | - } | |
108 | - | |
109 | - if (evt.keyCode === ctrlKeyCode) { | |
110 | - vm.ctrlDown = false; | |
111 | - evt.stopPropagation(); | |
112 | - evt.preventDefault(); | |
113 | - } | |
114 | - }; | |
80 | + initHotKeys(); | |
81 | + | |
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 | + } | |
115 | 130 | |
116 | 131 | vm.onEditRuleNodeClosed = function() { |
117 | 132 | vm.editingRuleNode = null; |
... | ... | @@ -289,44 +304,40 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, |
289 | 304 | loadRuleChainLibrary(); |
290 | 305 | |
291 | 306 | function loadRuleChainLibrary() { |
292 | - ruleChainService.getRuleNodeComponents().then( | |
293 | - (ruleNodeComponents) => { | |
294 | - for (var i=0;i<ruleNodeComponents.length;i++) { | |
295 | - var ruleNodeComponent = ruleNodeComponents[i]; | |
296 | - var componentType = ruleNodeComponent.type; | |
297 | - var model = vm.ruleNodeTypesModel[componentType].model; | |
298 | - var node = { | |
299 | - id: model.nodes.length, | |
300 | - component: ruleNodeComponent, | |
301 | - name: '', | |
302 | - nodeClass: vm.types.ruleNodeType[componentType].nodeClass, | |
303 | - icon: vm.types.ruleNodeType[componentType].icon, | |
304 | - x: 30, | |
305 | - y: 10+50*model.nodes.length, | |
306 | - connectors: [] | |
307 | - }; | |
308 | - if (ruleNodeComponent.configurationDescriptor.nodeDefinition.inEnabled) { | |
309 | - node.connectors.push( | |
310 | - { | |
311 | - type: flowchartConstants.leftConnectorType, | |
312 | - id: model.nodes.length * 2 | |
313 | - } | |
314 | - ); | |
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 | |
315 | 326 | } |
316 | - if (ruleNodeComponent.configurationDescriptor.nodeDefinition.outEnabled) { | |
317 | - node.connectors.push( | |
318 | - { | |
319 | - type: flowchartConstants.rightConnectorType, | |
320 | - id: model.nodes.length * 2 + 1 | |
321 | - } | |
322 | - ); | |
327 | + ); | |
328 | + } | |
329 | + if (ruleNodeComponent.configurationDescriptor.nodeDefinition.outEnabled) { | |
330 | + node.connectors.push( | |
331 | + { | |
332 | + type: flowchartConstants.rightConnectorType, | |
333 | + id: model.nodes.length * 2 + 1 | |
323 | 334 | } |
324 | - model.nodes.push(node); | |
325 | - } | |
326 | - vm.ruleChainLibraryLoaded = true; | |
327 | - prepareRuleChain(); | |
335 | + ); | |
328 | 336 | } |
329 | - ); | |
337 | + model.nodes.push(node); | |
338 | + } | |
339 | + vm.ruleChainLibraryLoaded = true; | |
340 | + prepareRuleChain(); | |
330 | 341 | } |
331 | 342 | |
332 | 343 | function prepareRuleChain() { | ... | ... |
... | ... | @@ -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; |
... | ... | @@ -186,12 +187,27 @@ |
186 | 187 | margin: 10px; |
187 | 188 | border-radius: 5px; |
188 | 189 | background-color: #ccc; |
190 | + pointer-events: all; | |
189 | 191 | } |
190 | 192 | |
191 | 193 | .fc-connector.fc-hover { |
192 | 194 | background-color: #000; |
193 | 195 | } |
194 | 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 | + | |
195 | 211 | .fc-edge { |
196 | 212 | outline: none; |
197 | 213 | stroke: gray; |
... | ... | @@ -246,11 +262,23 @@ |
246 | 262 | cursor: pointer; |
247 | 263 | } |
248 | 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 | + | |
249 | 275 | .fc-edge-label { |
250 | 276 | position: absolute; |
251 | - user-select: none; | |
252 | 277 | transition: transform .2s; |
253 | 278 | opacity: 0.8; |
279 | + &.ng-leave { | |
280 | + transition: 0s none; | |
281 | + } | |
254 | 282 | &.fc-hover { |
255 | 283 | transform: scale(1.25); |
256 | 284 | } |
... | ... | @@ -262,6 +290,13 @@ |
262 | 290 | } |
263 | 291 | } |
264 | 292 | } |
293 | + .fc-nodedelete { | |
294 | + right: -13px; | |
295 | + top: -30px; | |
296 | + } | |
297 | + &:focus { | |
298 | + outline: 0; | |
299 | + } | |
265 | 300 | } |
266 | 301 | |
267 | 302 | .fc-edge-label-text { |
... | ... | @@ -273,6 +308,7 @@ |
273 | 308 | font-size: 14px; |
274 | 309 | font-weight: 600; |
275 | 310 | span { |
311 | + cursor: default; | |
276 | 312 | border: solid 2px #003a79; |
277 | 313 | border-radius: 10px; |
278 | 314 | color: #003a79; | ... | ... |
... | ... | @@ -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" | ... | ... |