diff --git a/ui/src/app/components/confirm-on-exit.directive.js b/ui/src/app/components/confirm-on-exit.directive.js index fe9a9bdddd..e04e1102a9 100644 --- a/ui/src/app/components/confirm-on-exit.directive.js +++ b/ui/src/app/components/confirm-on-exit.directive.js @@ -18,17 +18,17 @@ export default angular.module('thingsboard.directives.confirmOnExit', []) .name; /*@ngInject*/ -function ConfirmOnExit($state, $mdDialog, $window, $filter, userService) { +function ConfirmOnExit($state, $mdDialog, $window, $filter, $parse, userService) { return { - link: function ($scope) { - + link: function ($scope, $element, $attributes) { + $scope.confirmForm = $scope.$eval($attributes.confirmForm); $window.onbeforeunload = function () { - if (userService.isAuthenticated() && (($scope.confirmForm && $scope.confirmForm.$dirty) || $scope.isDirty)) { + if (userService.isAuthenticated() && (($scope.confirmForm && $scope.confirmForm.$dirty) || $scope.$eval($attributes.isDirty))) { return $filter('translate')('confirm-on-exit.message'); } } $scope.$on('$stateChangeStart', function (event, next, current, params) { - if (userService.isAuthenticated() && (($scope.confirmForm && $scope.confirmForm.$dirty) || $scope.isDirty)) { + if (userService.isAuthenticated() && (($scope.confirmForm && $scope.confirmForm.$dirty) || $scope.$eval($attributes.isDirty))) { event.preventDefault(); var confirm = $mdDialog.confirm() .title($filter('translate')('confirm-on-exit.title')) @@ -40,7 +40,9 @@ function ConfirmOnExit($state, $mdDialog, $window, $filter, userService) { if ($scope.confirmForm) { $scope.confirmForm.$setPristine(); } else { - $scope.isDirty = false; + var remoteSetter = $parse($attributes.isDirty).assign; + remoteSetter($scope, false); + //$scope.isDirty = false; } $state.go(next.name, params); }, function () { @@ -48,9 +50,6 @@ function ConfirmOnExit($state, $mdDialog, $window, $filter, userService) { } }); }, - scope: { - confirmForm: '=', - isDirty: '=' - } + scope: false }; } \ No newline at end of file diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js index cca2a11648..5dce7872e8 100644 --- a/ui/src/app/locale/locale.constant.js +++ b/ui/src/app/locale/locale.constant.js @@ -1177,6 +1177,9 @@ export default angular.module('thingsboard.locale', []) "type": "Type", "description": "Description", "delete": "Delete rule node", + "select-all": "Select all nodes and connections", + "deselect-all": "Deselect all nodes and connections", + "delete-selected-objects": "Delete selected nodes and connections", "rulenode-details": "Rule node details", "debug-mode": "Debug mode", "configuration": "Configuration", diff --git a/ui/src/app/rulechain/rulechain.controller.js b/ui/src/app/rulechain/rulechain.controller.js index b792f138f8..4eba5b2c52 100644 --- a/ui/src/app/rulechain/rulechain.controller.js +++ b/ui/src/app/rulechain/rulechain.controller.js @@ -27,15 +27,10 @@ import addRuleNodeLinkTemplate from './add-link.tpl.html'; /* eslint-enable import/no-unresolved, import/default */ - -const deleteKeyCode = 46; -const ctrlKeyCode = 17; -const aKeyCode = 65; -const escKeyCode = 27; - /*@ngInject*/ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, $timeout, $mdExpansionPanel, $document, $mdDialog, - $filter, $translate, types, ruleChainService, Modelfactory, flowchartConstants, ruleChain, ruleChainMetaData) { + $filter, $translate, hotkeys, types, ruleChainService, Modelfactory, flowchartConstants, + ruleChain, ruleChainMetaData, ruleNodeComponents) { var vm = this; @@ -76,39 +71,62 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, vm.modelservice = Modelfactory(vm.ruleChainModel, vm.selectedObjects); - vm.ctrlDown = false; - vm.saveRuleChain = saveRuleChain; vm.revertRuleChain = revertRuleChain; - vm.keyDown = function (evt) { - if (evt.keyCode === ctrlKeyCode) { - vm.ctrlDown = true; - evt.stopPropagation(); - evt.preventDefault(); - } - }; + vm.objectsSelected = objectsSelected; + vm.deleteSelected = deleteSelected; - vm.keyUp = function (evt) { + initHotKeys(); - if (evt.keyCode === deleteKeyCode) { - vm.modelservice.deleteSelected(); - } - - if (evt.keyCode == aKeyCode && vm.ctrlDown) { - vm.modelservice.selectAll(); - } - - if (evt.keyCode == escKeyCode) { - vm.modelservice.deselectAll(); - } - - if (evt.keyCode === ctrlKeyCode) { - vm.ctrlDown = false; - evt.stopPropagation(); - evt.preventDefault(); - } - }; + function initHotKeys() { + hotkeys.bindTo($scope) + .add({ + combo: 'ctrl+a', + description: $translate.instant('rulenode.select-all'), + allowIn: ['INPUT', 'SELECT', 'TEXTAREA'], + callback: function (event) { + event.preventDefault(); + vm.modelservice.selectAll(); + } + }) + .add({ + combo: 'esc', + description: $translate.instant('rulenode.deselect-all'), + allowIn: ['INPUT', 'SELECT', 'TEXTAREA'], + callback: function (event) { + event.preventDefault(); + vm.modelservice.deselectAll(); + } + }) + .add({ + combo: 'ctrl+s', + description: $translate.instant('action.apply'), + allowIn: ['INPUT', 'SELECT', 'TEXTAREA'], + callback: function (event) { + event.preventDefault(); + vm.saveRuleChain(); + } + }) + .add({ + combo: 'ctrl+z', + description: $translate.instant('action.decline-changes'), + allowIn: ['INPUT', 'SELECT', 'TEXTAREA'], + callback: function (event) { + event.preventDefault(); + vm.revertRuleChain(); + } + }) + .add({ + combo: 'del', + description: $translate.instant('rulenode.delete-selected-objects'), + allowIn: ['INPUT', 'SELECT', 'TEXTAREA'], + callback: function (event) { + event.preventDefault(); + vm.modelservice.deleteSelected(); + } + }) + } vm.onEditRuleNodeClosed = function() { vm.editingRuleNode = null; @@ -286,44 +304,40 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, loadRuleChainLibrary(); function loadRuleChainLibrary() { - ruleChainService.getRuleNodeComponents().then( - (ruleNodeComponents) => { - for (var i=0;i 0 || + vm.modelservice.edges.getSelectedEdges().length > 0 + } + + function deleteSelected() { + vm.modelservice.deleteSelected(); + } } /*@ngInject*/ diff --git a/ui/src/app/rulechain/rulechain.routes.js b/ui/src/app/rulechain/rulechain.routes.js index 808661a65a..f9578effd3 100644 --- a/ui/src/app/rulechain/rulechain.routes.js +++ b/ui/src/app/rulechain/rulechain.routes.js @@ -68,6 +68,11 @@ export default function RuleChainRoutes($stateProvider, NodeTemplatePathProvider /*@ngInject*/ function($stateParams, ruleChainService) { return ruleChainService.getRuleChainMetaData($stateParams.ruleChainId); + }, + ruleNodeComponents: + /*@ngInject*/ + function($stateParams, ruleChainService) { + return ruleChainService.getRuleNodeComponents(); } }, data: { diff --git a/ui/src/app/rulechain/rulechain.scss b/ui/src/app/rulechain/rulechain.scss index 26a5225a68..c9b2ced8f5 100644 --- a/ui/src/app/rulechain/rulechain.scss +++ b/ui/src/app/rulechain/rulechain.scss @@ -75,6 +75,7 @@ padding: 5px 10px; border-radius: 5px; background-color: #F15B26; + pointer-events: none; color: #333; border: solid 1px #777; font-size: 12px; @@ -121,10 +122,6 @@ .fc-node { z-index: 1; outline: none; - &.fc-hover, &.fc-selected { - -webkit-filter: brightness(70%); - filter: brightness(70%); - } &.fc-dragging { z-index: 10; } @@ -132,6 +129,26 @@ padding: 0 15px; text-align: center; } + .fc-node-overlay { + position: absolute; + pointer-events: none; + left: 0; + top: 0; + right: 0; + bottom: 0; + background-color: #000; + opacity: 0; + } + &.fc-hover { + .fc-node-overlay { + opacity: 0.25; + } + } + &.fc-selected { + .fc-node-overlay { + opacity: 0.25; + } + } } .fc-leftConnectors, .fc-rightConnectors { @@ -170,17 +187,33 @@ margin: 10px; border-radius: 5px; background-color: #ccc; + pointer-events: all; } .fc-connector.fc-hover { background-color: #000; } +.fc-arrow-marker { + polygon { + stroke: gray; + fill: gray; + } +} + +.fc-arrow-marker-selected { + polygon { + stroke: red; + fill: red; + } +} + .fc-edge { outline: none; stroke: gray; stroke-width: 4; fill: transparent; + transition: stroke-width .2s; &.fc-selected { stroke: red; stroke-width: 4; @@ -229,24 +262,53 @@ cursor: pointer; } +.fc-noselect { + -webkit-touch-callout: none; /* iOS Safari */ + -webkit-user-select: none; /* Safari */ + -khtml-user-select: none; /* Konqueror HTML */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* Internet Explorer/Edge */ + user-select: none; /* Non-prefixed version, currently + supported by Chrome and Opera */ +} + .fc-edge-label { position: absolute; - user-select: none; - pointer-events: none; + transition: transform .2s; opacity: 0.8; + &.ng-leave { + transition: 0s none; + } + &.fc-hover { + transform: scale(1.25); + } + &.fc-selected { + .fc-edge-label-text { + span { + border: solid red; + color: red; + } + } + } + .fc-nodedelete { + right: -13px; + top: -30px; + } + &:focus { + outline: 0; + } } .fc-edge-label-text { position: absolute; - left: 50%; - -webkit-transform: translateX(-50%); - transform: translateX(-50%); + -webkit-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); white-space: nowrap; text-align: center; font-size: 14px; font-weight: 600; - top: 5px; span { + cursor: default; border: solid 2px #003a79; border-radius: 10px; color: #003a79; @@ -255,6 +317,13 @@ } } +.fc-select-rectangle { + border: 2px dashed #5262ff; + position: absolute; + background: rgba(20,125,255,0.1); + z-index: 2; +} + @keyframes dash { from { stroke-dashoffset: 500; diff --git a/ui/src/app/rulechain/rulechain.tpl.html b/ui/src/app/rulechain/rulechain.tpl.html index 9f1141e887..d23f920e0f 100644 --- a/ui/src/app/rulechain/rulechain.tpl.html +++ b/ui/src/app/rulechain/rulechain.tpl.html @@ -16,8 +16,10 @@ --> - +
@@ -50,8 +52,6 @@