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-objects": "Delete selected nodes and connections",
|
||||||
"delete-selected": "Delete selected",
|
"delete-selected": "Delete selected",
|
||||||
"select-all": "Select all",
|
"select-all": "Select all",
|
||||||
|
"copy-selected": "Copy selected",
|
||||||
"deselect-all": "Deselect all",
|
"deselect-all": "Deselect all",
|
||||||
"rulenode-details": "Rule node details",
|
"rulenode-details": "Rule node details",
|
||||||
"debug-mode": "Debug mode",
|
"debug-mode": "Debug mode",
|
||||||
|
|||||||
@ -29,7 +29,7 @@ import addRuleNodeLinkTemplate from './add-link.tpl.html';
|
|||||||
|
|
||||||
/*@ngInject*/
|
/*@ngInject*/
|
||||||
export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $timeout, $mdExpansionPanel, $window, $document, $mdDialog,
|
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) {
|
ruleChain, ruleChainMetaData, ruleNodeComponents) {
|
||||||
|
|
||||||
var vm = this;
|
var vm = this;
|
||||||
@ -140,6 +140,22 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
|
|||||||
subtitle: $translate.instant('rulechain.rulechain')
|
subtitle: $translate.instant('rulechain.rulechain')
|
||||||
};
|
};
|
||||||
contextInfo.items = [];
|
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()) {
|
if (objectsSelected()) {
|
||||||
contextInfo.items.push(
|
contextInfo.items.push(
|
||||||
{
|
{
|
||||||
@ -152,6 +168,17 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
|
|||||||
shortcut: "Esc"
|
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(
|
contextInfo.items.push(
|
||||||
{
|
{
|
||||||
action: function () {
|
action: function () {
|
||||||
@ -176,6 +203,11 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
contextInfo.items.push(
|
||||||
|
{
|
||||||
|
divider: true
|
||||||
|
}
|
||||||
|
);
|
||||||
contextInfo.items.push(
|
contextInfo.items.push(
|
||||||
{
|
{
|
||||||
action: function () {
|
action: function () {
|
||||||
@ -220,6 +252,16 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
|
|||||||
icon: "menu"
|
icon: "menu"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
contextInfo.items.push(
|
||||||
|
{
|
||||||
|
action: function (event) {
|
||||||
|
copyNode(event, node);
|
||||||
|
},
|
||||||
|
enabled: true,
|
||||||
|
value: "action.copy",
|
||||||
|
icon: "content_copy"
|
||||||
|
}
|
||||||
|
);
|
||||||
contextInfo.items.push(
|
contextInfo.items.push(
|
||||||
{
|
{
|
||||||
action: function () {
|
action: function () {
|
||||||
@ -526,7 +568,7 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
|
|||||||
if (destNode.component.type == types.ruleNodeType.RULE_CHAIN.value) {
|
if (destNode.component.type == types.ruleNodeType.RULE_CHAIN.value) {
|
||||||
deferred.reject();
|
deferred.reject();
|
||||||
} else {
|
} 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) {
|
if (res && res.length) {
|
||||||
vm.modelservice.edges.delete(res[0]);
|
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);
|
loadRuleChainLibrary(ruleNodeComponents, true);
|
||||||
|
|
||||||
$scope.$watch('vm.ruleNodeSearch',
|
$scope.$watch('vm.ruleNodeSearch',
|
||||||
|
|||||||
@ -108,11 +108,14 @@
|
|||||||
#tb-rule-chain-context-menu {
|
#tb-rule-chain-context-menu {
|
||||||
padding-top: 0px;
|
padding-top: 0px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
max-height: 404px;
|
||||||
.tb-context-menu-header {
|
.tb-context-menu-header {
|
||||||
padding: 8px 5px 5px;
|
padding: 8px 5px 5px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
height: 30px;
|
||||||
|
min-height: 30px;
|
||||||
&.tb-rulechain {
|
&.tb-rulechain {
|
||||||
background-color: #aac7e4;
|
background-color: #aac7e4;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -122,7 +122,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div ng-repeat="item in vm.contextInfo.items">
|
<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-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>
|
<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>
|
<span translate>{{item.value}}</span>
|
||||||
|
|||||||
@ -24,10 +24,11 @@ export default angular.module('thingsboard.itembuffer', [angularStorage])
|
|||||||
.name;
|
.name;
|
||||||
|
|
||||||
/*@ngInject*/
|
/*@ngInject*/
|
||||||
function ItemBuffer($q, bufferStore, types, utils, dashboardUtils) {
|
function ItemBuffer($q, bufferStore, types, utils, dashboardUtils, ruleChainService) {
|
||||||
|
|
||||||
const WIDGET_ITEM = "widget_item";
|
const WIDGET_ITEM = "widget_item";
|
||||||
const WIDGET_REFERENCE = "widget_reference";
|
const WIDGET_REFERENCE = "widget_reference";
|
||||||
|
const RULE_NODES = "rule_nodes";
|
||||||
|
|
||||||
var service = {
|
var service = {
|
||||||
prepareWidgetItem: prepareWidgetItem,
|
prepareWidgetItem: prepareWidgetItem,
|
||||||
@ -37,7 +38,10 @@ function ItemBuffer($q, bufferStore, types, utils, dashboardUtils) {
|
|||||||
canPasteWidgetReference: canPasteWidgetReference,
|
canPasteWidgetReference: canPasteWidgetReference,
|
||||||
pasteWidget: pasteWidget,
|
pasteWidget: pasteWidget,
|
||||||
pasteWidgetReference: pasteWidgetReference,
|
pasteWidgetReference: pasteWidgetReference,
|
||||||
addWidgetToDashboard: addWidgetToDashboard
|
addWidgetToDashboard: addWidgetToDashboard,
|
||||||
|
copyRuleNodes: copyRuleNodes,
|
||||||
|
hasRuleNodes: hasRuleNodes,
|
||||||
|
pasteRuleNodes: pasteRuleNodes
|
||||||
}
|
}
|
||||||
|
|
||||||
return service;
|
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) {
|
function copyWidget(dashboard, sourceState, sourceLayout, widget) {
|
||||||
var widgetItem = prepareWidgetItem(dashboard, sourceState, sourceLayout, widget);
|
var widgetItem = prepareWidgetItem(dashboard, sourceState, sourceLayout, widget);
|
||||||
bufferStore.set(WIDGET_ITEM, angular.toJson(widgetItem));
|
bufferStore.set(WIDGET_ITEM, angular.toJson(widgetItem));
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user