/*
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import './rulechain.scss';
import 'tooltipster/dist/css/tooltipster.bundle.min.css';
import 'tooltipster/dist/js/tooltipster.bundle.min.js';
import 'tooltipster/dist/css/plugins/tooltipster/sideTip/themes/tooltipster-sideTip-shadow.min.css';
/* eslint-disable import/no-unresolved, import/default */
import addRuleNodeTemplate from './add-rulenode.tpl.html';
import addRuleNodeLinkTemplate from './add-link.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $timeout, $mdExpansionPanel, $window, $document, $mdDialog,
$filter, $translate, hotkeys, types, ruleChainService, itembuffer, Modelfactory, flowchartConstants,
ruleChain, ruleChainMetaData, ruleNodeComponents, helpLinks) {
var vm = this;
vm.$mdExpansionPanel = $mdExpansionPanel;
vm.types = types;
if ($state.current.data.import && !ruleChain) {
$state.go('home.ruleChains');
return;
}
vm.isImport = $state.current.data.import;
vm.isConfirmOnExit = false;
$scope.$watch(function() {
return vm.isDirty || vm.isImport;
}, (val) => {
vm.isConfirmOnExit = val;
});
vm.errorTooltips = {};
vm.isFullscreen = false;
vm.editingRuleNode = null;
vm.isEditingRuleNode = false;
vm.editingRuleNodeLink = null;
vm.isEditingRuleNodeLink = false;
vm.isLibraryOpen = true;
vm.enableHotKeys = true;
Object.defineProperty(vm, 'isLibraryOpenReadonly', {
get: function() { return vm.isLibraryOpen },
set: function() {}
});
vm.ruleNodeSearch = '';
vm.ruleChain = ruleChain;
vm.ruleChainMetaData = ruleChainMetaData;
vm.canvasControl = {};
vm.ruleChainModel = {
nodes: [],
edges: []
};
vm.ruleNodeTypesModel = {};
vm.ruleNodeTypesCanvasControl = {};
vm.ruleChainLibraryLoaded = false;
for (var type in types.ruleNodeType) {
if (!types.ruleNodeType[type].special) {
vm.ruleNodeTypesModel[type] = {
model: {
nodes: [],
edges: []
},
selectedObjects: []
};
vm.ruleNodeTypesCanvasControl[type] = {};
}
}
vm.selectedObjects = [];
vm.modelservice = Modelfactory(vm.ruleChainModel, vm.selectedObjects);
vm.saveRuleChain = saveRuleChain;
vm.revertRuleChain = revertRuleChain;
vm.objectsSelected = objectsSelected;
vm.deleteSelected = deleteSelected;
vm.triggerResize = triggerResize;
vm.openRuleChainContextMenu = openRuleChainContextMenu;
vm.helpLinkIdForRuleNodeType = helpLinkIdForRuleNodeType;
initHotKeys();
function openRuleChainContextMenu($event, $mdOpenMousepointMenu) {
if (vm.canvasControl.modelservice && !$event.ctrlKey && !$event.metaKey) {
var x = $event.clientX;
var y = $event.clientY;
var item = vm.canvasControl.modelservice.getItemInfoAtPoint(x, y);
vm.contextInfo = prepareContextMenu(item);
if (vm.contextInfo.items && vm.contextInfo.items.length > 0) {
vm.contextMenuEvent = $event;
$mdOpenMousepointMenu($event);
return false;
}
}
}
function prepareContextMenu(item) {
if (objectsSelected() || (!item.node && !item.edge)) {
return prepareRuleChainContextMenu();
} else if (item.node) {
return prepareRuleNodeContextMenu(item.node);
} else if (item.edge) {
return prepareEdgeContextMenu(item.edge);
}
}
function prepareRuleChainContextMenu() {
var contextInfo = {
headerClass: 'tb-rulechain',
icon: 'settings_ethernet',
title: vm.ruleChain.name,
subtitle: $translate.instant('rulechain.rulechain')
};
contextInfo.items = [];
if (vm.modelservice.nodes.getSelectedNodes().length) {
contextInfo.items.push(
{
action: function () {
copyRuleNodes();
},
enabled: true,
value: "rulenode.copy-selected",
icon: "content_copy",
shortcut: "M-C"
}
);
}
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(
{
action: function () {
vm.modelservice.deselectAll();
},
enabled: true,
value: "rulenode.deselect-all",
icon: "tab_unselected",
shortcut: "Esc"
}
);
contextInfo.items.push(
{
action: function () {
vm.modelservice.deleteSelected();
},
enabled: true,
value: "rulenode.delete-selected",
icon: "clear",
shortcut: "Del"
}
);
} else {
contextInfo.items.push(
{
action: function () {
vm.modelservice.selectAll();
},
enabled: true,
value: "rulenode.select-all",
icon: "select_all",
shortcut: "M-A"
}
);
}
contextInfo.items.push(
{
divider: true
}
);
contextInfo.items.push(
{
action: function () {
vm.saveRuleChain();
},
enabled: !(vm.isInvalid || (!vm.isDirty && !vm.isImport)),
value: "action.apply-changes",
icon: "done",
shortcut: "M-S"
}
);
contextInfo.items.push(
{
action: function () {
vm.revertRuleChain();
},
enabled: vm.isDirty,
value: "action.decline-changes",
icon: "close",
shortcut: "M-Z"
}
);
return contextInfo;
}
function prepareRuleNodeContextMenu(node) {
var contextInfo = {
headerClass: node.nodeClass,
icon: node.icon,
iconUrl: node.iconUrl,
title: node.name,
subtitle: node.component.name
};
contextInfo.items = [];
if (!node.readonly) {
contextInfo.items.push(
{
action: function () {
openNodeDetails(node);
},
enabled: true,
value: "rulenode.details",
icon: "menu"
}
);
contextInfo.items.push(
{
action: function () {
copyNode(node);
},
enabled: true,
value: "action.copy",
icon: "content_copy"
}
);
contextInfo.items.push(
{
action: function () {
vm.canvasControl.modelservice.nodes.delete(node);
},
enabled: true,
value: "action.delete",
icon: "clear",
shortcut: "M-X"
}
);
}
return contextInfo;
}
function prepareEdgeContextMenu(edge) {
var contextInfo = {
headerClass: 'tb-link',
icon: 'trending_flat',
title: edge.label,
subtitle: $translate.instant('rulenode.link')
};
contextInfo.items = [];
var sourceNode = vm.modelservice.nodes.getNodeByConnectorId(edge.source);
if (sourceNode.component.type != types.ruleNodeType.INPUT.value) {
contextInfo.items.push(
{
action: function () {
openLinkDetails(edge);
},
enabled: true,
value: "rulenode.details",
icon: "menu"
}
);
}
contextInfo.items.push(
{
action: function () {
vm.canvasControl.modelservice.edges.delete(edge);
},
enabled: true,
value: "action.delete",
icon: "clear",
shortcut: "M-X"
}
);
return contextInfo;
}
function initHotKeys() {
hotkeys.bindTo($scope)
.add({
combo: 'ctrl+a',
description: $translate.instant('rulenode.select-all-objects'),
allowIn: ['INPUT', 'SELECT', 'TEXTAREA'],
callback: function (event) {
if (vm.enableHotKeys) {
event.preventDefault();
vm.modelservice.selectAll();
}
}
})
.add({
combo: 'ctrl+c',
description: $translate.instant('rulenode.copy-selected'),
allowIn: ['INPUT', 'SELECT', 'TEXTAREA'],
callback: function (event) {
if (vm.enableHotKeys) {
event.preventDefault();
copyRuleNodes();
}
}
})
.add({
combo: 'ctrl+v',
description: $translate.instant('action.paste'),
allowIn: ['INPUT', 'SELECT', 'TEXTAREA'],
callback: function (event) {
if (vm.enableHotKeys) {
event.preventDefault();
if (itembuffer.hasRuleNodes()) {
pasteRuleNodes();
}
}
}
})
.add({
combo: 'esc',
description: $translate.instant('rulenode.deselect-all-objects'),
allowIn: ['INPUT', 'SELECT', 'TEXTAREA'],
callback: function (event) {
if (vm.enableHotKeys) {
event.preventDefault();
event.stopPropagation();
vm.modelservice.deselectAll();
}
}
})
.add({
combo: 'ctrl+s',
description: $translate.instant('action.apply'),
allowIn: ['INPUT', 'SELECT', 'TEXTAREA'],
callback: function (event) {
if (vm.enableHotKeys) {
event.preventDefault();
vm.saveRuleChain();
}
}
})
.add({
combo: 'ctrl+z',
description: $translate.instant('action.decline-changes'),
allowIn: ['INPUT', 'SELECT', 'TEXTAREA'],
callback: function (event) {
if (vm.enableHotKeys) {
event.preventDefault();
vm.revertRuleChain();
}
}
})
.add({
combo: 'del',
description: $translate.instant('rulenode.delete-selected-objects'),
allowIn: ['INPUT', 'SELECT', 'TEXTAREA'],
callback: function (event) {
if (vm.enableHotKeys) {
event.preventDefault();
vm.modelservice.deleteSelected();
}
}
})
}
vm.onEditRuleNodeClosed = function() {
vm.editingRuleNode = null;
};
vm.onEditRuleNodeLinkClosed = function() {
vm.editingRuleNodeLink = null;
};
vm.saveRuleNode = function(theForm) {
$scope.$broadcast('form-submit');
if (theForm.$valid) {
theForm.$setPristine();
if (vm.editingRuleNode.error) {
delete vm.editingRuleNode.error;
}
vm.ruleChainModel.nodes[vm.editingRuleNodeIndex] = vm.editingRuleNode;
vm.editingRuleNode = angular.copy(vm.editingRuleNode);
updateRuleNodesHighlight();
}
};
vm.saveRuleNodeLink = function(theForm) {
theForm.$setPristine();
vm.ruleChainModel.edges[vm.editingRuleNodeLinkIndex] = vm.editingRuleNodeLink;
vm.editingRuleNodeLink = angular.copy(vm.editingRuleNodeLink);
};
vm.onRevertRuleNodeEdit = function(theForm) {
theForm.$setPristine();
var node = vm.ruleChainModel.nodes[vm.editingRuleNodeIndex];
vm.editingRuleNode = angular.copy(node);
};
vm.onRevertRuleNodeLinkEdit = function(theForm) {
theForm.$setPristine();
var edge = vm.ruleChainModel.edges[vm.editingRuleNodeLinkIndex];
vm.editingRuleNodeLink = angular.copy(edge);
};
vm.nodeLibCallbacks = {
nodeCallbacks: {
'mouseEnter': function (event, node) {
displayLibNodeDescriptionTooltip(event, node);
},
'mouseLeave': function () {
destroyTooltips();
},
'mouseDown': function () {
destroyTooltips();
}
}
};
vm.typeHeaderMouseEnter = function(event, typeId) {
var ruleNodeType = types.ruleNodeType[typeId];
displayTooltip(event,
'<div class="tb-rule-node-tooltip tb-lib-tooltip">' +
'<div id="tb-node-content" layout="column">' +
'<div class="tb-node-title">' + $translate.instant(ruleNodeType.name) + '</div>' +
'<div class="tb-node-details">' + $translate.instant(ruleNodeType.details) + '</div>' +
'</div>' +
'</div>'
);
};
vm.destroyTooltips = destroyTooltips;
function helpLinkIdForRuleNodeType() {
return helpLinks.getRuleNodeLink(vm.editingRuleNode);
}
function destroyTooltips() {
if (vm.tooltipTimeout) {
$timeout.cancel(vm.tooltipTimeout);
vm.tooltipTimeout = null;
}
var instances = angular.element.tooltipster.instances();
instances.forEach((instance) => {
if (!instance.isErrorTooltip) {
instance.destroy();
}
});
}
function displayLibNodeDescriptionTooltip(event, node) {
displayTooltip(event,
'<div class="tb-rule-node-tooltip tb-lib-tooltip">' +
'<div id="tb-node-content" layout="column">' +
'<div class="tb-node-title">' + node.component.name + '</div>' +
'<div class="tb-node-description">' + node.component.configurationDescriptor.nodeDefinition.description + '</div>' +
'<div class="tb-node-details">' + node.component.configurationDescriptor.nodeDefinition.details + '</div>' +
'</div>' +
'</div>'
);
}
function displayNodeDescriptionTooltip(event, node) {
if (!vm.errorTooltips[node.id]) {
var name, desc, details;
if (node.component.type == vm.types.ruleNodeType.INPUT.value) {
name = $translate.instant(vm.types.ruleNodeType.INPUT.name) + '';
desc = $translate.instant(vm.types.ruleNodeType.INPUT.details) + '';
} else {
name = node.name;
desc = $translate.instant(vm.types.ruleNodeType[node.component.type].name) + ' - ' + node.component.name;
if (node.additionalInfo) {
details = node.additionalInfo.description;
}
}
var tooltipContent = '<div class="tb-rule-node-tooltip">' +
'<div id="tb-node-content" layout="column">' +
'<div class="tb-node-title">' + name + '</div>' +
'<div class="tb-node-description">' + desc + '</div>';
if (details) {
tooltipContent += '<div class="tb-node-details">' + details + '</div>';
}
tooltipContent += '</div>' +
'</div>';
displayTooltip(event, tooltipContent);
}
}
function displayTooltip(event, content) {
destroyTooltips();
vm.tooltipTimeout = $timeout(() => {
var element = angular.element(event.target);
element.tooltipster(
{
theme: 'tooltipster-shadow',
delay: 100,
trigger: 'custom',
triggerOpen: {
click: false,
tap: false
},
triggerClose: {
click: true,
tap: true,
scroll: true
},
side: 'right',
trackOrigin: true
}
);
var contentElement = angular.element(content);
$compile(contentElement)($scope);
var tooltip = element.tooltipster('instance');
tooltip.content(contentElement);
tooltip.open();
}, 500);
}
function updateNodeErrorTooltip(node) {
if (node.error) {
var element = angular.element('#' + node.id);
var tooltip = vm.errorTooltips[node.id];
if (!tooltip || !element.hasClass("tooltipstered")) {
element.tooltipster(
{
theme: 'tooltipster-shadow',
delay: 0,
animationDuration: 0,
trigger: 'custom',
triggerOpen: {
click: false,
tap: false
},
triggerClose: {
click: false,
tap: false,
scroll: false
},
side: 'top',
trackOrigin: true
}
);
var content = '<div class="tb-rule-node-error-tooltip">' +
'<div id="tooltip-content" layout="column">' +
'<div class="tb-node-details">' + node.error + '</div>' +
'</div>' +
'</div>';
var contentElement = angular.element(content);
$compile(contentElement)($scope);
tooltip = element.tooltipster('instance');
tooltip.isErrorTooltip = true;
tooltip.content(contentElement);
vm.errorTooltips[node.id] = tooltip;
}
$mdUtil.nextTick(() => {
tooltip.open();
});
} else {
if (vm.errorTooltips[node.id]) {
tooltip = vm.errorTooltips[node.id];
tooltip.destroy();
delete vm.errorTooltips[node.id];
}
}
}
function updateErrorTooltips(hide) {
for (var nodeId in vm.errorTooltips) {
var tooltip = vm.errorTooltips[nodeId];
if (hide) {
tooltip.close();
} else {
tooltip.open();
}
}
}
$scope.$watch(function() {
return vm.isEditingRuleNode || vm.isEditingRuleNodeLink;
}, (val) => {
vm.enableHotKeys = !val;
updateErrorTooltips(val);
});
vm.editCallbacks = {
edgeDoubleClick: function (event, edge) {
openLinkDetails(edge);
},
edgeEdit: function(event, edge) {
openLinkDetails(edge);
},
nodeCallbacks: {
'doubleClick': function (event, node) {
openNodeDetails(node);
},
'nodeEdit': function (event, node) {
openNodeDetails(node);
},
'mouseEnter': function (event, node) {
displayNodeDescriptionTooltip(event, node);
},
'mouseLeave': function () {
destroyTooltips();
},
'mouseDown': function () {
destroyTooltips();
}
},
isValidEdge: function (source, destination) {
return source.type === flowchartConstants.rightConnectorType && destination.type === flowchartConstants.leftConnectorType;
},
createEdge: function (event, edge) {
var deferred = $q.defer();
var sourceNode = vm.modelservice.nodes.getNodeByConnectorId(edge.source);
if (sourceNode.component.type == types.ruleNodeType.INPUT.value) {
var destNode = vm.modelservice.nodes.getNodeByConnectorId(edge.destination);
if (destNode.component.type == types.ruleNodeType.RULE_CHAIN.value) {
deferred.reject();
} else {
var res = $filter('filter')(vm.ruleChainModel.edges, {source: vm.inputConnectorId}, true);
if (res && res.length) {
vm.modelservice.edges.delete(res[0]);
}
deferred.resolve(edge);
}
} else {
if (edge.label) {
deferred.resolve(edge);
} else {
var labels = ruleChainService.getRuleNodeSupportedLinks(sourceNode.component);
vm.enableHotKeys = false;
addRuleNodeLink(event, edge, labels).then(
(link) => {
deferred.resolve(link);
vm.enableHotKeys = true;
},
() => {
deferred.reject();
vm.enableHotKeys = true;
}
);
}
}
return deferred.promise;
},
dropNode: function (event, node) {
addRuleNode(event, node);
}
};
function openNodeDetails(node) {
if (node.component.type != types.ruleNodeType.INPUT.value) {
vm.isEditingRuleNodeLink = false;
vm.editingRuleNodeLink = null;
vm.isEditingRuleNode = true;
vm.editingRuleNodeIndex = vm.ruleChainModel.nodes.indexOf(node);
vm.editingRuleNode = angular.copy(node);
$mdUtil.nextTick(() => {
if (vm.ruleNodeForm) {
vm.ruleNodeForm.$setPristine();
}
});
}
}
function openLinkDetails(edge) {
var sourceNode = vm.modelservice.nodes.getNodeByConnectorId(edge.source);
if (sourceNode.component.type != types.ruleNodeType.INPUT.value) {
vm.isEditingRuleNode = false;
vm.editingRuleNode = null;
vm.editingRuleNodeLinkLabels = ruleChainService.getRuleNodeSupportedLinks(sourceNode.component);
vm.isEditingRuleNodeLink = true;
vm.editingRuleNodeLinkIndex = vm.ruleChainModel.edges.indexOf(edge);
vm.editingRuleNodeLink = angular.copy(edge);
$mdUtil.nextTick(() => {
if (vm.ruleNodeLinkForm) {
vm.ruleNodeLinkForm.$setPristine();
}
});
}
}
function copyNode(node) {
itembuffer.copyRuleNodes([node], []);
}
function copyRuleNodes() {
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(nodes, connections);
}
function pasteRuleNodes(event) {
var canvas = angular.element(vm.canvasControl.modelservice.getCanvasHtmlElement());
var x,y;
if (event) {
var offset = canvas.offset();
x = Math.round(event.clientX - offset.left);
y = Math.round(event.clientY - offset.top);
} else {
var scrollParent = canvas.parent();
var scrollTop = scrollParent.scrollTop();
var scrollLeft = scrollParent.scrollLeft();
x = scrollLeft + scrollParent.width()/2;
y = scrollTop + scrollParent.height()/2;
}
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',
function (newVal, oldVal) {
if (!angular.equals(newVal, oldVal)) {
var res = $filter('filter')(ruleNodeComponents, {name: vm.ruleNodeSearch});
loadRuleChainLibrary(res);
}
}
);
$scope.$on('searchTextUpdated', function () {
updateRuleNodesHighlight();
});
function loadRuleChainLibrary(ruleNodeComponents, loadRuleChain) {
for (var componentType in vm.ruleNodeTypesModel) {
vm.ruleNodeTypesModel[componentType].model.nodes.length = 0;
}
for (var i=0;i<ruleNodeComponents.length;i++) {
var ruleNodeComponent = ruleNodeComponents[i];
componentType = ruleNodeComponent.type;
var model = vm.ruleNodeTypesModel[componentType].model;
var icon = vm.types.ruleNodeType[componentType].icon;
var iconUrl = null;
if (ruleNodeComponent.configurationDescriptor.nodeDefinition.icon) {
icon = ruleNodeComponent.configurationDescriptor.nodeDefinition.icon;
}
if (ruleNodeComponent.configurationDescriptor.nodeDefinition.iconUrl) {
iconUrl = ruleNodeComponent.configurationDescriptor.nodeDefinition.iconUrl;
}
var node = {
id: 'node-lib-' + componentType + '-' + model.nodes.length,
component: ruleNodeComponent,
name: '',
nodeClass: vm.types.ruleNodeType[componentType].nodeClass,
icon: icon,
iconUrl: iconUrl,
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
}
);
}
model.nodes.push(node);
}
vm.ruleChainLibraryLoaded = true;
if (loadRuleChain) {
prepareRuleChain();
}
$mdUtil.nextTick(() => {
for (componentType in vm.ruleNodeTypesCanvasControl) {
if (vm.ruleNodeTypesCanvasControl[componentType].adjustCanvasSize) {
vm.ruleNodeTypesCanvasControl[componentType].adjustCanvasSize(true);
}
}
for (componentType in vm.ruleNodeTypesModel) {
var panel = vm.$mdExpansionPanel(componentType);
if (panel) {
if (!vm.ruleNodeTypesModel[componentType].model.nodes.length) {
panel.collapse();
} else {
panel.expand();
}
}
}
});
}
function prepareRuleChain() {
if (vm.ruleChainWatch) {
vm.ruleChainWatch();
vm.ruleChainWatch = null;
}
vm.nextNodeID = 1;
vm.nextConnectorID = 1;
vm.selectedObjects.length = 0;
vm.ruleChainModel.nodes.length = 0;
vm.ruleChainModel.edges.length = 0;
vm.inputConnectorId = vm.nextConnectorID++;
vm.ruleChainModel.nodes.push(
{
id: 'rule-chain-node-' + vm.nextNodeID++,
component: types.inputNodeComponent,
name: "",
nodeClass: types.ruleNodeType.INPUT.nodeClass,
icon: types.ruleNodeType.INPUT.icon,
readonly: true,
x: 50,
y: 150,
connectors: [
{
type: flowchartConstants.rightConnectorType,
id: vm.inputConnectorId
},
]
}
);
ruleChainService.resolveTargetRuleChains(vm.ruleChainMetaData.ruleChainConnections)
.then((ruleChainsMap) => {
createRuleChainModel(ruleChainsMap);
}
);
}
function createRuleChainModel(ruleChainsMap) {
var nodes = [];
for (var i=0;i<vm.ruleChainMetaData.nodes.length;i++) {
var ruleNode = vm.ruleChainMetaData.nodes[i];
var component = ruleChainService.getRuleNodeComponentByClazz(ruleNode.type);
if (component) {
var icon = vm.types.ruleNodeType[component.type].icon;
var iconUrl = null;
if (component.configurationDescriptor.nodeDefinition.icon) {
icon = component.configurationDescriptor.nodeDefinition.icon;
}
if (component.configurationDescriptor.nodeDefinition.iconUrl) {
iconUrl = component.configurationDescriptor.nodeDefinition.iconUrl;
}
var node = {
id: 'rule-chain-node-' + vm.nextNodeID++,
ruleNodeId: ruleNode.id,
additionalInfo: ruleNode.additionalInfo,
configuration: ruleNode.configuration,
debugMode: ruleNode.debugMode,
x: ruleNode.additionalInfo.layoutX,
y: ruleNode.additionalInfo.layoutY,
component: component,
name: ruleNode.name,
nodeClass: vm.types.ruleNodeType[component.type].nodeClass,
icon: icon,
iconUrl: iconUrl,
connectors: []
};
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);
}
}
if (vm.ruleChainMetaData.firstNodeIndex > -1) {
var destNode = nodes[vm.ruleChainMetaData.firstNodeIndex];
if (destNode) {
var connectors = vm.modelservice.nodes.getConnectorsByType(destNode, flowchartConstants.leftConnectorType);
if (connectors && connectors.length) {
var edge = {
source: vm.inputConnectorId,
destination: connectors[0].id
};
vm.ruleChainModel.edges.push(edge);
}
}
}
if (vm.ruleChainMetaData.connections) {
for (i = 0; i < vm.ruleChainMetaData.connections.length; i++) {
var connection = vm.ruleChainMetaData.connections[i];
var sourceNode = nodes[connection.fromIndex];
destNode = nodes[connection.toIndex];
if (sourceNode && destNode) {
var sourceConnectors = vm.modelservice.nodes.getConnectorsByType(sourceNode, flowchartConstants.rightConnectorType);
var destConnectors = vm.modelservice.nodes.getConnectorsByType(destNode, flowchartConstants.leftConnectorType);
if (sourceConnectors && sourceConnectors.length && destConnectors && destConnectors.length) {
edge = {
source: sourceConnectors[0].id,
destination: destConnectors[0].id,
label: connection.type
};
vm.ruleChainModel.edges.push(edge);
}
}
}
}
if (vm.ruleChainMetaData.ruleChainConnections) {
var ruleChainNodesMap = {};
for (i = 0; i < vm.ruleChainMetaData.ruleChainConnections.length; i++) {
var ruleChainConnection = vm.ruleChainMetaData.ruleChainConnections[i];
var ruleChain = ruleChainsMap[ruleChainConnection.targetRuleChainId.id];
if (ruleChainConnection.additionalInfo && ruleChainConnection.additionalInfo.ruleChainNodeId) {
var ruleChainNode = ruleChainNodesMap[ruleChainConnection.additionalInfo.ruleChainNodeId];
if (!ruleChainNode) {
ruleChainNode = {
id: 'rule-chain-node-' + vm.nextNodeID++,
additionalInfo: ruleChainConnection.additionalInfo,
x: ruleChainConnection.additionalInfo.layoutX,
y: ruleChainConnection.additionalInfo.layoutY,
component: types.ruleChainNodeComponent,
nodeClass: vm.types.ruleNodeType.RULE_CHAIN.nodeClass,
icon: vm.types.ruleNodeType.RULE_CHAIN.icon,
connectors: [
{
type: flowchartConstants.leftConnectorType,
id: vm.nextConnectorID++
}
]
};
if (ruleChain.name) {
ruleChainNode.name = ruleChain.name;
ruleChainNode.targetRuleChainId = ruleChainConnection.targetRuleChainId.id;
} else {
ruleChainNode.name = "Unresolved";
ruleChainNode.targetRuleChainId = null;
ruleChainNode.error = $translate.instant('rulenode.invalid-target-rulechain');
}
ruleChainNodesMap[ruleChainConnection.additionalInfo.ruleChainNodeId] = ruleChainNode;
vm.ruleChainModel.nodes.push(ruleChainNode);
}
sourceNode = nodes[ruleChainConnection.fromIndex];
if (sourceNode) {
connectors = vm.modelservice.nodes.getConnectorsByType(sourceNode, flowchartConstants.rightConnectorType);
if (connectors && connectors.length) {
var ruleChainEdge = {
source: connectors[0].id,
destination: ruleChainNode.connectors[0].id,
label: ruleChainConnection.type
};
vm.ruleChainModel.edges.push(ruleChainEdge);
}
}
}
}
}
if (vm.canvasControl.adjustCanvasSize) {
vm.canvasControl.adjustCanvasSize(true);
}
vm.isDirty = false;
updateRuleNodesHighlight();
validate();
$mdUtil.nextTick(() => {
vm.ruleChainWatch = $scope.$watch('vm.ruleChainModel',
function (newVal, oldVal) {
if (!angular.equals(newVal, oldVal)) {
validate();
if (!vm.isDirty) {
vm.isDirty = true;
}
}
}, true
);
});
}
function updateRuleNodesHighlight() {
for (var i = 0; i < vm.ruleChainModel.nodes.length; i++) {
vm.ruleChainModel.nodes[i].highlighted = false;
}
if ($scope.searchConfig.searchText) {
var res = $filter('filter')(vm.ruleChainModel.nodes, {name: $scope.searchConfig.searchText});
if (res) {
for (i = 0; i < res.length; i++) {
res[i].highlighted = true;
}
}
}
}
function validate() {
$mdUtil.nextTick(() => {
vm.isInvalid = false;
for (var i = 0; i < vm.ruleChainModel.nodes.length; i++) {
if (vm.ruleChainModel.nodes[i].error) {
vm.isInvalid = true;
}
updateNodeErrorTooltip(vm.ruleChainModel.nodes[i]);
}
});
}
function saveRuleChain() {
var saveRuleChainPromise;
if (vm.isImport) {
saveRuleChainPromise = ruleChainService.saveRuleChain(vm.ruleChain);
} else {
saveRuleChainPromise = $q.when(vm.ruleChain);
}
saveRuleChainPromise.then(
(ruleChain) => {
vm.ruleChain = ruleChain;
var ruleChainMetaData = {
ruleChainId: vm.ruleChain.id,
nodes: [],
connections: [],
ruleChainConnections: []
};
var nodes = [];
for (var i=0;i<vm.ruleChainModel.nodes.length;i++) {
var node = vm.ruleChainModel.nodes[i];
if (node.component.type != types.ruleNodeType.INPUT.value && node.component.type != types.ruleNodeType.RULE_CHAIN.value) {
var ruleNode = {};
if (node.ruleNodeId) {
ruleNode.id = node.ruleNodeId;
}
ruleNode.type = node.component.clazz;
ruleNode.name = node.name;
ruleNode.configuration = node.configuration;
ruleNode.additionalInfo = node.additionalInfo;
ruleNode.debugMode = node.debugMode;
if (!ruleNode.additionalInfo) {
ruleNode.additionalInfo = {};
}
ruleNode.additionalInfo.layoutX = node.x;
ruleNode.additionalInfo.layoutY = node.y;
ruleChainMetaData.nodes.push(ruleNode);
nodes.push(node);
}
}
var res = $filter('filter')(vm.ruleChainModel.edges, {source: vm.inputConnectorId}, true);
if (res && res.length) {
var firstNodeEdge = res[0];
var firstNode = vm.modelservice.nodes.getNodeByConnectorId(firstNodeEdge.destination);
ruleChainMetaData.firstNodeIndex = nodes.indexOf(firstNode);
}
for (i=0;i<vm.ruleChainModel.edges.length;i++) {
var edge = vm.ruleChainModel.edges[i];
var sourceNode = vm.modelservice.nodes.getNodeByConnectorId(edge.source);
var destNode = vm.modelservice.nodes.getNodeByConnectorId(edge.destination);
if (sourceNode.component.type != types.ruleNodeType.INPUT.value) {
var fromIndex = nodes.indexOf(sourceNode);
if (destNode.component.type == types.ruleNodeType.RULE_CHAIN.value) {
var ruleChainConnection = {
fromIndex: fromIndex,
targetRuleChainId: {entityType: vm.types.entityType.rulechain, id: destNode.targetRuleChainId},
additionalInfo: destNode.additionalInfo,
type: edge.label
};
if (!ruleChainConnection.additionalInfo) {
ruleChainConnection.additionalInfo = {};
}
ruleChainConnection.additionalInfo.layoutX = destNode.x;
ruleChainConnection.additionalInfo.layoutY = destNode.y;
ruleChainConnection.additionalInfo.ruleChainNodeId = destNode.id;
ruleChainMetaData.ruleChainConnections.push(ruleChainConnection);
} else {
var toIndex = nodes.indexOf(destNode);
var nodeConnection = {
fromIndex: fromIndex,
toIndex: toIndex,
type: edge.label
};
ruleChainMetaData.connections.push(nodeConnection);
}
}
}
ruleChainService.saveRuleChainMetaData(ruleChainMetaData).then(
(ruleChainMetaData) => {
vm.ruleChainMetaData = ruleChainMetaData;
if (vm.isImport) {
vm.isDirty = false;
vm.isImport = false;
$mdUtil.nextTick(() => {
$state.go('home.ruleChains.ruleChain', {ruleChainId: vm.ruleChain.id.id});
});
} else {
prepareRuleChain();
}
}
);
}
);
}
function revertRuleChain() {
prepareRuleChain();
}
function addRuleNode($event, ruleNode) {
ruleNode.configuration = angular.copy(ruleNode.component.configurationDescriptor.nodeDefinition.defaultConfiguration);
var ruleChainId = vm.ruleChain.id ? vm.ruleChain.id.id : null;
vm.enableHotKeys = false;
$mdDialog.show({
controller: 'AddRuleNodeController',
controllerAs: 'vm',
templateUrl: addRuleNodeTemplate,
parent: angular.element($document[0].body),
locals: {ruleNode: ruleNode, ruleChainId: ruleChainId},
fullscreen: true,
targetEvent: $event
}).then(function (ruleNode) {
ruleNode.id = 'rule-chain-node-' + vm.nextNodeID++;
ruleNode.connectors = [];
if (ruleNode.component.configurationDescriptor.nodeDefinition.inEnabled) {
ruleNode.connectors.push(
{
id: vm.nextConnectorID++,
type: flowchartConstants.leftConnectorType
}
);
}
if (ruleNode.component.configurationDescriptor.nodeDefinition.outEnabled) {
ruleNode.connectors.push(
{
id: vm.nextConnectorID++,
type: flowchartConstants.rightConnectorType
}
);
}
vm.ruleChainModel.nodes.push(ruleNode);
updateRuleNodesHighlight();
vm.enableHotKeys = true;
}, function () {
vm.enableHotKeys = true;
});
}
function addRuleNodeLink($event, link, labels) {
return $mdDialog.show({
controller: 'AddRuleNodeLinkController',
controllerAs: 'vm',
templateUrl: addRuleNodeLinkTemplate,
parent: angular.element($document[0].body),
locals: {link: link, labels: labels},
fullscreen: true,
targetEvent: $event
});
}
function objectsSelected() {
return vm.modelservice.nodes.getSelectedNodes().length > 0 ||
vm.modelservice.edges.getSelectedEdges().length > 0
}
function deleteSelected() {
vm.modelservice.deleteSelected();
}
function triggerResize() {
var w = angular.element($window);
w.triggerHandler('resize');
}
}
/*@ngInject*/
export function AddRuleNodeController($scope, $mdDialog, ruleNode, ruleChainId, helpLinks) {
var vm = this;
vm.helpLinks = helpLinks;
vm.ruleNode = ruleNode;
vm.ruleChainId = ruleChainId;
vm.add = add;
vm.cancel = cancel;
function cancel() {
$mdDialog.cancel();
}
function add() {
$scope.theForm.$setPristine();
$mdDialog.hide(vm.ruleNode);
}
}
/*@ngInject*/
export function AddRuleNodeLinkController($scope, $mdDialog, link, labels, helpLinks) {
var vm = this;
vm.helpLinks = helpLinks;
vm.link = link;
vm.labels = labels;
vm.add = add;
vm.cancel = cancel;
function cancel() {
$mdDialog.cancel();
}
function add() {
$scope.theForm.$setPristine();
$mdDialog.hide(vm.link);
}
}