|
|
|
@ -14,14 +14,14 @@ |
|
|
|
/// limitations under the License.
|
|
|
|
///
|
|
|
|
|
|
|
|
import { Component, OnInit } from '@angular/core'; |
|
|
|
import { AfterViewInit, Component, ElementRef, OnInit, QueryList, ViewChild, ViewChildren } from '@angular/core'; |
|
|
|
import { PageComponent } from '@shared/components/page.component'; |
|
|
|
import { Store } from '@ngrx/store'; |
|
|
|
import { AppState } from '@core/core.state'; |
|
|
|
import { FormBuilder } from '@angular/forms'; |
|
|
|
import { HasDirtyFlag } from '@core/guards/confirm-on-exit.guard'; |
|
|
|
import { TranslateService } from '@ngx-translate/core'; |
|
|
|
import { MatDialog } from '@angular/material'; |
|
|
|
import { MatDialog, MatExpansionPanel } from '@angular/material'; |
|
|
|
import { DialogService } from '@core/services/dialog.service'; |
|
|
|
import { AuthService } from '@core/auth/auth.service'; |
|
|
|
import { ActivatedRoute, Router } from '@angular/router'; |
|
|
|
@ -31,26 +31,46 @@ import { |
|
|
|
RuleChain, |
|
|
|
ruleChainNodeComponent |
|
|
|
} from '@shared/models/rule-chain.models'; |
|
|
|
import { FlowchartConstants } from 'ngx-flowchart/dist/ngx-flowchart'; |
|
|
|
import { RuleNodeComponentDescriptor, RuleNodeType, ruleNodeTypeDescriptors } from '@shared/models/rule-node.models'; |
|
|
|
import { FlowchartConstants, UserCallbacks, NgxFlowchartComponent } from 'ngx-flowchart/dist/ngx-flowchart'; |
|
|
|
import { |
|
|
|
RuleNodeComponentDescriptor, |
|
|
|
RuleNodeType, |
|
|
|
ruleNodeTypeDescriptors, |
|
|
|
ruleNodeTypesLibrary |
|
|
|
} from '@shared/models/rule-node.models'; |
|
|
|
import { FcRuleEdge, FcRuleNode, FcRuleNodeModel, FcRuleNodeType, FcRuleNodeTypeModel } from './rulechain-page.models'; |
|
|
|
import { RuleChainService } from '@core/http/rule-chain.service'; |
|
|
|
import { fromEvent, of } from 'rxjs'; |
|
|
|
import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators'; |
|
|
|
import Timeout = NodeJS.Timeout; |
|
|
|
import { ISearchableComponent } from '../../models/searchable-component.models'; |
|
|
|
|
|
|
|
@Component({ |
|
|
|
selector: 'tb-rulechain-page', |
|
|
|
templateUrl: './rulechain-page.component.html', |
|
|
|
styleUrls: ['./rulechain-page.component.scss'] |
|
|
|
}) |
|
|
|
export class RuleChainPageComponent extends PageComponent implements OnInit, HasDirtyFlag { |
|
|
|
export class RuleChainPageComponent extends PageComponent |
|
|
|
implements AfterViewInit, OnInit, HasDirtyFlag, ISearchableComponent { |
|
|
|
|
|
|
|
get isDirty(): boolean { |
|
|
|
return this.isDirtyValue || this.isImport; |
|
|
|
} |
|
|
|
|
|
|
|
@ViewChild('ruleNodeSearchInput', {static: false}) ruleNodeSearchInputField: ElementRef; |
|
|
|
|
|
|
|
@ViewChild('ruleChainCanvas', {static: true}) ruleChainCanvas: NgxFlowchartComponent; |
|
|
|
|
|
|
|
@ViewChildren('ruleNodeTypeExpansionPanels', |
|
|
|
{read: MatExpansionPanel}) expansionPanels: QueryList<MatExpansionPanel>; |
|
|
|
|
|
|
|
ruleNodeTypeDescriptorsMap = ruleNodeTypeDescriptors; |
|
|
|
ruleNodeTypesLibraryArray = ruleNodeTypesLibrary; |
|
|
|
|
|
|
|
isImport: boolean; |
|
|
|
isDirtyValue: boolean; |
|
|
|
|
|
|
|
errorTooltips = {}; |
|
|
|
errorTooltips: {[nodeId: string]: JQueryTooltipster.ITooltipsterInstance} = {}; |
|
|
|
isFullscreen = false; |
|
|
|
|
|
|
|
editingRuleNode = null; |
|
|
|
@ -62,6 +82,7 @@ export class RuleChainPageComponent extends PageComponent implements OnInit, Has |
|
|
|
isLibraryOpen = true; |
|
|
|
|
|
|
|
ruleNodeSearch = ''; |
|
|
|
ruleNodeTypeSearch = ''; |
|
|
|
|
|
|
|
ruleChain: RuleChain; |
|
|
|
ruleChainMetaData: ResolvedRuleChainMetaData; |
|
|
|
@ -71,16 +92,58 @@ export class RuleChainPageComponent extends PageComponent implements OnInit, Has |
|
|
|
edges: [] |
|
|
|
}; |
|
|
|
selectedObjects = []; |
|
|
|
|
|
|
|
editCallbacks: UserCallbacks = { |
|
|
|
edgeDoubleClick: (event, edge) => { |
|
|
|
console.log('TODO'); |
|
|
|
}, |
|
|
|
edgeEdit: (event, edge) => { |
|
|
|
console.log('TODO'); |
|
|
|
}, |
|
|
|
nodeCallbacks: { |
|
|
|
doubleClick: (event, node) => { |
|
|
|
console.log('TODO'); |
|
|
|
}, |
|
|
|
nodeEdit: (event, node) => { |
|
|
|
console.log('TODO'); |
|
|
|
}, |
|
|
|
mouseEnter: this.displayNodeDescriptionTooltip.bind(this), |
|
|
|
mouseLeave: this.destroyTooltips.bind(this), |
|
|
|
mouseDown: this.destroyTooltips.bind(this) |
|
|
|
}, |
|
|
|
isValidEdge: (source, destination) => { |
|
|
|
return source.type === FlowchartConstants.rightConnectorType && destination.type === FlowchartConstants.leftConnectorType; |
|
|
|
}, |
|
|
|
createEdge: (event, edge) => { |
|
|
|
console.log('TODO'); |
|
|
|
return of(edge); |
|
|
|
}, |
|
|
|
dropNode: (event, node) => { |
|
|
|
console.log('TODO dropNode'); |
|
|
|
console.log(node); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
nextNodeID: number; |
|
|
|
nextConnectorID: number; |
|
|
|
inputConnectorId: number; |
|
|
|
|
|
|
|
ruleNodeTypesModel: {[type: string]: {model: FcRuleNodeTypeModel, selectedObjects: any[]}} = {}; |
|
|
|
|
|
|
|
nodeLibCallbacks: UserCallbacks = { |
|
|
|
nodeCallbacks: { |
|
|
|
mouseEnter: this.displayLibNodeDescriptionTooltip.bind(this), |
|
|
|
mouseLeave: this.destroyTooltips.bind(this), |
|
|
|
mouseDown: this.destroyTooltips.bind(this) |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
ruleNodeComponents: Array<RuleNodeComponentDescriptor>; |
|
|
|
|
|
|
|
flowchartConstants = FlowchartConstants; |
|
|
|
|
|
|
|
private tooltipTimeout: Timeout; |
|
|
|
|
|
|
|
constructor(protected store: Store<AppState>, |
|
|
|
private route: ActivatedRoute, |
|
|
|
private router: Router, |
|
|
|
@ -97,6 +160,23 @@ export class RuleChainPageComponent extends PageComponent implements OnInit, Has |
|
|
|
ngOnInit() { |
|
|
|
} |
|
|
|
|
|
|
|
ngAfterViewInit() { |
|
|
|
fromEvent(this.ruleNodeSearchInputField.nativeElement, 'keyup') |
|
|
|
.pipe( |
|
|
|
debounceTime(150), |
|
|
|
distinctUntilChanged(), |
|
|
|
tap(() => { |
|
|
|
this.updateRuleChainLibrary(); |
|
|
|
}) |
|
|
|
) |
|
|
|
.subscribe(); |
|
|
|
} |
|
|
|
|
|
|
|
onSearchTextUpdated(searchText: string) { |
|
|
|
this.ruleNodeSearch = searchText; |
|
|
|
this.updateRuleNodesHighlight(); |
|
|
|
} |
|
|
|
|
|
|
|
private init() { |
|
|
|
this.ruleChain = this.route.snapshot.data.ruleChain; |
|
|
|
if (this.route.snapshot.data.import && !this.ruleChain) { |
|
|
|
@ -106,8 +186,8 @@ export class RuleChainPageComponent extends PageComponent implements OnInit, Has |
|
|
|
this.isImport = this.route.snapshot.data.import; |
|
|
|
this.ruleChainMetaData = this.route.snapshot.data.ruleChainMetaData; |
|
|
|
this.ruleNodeComponents = this.route.snapshot.data.ruleNodeComponents; |
|
|
|
for (const type of Object.keys(RuleNodeType)) { |
|
|
|
const desc = ruleNodeTypeDescriptors.get(RuleNodeType[type]); |
|
|
|
for (const type of ruleNodeTypesLibrary) { |
|
|
|
const desc = ruleNodeTypeDescriptors.get(type); |
|
|
|
if (!desc.special) { |
|
|
|
this.ruleNodeTypesModel[type] = { |
|
|
|
model: { |
|
|
|
@ -118,10 +198,17 @@ export class RuleChainPageComponent extends PageComponent implements OnInit, Has |
|
|
|
}; |
|
|
|
} |
|
|
|
} |
|
|
|
this.loadRuleChainLibrary(this.ruleNodeComponents); |
|
|
|
this.updateRuleChainLibrary(); |
|
|
|
this.createRuleChainModel(); |
|
|
|
} |
|
|
|
|
|
|
|
private updateRuleChainLibrary() { |
|
|
|
const search = this.ruleNodeTypeSearch.toUpperCase(); |
|
|
|
const res = this.ruleNodeComponents.filter( |
|
|
|
(ruleNodeComponent) => ruleNodeComponent.name.toUpperCase().includes(search)); |
|
|
|
this.loadRuleChainLibrary(res); |
|
|
|
} |
|
|
|
|
|
|
|
private loadRuleChainLibrary(ruleNodeComponents: Array<RuleNodeComponentDescriptor>) { |
|
|
|
for (const componentType of Object.keys(this.ruleNodeTypesModel)) { |
|
|
|
this.ruleNodeTypesModel[componentType].model.nodes.length = 0; |
|
|
|
@ -167,6 +254,21 @@ export class RuleChainPageComponent extends PageComponent implements OnInit, Has |
|
|
|
} |
|
|
|
model.nodes.push(node); |
|
|
|
}); |
|
|
|
if (this.expansionPanels) { |
|
|
|
for (let i = 0; i < ruleNodeTypesLibrary.length; i++) { |
|
|
|
const panel = this.expansionPanels.find((item, index) => { |
|
|
|
return index === i; |
|
|
|
}); |
|
|
|
if (panel) { |
|
|
|
const type = ruleNodeTypesLibrary[i]; |
|
|
|
if (!this.ruleNodeTypesModel[type].model.nodes.length) { |
|
|
|
panel.close(); |
|
|
|
} else { |
|
|
|
panel.open(); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private createRuleChainModel() { |
|
|
|
@ -350,5 +452,115 @@ export class RuleChainPageComponent extends PageComponent implements OnInit, Has |
|
|
|
this.isDirtyValue = true; |
|
|
|
} |
|
|
|
|
|
|
|
typeHeaderMouseEnter(event: MouseEvent, ruleNodeType: RuleNodeType) { |
|
|
|
const type = ruleNodeTypeDescriptors.get(ruleNodeType); |
|
|
|
this.displayTooltip(event, |
|
|
|
'<div class="tb-rule-node-tooltip tb-lib-tooltip">' + |
|
|
|
'<div id="tb-node-content" layout="column">' + |
|
|
|
'<div class="tb-node-title">' + this.translate.instant(type.name) + '</div>' + |
|
|
|
'<div class="tb-node-details">' + this.translate.instant(type.details) + '</div>' + |
|
|
|
'</div>' + |
|
|
|
'</div>' |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
displayLibNodeDescriptionTooltip(event: MouseEvent, node: FcRuleNodeType) { |
|
|
|
this.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>' |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
displayNodeDescriptionTooltip(event: MouseEvent, node: FcRuleNode) { |
|
|
|
if (!this.errorTooltips[node.id]) { |
|
|
|
let name: string; |
|
|
|
let desc: string; |
|
|
|
let details: string; |
|
|
|
if (node.component.type === RuleNodeType.INPUT) { |
|
|
|
name = this.translate.instant(ruleNodeTypeDescriptors.get(RuleNodeType.INPUT).name); |
|
|
|
desc = this.translate.instant(ruleNodeTypeDescriptors.get(RuleNodeType.INPUT).details); |
|
|
|
} else { |
|
|
|
name = node.name; |
|
|
|
desc = this.translate.instant(ruleNodeTypeDescriptors.get(node.component.type).name) + ' - ' + node.component.name; |
|
|
|
if (node.additionalInfo) { |
|
|
|
details = node.additionalInfo.description; |
|
|
|
} |
|
|
|
} |
|
|
|
let 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>'; |
|
|
|
this.displayTooltip(event, tooltipContent); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
destroyTooltips() { |
|
|
|
if (this.tooltipTimeout) { |
|
|
|
clearTimeout(this.tooltipTimeout); |
|
|
|
this.tooltipTimeout = null; |
|
|
|
} |
|
|
|
const instances = $.tooltipster.instances(); |
|
|
|
instances.forEach((instance) => { |
|
|
|
if (!instance.isErrorTooltip) { |
|
|
|
instance.destroy(); |
|
|
|
} |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
updateRuleNodesHighlight() { |
|
|
|
for (const ruleNode of this.ruleChainModel.nodes) { |
|
|
|
ruleNode.highlighted = false; |
|
|
|
} |
|
|
|
if (this.ruleNodeSearch) { |
|
|
|
const search = this.ruleNodeSearch.toUpperCase(); |
|
|
|
const res = this.ruleChainModel.nodes.filter(node => node.name.toUpperCase().includes(search)); |
|
|
|
if (res) { |
|
|
|
for (const ruleNode of res) { |
|
|
|
ruleNode.highlighted = true; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
this.ruleChainCanvas.modelService.detectChanges(); |
|
|
|
} |
|
|
|
|
|
|
|
private displayTooltip(event: MouseEvent, content: string) { |
|
|
|
this.destroyTooltips(); |
|
|
|
this.tooltipTimeout = setTimeout(() => { |
|
|
|
const 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 |
|
|
|
} |
|
|
|
); |
|
|
|
const contentElement = $(content); |
|
|
|
const tooltip = element.tooltipster('instance'); |
|
|
|
tooltip.content(contentElement); |
|
|
|
tooltip.open(); |
|
|
|
}, 500); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|