Rule Chain UI: Copy/Paste support.
This commit is contained in:
parent
737f2cc92a
commit
b13d666f1b
@ -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",
|
||||
|
||||
@ -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(
|
||||
{
|
||||
@ -152,6 +168,17 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
|
||||
shortcut: "Esc"
|
||||
}
|
||||
);
|
||||
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 () {
|
||||
@ -176,6 +203,11 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
|
||||
}
|
||||
);
|
||||
}
|
||||
contextInfo.items.push(
|
||||
{
|
||||
divider: true
|
||||
}
|
||||
);
|
||||
contextInfo.items.push(
|
||||
{
|
||||
action: function () {
|
||||
@ -220,6 +252,16 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
|
||||
icon: "menu"
|
||||
}
|
||||
);
|
||||
contextInfo.items.push(
|
||||
{
|
||||
action: function (event) {
|
||||
copyNode(event, node);
|
||||
},
|
||||
enabled: true,
|
||||
value: "action.copy",
|
||||
icon: "content_copy"
|
||||
}
|
||||
);
|
||||
contextInfo.items.push(
|
||||
{
|
||||
action: function () {
|
||||
@ -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',
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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));
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user