thingsboard-memoizeit
Changes
ui/src/app/locale/locale.constant.js 3(+3 -0)
ui/src/app/rulechain/rulechain.controller.js 162(+92 -70)
ui/src/app/rulechain/rulechain.scss 89(+79 -10)
ui/src/app/rulechain/rulechain.tpl.html 15(+11 -4)
Details
diff --git a/ui/src/app/components/confirm-on-exit.directive.js b/ui/src/app/components/confirm-on-exit.directive.js
index fe9a9bd..e04e110 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
ui/src/app/locale/locale.constant.js 3(+3 -0)
diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js
index cca2a11..5dce787 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",
ui/src/app/rulechain/rulechain.controller.js 162(+92 -70)
diff --git a/ui/src/app/rulechain/rulechain.controller.js b/ui/src/app/rulechain/rulechain.controller.js
index b792f13..4eba5b2 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.keyUp = function (evt) {
-
- if (evt.keyCode === deleteKeyCode) {
- vm.modelservice.deleteSelected();
- }
-
- if (evt.keyCode == aKeyCode && vm.ctrlDown) {
- vm.modelservice.selectAll();
- }
+ vm.objectsSelected = objectsSelected;
+ vm.deleteSelected = deleteSelected;
- if (evt.keyCode == escKeyCode) {
- vm.modelservice.deselectAll();
- }
+ initHotKeys();
- 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<ruleNodeComponents.length;i++) {
- var ruleNodeComponent = ruleNodeComponents[i];
- var componentType = ruleNodeComponent.type;
- var model = vm.ruleNodeTypesModel[componentType].model;
- var node = {
- id: model.nodes.length,
- component: ruleNodeComponent,
- name: '',
- nodeClass: vm.types.ruleNodeType[componentType].nodeClass,
- icon: vm.types.ruleNodeType[componentType].icon,
- x: 30,
- y: 10+50*model.nodes.length,
- connectors: []
- };
- if (ruleNodeComponent.configurationDescriptor.nodeDefinition.inEnabled) {
- node.connectors.push(
- {
- type: flowchartConstants.leftConnectorType,
- id: model.nodes.length * 2
- }
- );
+ for (var i=0;i<ruleNodeComponents.length;i++) {
+ var ruleNodeComponent = ruleNodeComponents[i];
+ var componentType = ruleNodeComponent.type;
+ var model = vm.ruleNodeTypesModel[componentType].model;
+ var node = {
+ id: model.nodes.length,
+ component: ruleNodeComponent,
+ name: '',
+ nodeClass: vm.types.ruleNodeType[componentType].nodeClass,
+ icon: vm.types.ruleNodeType[componentType].icon,
+ x: 30,
+ y: 10+50*model.nodes.length,
+ connectors: []
+ };
+ if (ruleNodeComponent.configurationDescriptor.nodeDefinition.inEnabled) {
+ node.connectors.push(
+ {
+ type: flowchartConstants.leftConnectorType,
+ id: model.nodes.length * 2
}
- if (ruleNodeComponent.configurationDescriptor.nodeDefinition.outEnabled) {
- node.connectors.push(
- {
- type: flowchartConstants.rightConnectorType,
- id: model.nodes.length * 2 + 1
- }
- );
+ );
+ }
+ if (ruleNodeComponent.configurationDescriptor.nodeDefinition.outEnabled) {
+ node.connectors.push(
+ {
+ type: flowchartConstants.rightConnectorType,
+ id: model.nodes.length * 2 + 1
}
- model.nodes.push(node);
- }
- vm.ruleChainLibraryLoaded = true;
- prepareRuleChain();
+ );
}
- );
+ model.nodes.push(node);
+ }
+ vm.ruleChainLibraryLoaded = true;
+ prepareRuleChain();
}
function prepareRuleChain() {
@@ -632,6 +646,14 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil,
});
}
+ function objectsSelected() {
+ return vm.modelservice.nodes.getSelectedNodes().length > 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 808661a..f9578ef 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: {
ui/src/app/rulechain/rulechain.scss 89(+79 -10)
diff --git a/ui/src/app/rulechain/rulechain.scss b/ui/src/app/rulechain/rulechain.scss
index 26a5225..c9b2ced 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;
ui/src/app/rulechain/rulechain.tpl.html 15(+11 -4)
diff --git a/ui/src/app/rulechain/rulechain.tpl.html b/ui/src/app/rulechain/rulechain.tpl.html
index 9f1141e..d23f920 100644
--- a/ui/src/app/rulechain/rulechain.tpl.html
+++ b/ui/src/app/rulechain/rulechain.tpl.html
@@ -16,8 +16,10 @@
-->
-<md-content flex tb-expand-fullscreen
- expand-tooltip-direction="bottom" layout="column" class="tb-rulechain">
+<md-content flex tb-expand-fullscreen tb-confirm-on-exit is-dirty="vm.isDirty"
+ expand-tooltip-direction="bottom" layout="column" class="tb-rulechain"
+ ng-keydown="vm.keyDown($event)"
+ ng-keyup="vm.keyUp($event)">
<section class="tb-rulechain-container" flex layout="column">
<div class="tb-rulechain-layout" flex layout="row">
<div class="tb-rulechain-library">
@@ -50,8 +52,6 @@
</div>
<div flex class="tb-rulechain-graph">
<fc-canvas id="tb-rulchain-canvas"
- ng-keydown="vm.keyDown($event)"
- ng-keyup="vm.keyUp($event)"
model="vm.ruleChainModel"
selected-objects="vm.selectedObjects"
edge-style="curved"
@@ -112,6 +112,13 @@
</tb-details-sidenav>
</section>
<section layout="row" layout-wrap class="tb-footer-buttons md-fab" layout-align="start end">
+ <md-button ng-disabled="$root.loading" ng-show="vm.objectsSelected()" class="tb-btn-footer md-accent md-hue-2 md-fab"
+ ng-click="vm.deleteSelected()" aria-label="{{ 'action.delete' | translate }}">
+ <md-tooltip md-direction="top">
+ {{ 'rulenode.delete-selected-objects' | translate }}
+ </md-tooltip>
+ <ng-md-icon icon="delete"></ng-md-icon>
+ </md-button>
<md-button ng-disabled="$root.loading || !vm.isDirty"
class="tb-btn-footer md-accent md-hue-2 md-fab"
aria-label="{{ 'action.apply' | translate }}"
diff --git a/ui/src/app/rulechain/rulenode.tpl.html b/ui/src/app/rulechain/rulenode.tpl.html
index ffc8a0f..55ee3d3 100644
--- a/ui/src/app/rulechain/rulenode.tpl.html
+++ b/ui/src/app/rulechain/rulenode.tpl.html
@@ -22,6 +22,7 @@
ng-mousedown="callbacks.mouseDown($event, node)"
ng-mouseenter="callbacks.mouseEnter($event, node)"
ng-mouseleave="callbacks.mouseLeave($event, node)">
+ <div class="{{flowchartConstants.nodeOverlayClass}}"></div>
<div class="tb-rule-node {{node.nodeClass}}">
<md-icon aria-label="node-type-icon" flex="15"
class="material-icons">{{node.icon}}</md-icon>