thingsboard-memoizeit
Changes
ui/src/app/locale/locale.constant.js 1(+1 -0)
ui/src/app/rulechain/rulechain.controller.js 152(+150 -2)
ui/src/app/rulechain/rulechain.scss 3(+3 -0)
ui/src/app/services/item-buffer.service.js 71(+69 -2)
Details
ui/src/app/locale/locale.constant.js 1(+1 -0)
diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js
index bd5858f..c515e3e 100644
--- a/ui/src/app/locale/locale.constant.js
+++ b/ui/src/app/locale/locale.constant.js
@@ -1208,6 +1208,7 @@ export default angular.module('thingsboard.locale', [])
"delete-selected-objects": "Delete selected nodes and connections",
"delete-selected": "Delete selected",
"select-all": "Select all",
+ "copy-selected": "Copy selected",
"deselect-all": "Deselect all",
"rulenode-details": "Rule node details",
"debug-mode": "Debug mode",
ui/src/app/rulechain/rulechain.controller.js 152(+150 -2)
diff --git a/ui/src/app/rulechain/rulechain.controller.js b/ui/src/app/rulechain/rulechain.controller.js
index f657216..d77d644 100644
--- a/ui/src/app/rulechain/rulechain.controller.js
+++ b/ui/src/app/rulechain/rulechain.controller.js
@@ -29,7 +29,7 @@ import addRuleNodeLinkTemplate from './add-link.tpl.html';
/*@ngInject*/
export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $timeout, $mdExpansionPanel, $window, $document, $mdDialog,
- $filter, $translate, hotkeys, types, ruleChainService, Modelfactory, flowchartConstants,
+ $filter, $translate, hotkeys, types, ruleChainService, itembuffer, Modelfactory, flowchartConstants,
ruleChain, ruleChainMetaData, ruleNodeComponents) {
var vm = this;
@@ -140,6 +140,22 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
subtitle: $translate.instant('rulechain.rulechain')
};
contextInfo.items = [];
+ contextInfo.items.push(
+ {
+ action: function ($event) {
+ pasteRuleNodes($event);
+ },
+ enabled: itembuffer.hasRuleNodes(),
+ value: "action.paste",
+ icon: "content_paste",
+ shortcut: "M-V"
+ }
+ );
+ contextInfo.items.push(
+ {
+ divider: true
+ }
+ );
if (objectsSelected()) {
contextInfo.items.push(
{
@@ -154,6 +170,17 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
);
contextInfo.items.push(
{
+ action: function (event) {
+ copyRuleNodes(event);
+ },
+ enabled: true,
+ value: "rulenode.copy-selected",
+ icon: "content_copy",
+ shortcut: "M-C"
+ }
+ );
+ contextInfo.items.push(
+ {
action: function () {
vm.modelservice.deleteSelected();
},
@@ -178,6 +205,11 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
}
contextInfo.items.push(
{
+ divider: true
+ }
+ );
+ contextInfo.items.push(
+ {
action: function () {
vm.saveRuleChain();
},
@@ -222,6 +254,16 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
);
contextInfo.items.push(
{
+ action: function (event) {
+ copyNode(event, node);
+ },
+ enabled: true,
+ value: "action.copy",
+ icon: "content_copy"
+ }
+ );
+ contextInfo.items.push(
+ {
action: function () {
vm.canvasControl.modelservice.nodes.delete(node);
},
@@ -526,7 +568,7 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
if (destNode.component.type == types.ruleNodeType.RULE_CHAIN.value) {
deferred.reject();
} else {
- var res = $filter('filter')(vm.ruleChainModel.edges, {source: vm.inputConnectorId});
+ var res = $filter('filter')(vm.ruleChainModel.edges, {source: vm.inputConnectorId}, true);
if (res && res.length) {
vm.modelservice.edges.delete(res[0]);
}
@@ -582,6 +624,112 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
}
}
+ function copyNode(event, node) {
+ var offset = angular.element(vm.canvasControl.modelservice.getCanvasHtmlElement()).offset();
+ var x = Math.round(event.clientX - offset.left);
+ var y = Math.round(event.clientY - offset.top);
+ itembuffer.copyRuleNodes(x, y, [node], []);
+ }
+
+ function copyRuleNodes(event) {
+ var offset = angular.element(vm.canvasControl.modelservice.getCanvasHtmlElement()).offset();
+ var x = Math.round(event.clientX - offset.left);
+ var y = Math.round(event.clientY - offset.top);
+ var nodes = vm.modelservice.nodes.getSelectedNodes();
+ var edges = vm.modelservice.edges.getSelectedEdges();
+ var connections = [];
+ for (var i=0;i<edges.length;i++) {
+ var edge = edges[i];
+ var sourceNode = vm.modelservice.nodes.getNodeByConnectorId(edge.source);
+ var destNode = vm.modelservice.nodes.getNodeByConnectorId(edge.destination);
+ var isInputSource = sourceNode.component.type == types.ruleNodeType.INPUT.value;
+ var fromIndex = nodes.indexOf(sourceNode);
+ var toIndex = nodes.indexOf(destNode);
+ if ( (isInputSource || fromIndex > -1) && toIndex > -1 ) {
+ var connection = {
+ isInputSource: isInputSource,
+ fromIndex: fromIndex,
+ toIndex: toIndex,
+ label: edge.label
+ };
+ connections.push(connection);
+ }
+ }
+ itembuffer.copyRuleNodes(x, y, nodes, connections);
+ }
+
+ function pasteRuleNodes(event) {
+ var offset = angular.element(vm.canvasControl.modelservice.getCanvasHtmlElement()).offset();
+ var x = Math.round(event.clientX - offset.left);
+ var y = Math.round(event.clientY - offset.top);
+ var ruleNodes = itembuffer.pasteRuleNodes(x, y, event);
+ if (ruleNodes) {
+ vm.modelservice.deselectAll();
+ var nodes = [];
+ for (var i=0;i<ruleNodes.nodes.length;i++) {
+ var node = ruleNodes.nodes[i];
+ node.id = 'rule-chain-node-' + vm.nextNodeID++;
+ var component = node.component;
+ if (component.configurationDescriptor.nodeDefinition.inEnabled) {
+ node.connectors.push(
+ {
+ type: flowchartConstants.leftConnectorType,
+ id: vm.nextConnectorID++
+ }
+ );
+ }
+ if (component.configurationDescriptor.nodeDefinition.outEnabled) {
+ node.connectors.push(
+ {
+ type: flowchartConstants.rightConnectorType,
+ id: vm.nextConnectorID++
+ }
+ );
+ }
+ nodes.push(node);
+ vm.ruleChainModel.nodes.push(node);
+ vm.modelservice.nodes.select(node);
+ }
+ for (i=0;i<ruleNodes.connections.length;i++) {
+ var connection = ruleNodes.connections[i];
+ var sourceNode = nodes[connection.fromIndex];
+ var destNode = nodes[connection.toIndex];
+ if ( (connection.isInputSource || sourceNode) && destNode ) {
+ var source, destination;
+ if (connection.isInputSource) {
+ source = vm.inputConnectorId;
+ } else {
+ var sourceConnectors = vm.modelservice.nodes.getConnectorsByType(sourceNode, flowchartConstants.rightConnectorType);
+ if (sourceConnectors && sourceConnectors.length) {
+ source = sourceConnectors[0].id;
+ }
+ }
+ var destConnectors = vm.modelservice.nodes.getConnectorsByType(destNode, flowchartConstants.leftConnectorType);
+ if (destConnectors && destConnectors.length) {
+ destination = destConnectors[0].id;
+ }
+ if (source && destination) {
+ var edge = {
+ source: source,
+ destination: destination,
+ label: connection.label
+ };
+ vm.ruleChainModel.edges.push(edge);
+ vm.modelservice.edges.select(edge);
+ }
+ }
+ }
+
+ if (vm.canvasControl.adjustCanvasSize) {
+ vm.canvasControl.adjustCanvasSize();
+ }
+
+ updateRuleNodesHighlight();
+
+ validate();
+ }
+ }
+
loadRuleChainLibrary(ruleNodeComponents, true);
$scope.$watch('vm.ruleNodeSearch',
ui/src/app/rulechain/rulechain.scss 3(+3 -0)
diff --git a/ui/src/app/rulechain/rulechain.scss b/ui/src/app/rulechain/rulechain.scss
index b89557b..257a97d 100644
--- a/ui/src/app/rulechain/rulechain.scss
+++ b/ui/src/app/rulechain/rulechain.scss
@@ -108,11 +108,14 @@
#tb-rule-chain-context-menu {
padding-top: 0px;
border-radius: 8px;
+ max-height: 404px;
.tb-context-menu-header {
padding: 8px 5px 5px;
font-size: 14px;
display: flex;
flex-direction: row;
+ height: 30px;
+ min-height: 30px;
&.tb-rulechain {
background-color: #aac7e4;
}
diff --git a/ui/src/app/rulechain/rulechain.tpl.html b/ui/src/app/rulechain/rulechain.tpl.html
index 51d8d71..81e8aba 100644
--- a/ui/src/app/rulechain/rulechain.tpl.html
+++ b/ui/src/app/rulechain/rulechain.tpl.html
@@ -122,7 +122,8 @@
</div>
</div>
<div ng-repeat="item in vm.contextInfo.items">
- <md-menu-item>
+ <md-divider ng-if="item.divider"></md-divider>
+ <md-menu-item ng-if="!item.divider">
<md-button ng-disabled="!item.enabled" ng-click="item.action(vm.contextMenuEvent)">
<md-icon ng-if="item.icon" md-menu-align-target aria-label="{{ item.value | translate }}" class="material-icons">{{item.icon}}</md-icon>
<span translate>{{item.value}}</span>
ui/src/app/services/item-buffer.service.js 71(+69 -2)
diff --git a/ui/src/app/services/item-buffer.service.js b/ui/src/app/services/item-buffer.service.js
index 9fce811..d6a9ce7 100644
--- a/ui/src/app/services/item-buffer.service.js
+++ b/ui/src/app/services/item-buffer.service.js
@@ -24,10 +24,11 @@ export default angular.module('thingsboard.itembuffer', [angularStorage])
.name;
/*@ngInject*/
-function ItemBuffer($q, bufferStore, types, utils, dashboardUtils) {
+function ItemBuffer($q, bufferStore, types, utils, dashboardUtils, ruleChainService) {
const WIDGET_ITEM = "widget_item";
const WIDGET_REFERENCE = "widget_reference";
+ const RULE_NODES = "rule_nodes";
var service = {
prepareWidgetItem: prepareWidgetItem,
@@ -37,7 +38,10 @@ function ItemBuffer($q, bufferStore, types, utils, dashboardUtils) {
canPasteWidgetReference: canPasteWidgetReference,
pasteWidget: pasteWidget,
pasteWidgetReference: pasteWidgetReference,
- addWidgetToDashboard: addWidgetToDashboard
+ addWidgetToDashboard: addWidgetToDashboard,
+ copyRuleNodes: copyRuleNodes,
+ hasRuleNodes: hasRuleNodes,
+ pasteRuleNodes: pasteRuleNodes
}
return service;
@@ -151,6 +155,69 @@ function ItemBuffer($q, bufferStore, types, utils, dashboardUtils) {
};
}
+ function copyRuleNodes(x, y, nodes, connections) {
+ var ruleNodes = {
+ nodes: [],
+ connections: [],
+ originX: x,
+ originY: y
+ };
+ for (var i=0;i<nodes.length;i++) {
+ var origNode = nodes[i];
+ var node = {
+ additionalInfo: origNode.additionalInfo,
+ configuration: origNode.configuration,
+ debugMode: origNode.debugMode,
+ x: origNode.x,
+ y: origNode.y,
+ name: origNode.name,
+ componentClazz: origNode.component.clazz,
+ };
+ if (origNode.targetRuleChainId) {
+ node.targetRuleChainId = origNode.targetRuleChainId;
+ }
+ if (origNode.error) {
+ node.error = origNode.error;
+ }
+ ruleNodes.nodes.push(node);
+ }
+ for (i=0;i<connections.length;i++) {
+ var connection = connections[i];
+ ruleNodes.connections.push(connection);
+ }
+ bufferStore.set(RULE_NODES, angular.toJson(ruleNodes));
+ }
+
+ function hasRuleNodes() {
+ return bufferStore.get(RULE_NODES);
+ }
+
+ function pasteRuleNodes(x, y) {
+ var ruleNodesJson = bufferStore.get(RULE_NODES);
+ if (ruleNodesJson) {
+ var ruleNodes = angular.fromJson(ruleNodesJson);
+ var deltaX = x - ruleNodes.originX;
+ var deltaY = y - ruleNodes.originY;
+ for (var i=0;i<ruleNodes.nodes.length;i++) {
+ var node = ruleNodes.nodes[i];
+ var component = ruleChainService.getRuleNodeComponentByClazz(node.componentClazz);
+ if (component) {
+ delete node.componentClazz;
+ node.component = component;
+ node.nodeClass = types.ruleNodeType[component.type].nodeClass;
+ node.icon = types.ruleNodeType[component.type].icon;
+ node.connectors = [];
+ node.x = Math.round(node.x + deltaX);
+ node.y = Math.round(node.y + deltaY);
+ } else {
+ return null;
+ }
+ }
+ return ruleNodes;
+ }
+ return null;
+ }
+
function copyWidget(dashboard, sourceState, sourceLayout, widget) {
var widgetItem = prepareWidgetItem(dashboard, sourceState, sourceLayout, widget);
bufferStore.set(WIDGET_ITEM, angular.toJson(widgetItem));