import { Component, OnInit, AfterViewInit, Input, Output, EventEmitter, OnChanges, SimpleChanges, OnDestroy } from '@angular/core';
import { interval } from 'rxjs/observable/interval';
import { IChartModel } from './model/flow-data';
import { HelperService } from '../../core/helper/helper.service';
import { NgFlowchartService } from './ng-flowchart.service';
declare let $;
@Component({
selector: 'app-ng-flowchart',
templateUrl: './ng-flowchart.component.html',
styleUrls: ['./ng-flowchart.component.css'],
})
export class NgFlowchartComponent implements OnInit, AfterViewInit, OnChanges, OnDestroy {
@Input('data') flowchartData: IChartModel;
@Input() event: any;
currentElement = 'dp_flowchart';
// operatorId = 2;
operatorId: string;
// flowchartData;
possibleZooms = [0.5, 0.75, 1, 2, 3];
currentZoom = 2;
subscribeInterval: any;
constructor(
private helperService: HelperService,
private ngFlowchartService: NgFlowchartService
) { }
private autoSaveWorkflow() {
const $flowchart = $('#' + this.currentElement);
const source = interval(1000);
this.subscribeInterval = source.subscribe(val => {
const flowchartData = $flowchart.flowchart('getData');
const currentData = JSON.stringify(flowchartData);
const previousData = localStorage.getItem('ui.flowchart');
if (previousData !== currentData) {
localStorage.setItem('ui.flowchart', currentData);
}
});
}
ngOnInit() {
this.ngFlowchartService.currentMessage.subscribe(message => {
if (message.action && message.action === 'ng-clear-editor') {
setTimeout(() => {
this.clearEditor();
}, 100);
}
});
}
setFlowchartData(data) {
const $flowchart = $('#' + this.currentElement);
$flowchart.flowchart('setData', data);
}
ngOnChanges(changes: SimpleChanges) {
// console.log(changes);
this.dataChanges(changes);
}
private initFlowchartData() {
if (localStorage.getItem('ui.flowchart')) {
return JSON.parse(localStorage.getItem('ui.flowchart'));
} else {
return this.flowchartData;
}
}
ngAfterViewInit() {
setTimeout(() => {
this.advanceOption();
this.autoSaveWorkflow();
}, 100);
}
private dataChanges(changes) {
if (changes.hasOwnProperty('event')) {
if (changes.event.currentValue) {
if (changes.event.previousValue) {
/** Have current and previous state */
const data = {
curIn: +(changes.event.currentValue.inputCount),
preIn: +(changes.event.previousValue.inputCount),
curOut: +(changes.event.currentValue.outputCount),
preOut: +(changes.event.previousValue.outputCount)
};
if (data.curIn !== data.preIn || data.curOut !== data.preOut) {
this.addEndPoint(changes.event.currentValue);
}
} else {
/** Have only current state */
const data = {
curIn: +(changes.event.currentValue.inputCount),
curOut: +(changes.event.currentValue.outputCount)
};
if (data.curIn !== 1 || data.curOut !== 1) {
this.addEndPoint(changes.event.currentValue);
}
}
}
}
}
private addEndPoint(endpoint) {
// console.log(endpoint);
/**
* 1. Find the component in the chart data (using unique identifier - type as 'Event')
* 2. If already +++++input & output endpoints there then create remaining only
* 3. If input or output count less the previous one and have a connection (link)
* associated with the endpoint then popup comfirmation dialog for removing the
* desired in/out.
*/
const $flowchart = $('#' + this.currentElement);
const flowchartData = $flowchart.flowchart('getData');
Object.keys(flowchartData['operators']).forEach((key) => {
if (endpoint.eventName === flowchartData.operators[key].type.eventName) {
let inLen = Object.keys(flowchartData.operators[key].properties.inputs).length;
let outLen = Object.keys(flowchartData.operators[key].properties.outputs).length;
const _inLen = +endpoint.inputCount - inLen;
const _outLen = +endpoint.outputCount - outLen;
let loopFlag = false;
if (_inLen !== 0) {
loopFlag = true;
if (_inLen < 0) {
inLen = inLen + _inLen;
for (let i = inLen; i > Math.abs(inLen + _inLen); i--) {
const _key = Object.keys(flowchartData.operators[key].properties.inputs)[i];
delete flowchartData.operators[key].properties.inputs[_key];
}
} else {
for (let i = (inLen - 1); i < (inLen + _inLen); i++) {
flowchartData.operators[key].properties.inputs['input_' + i] = {
label: 'IN_' + (i + 1)
};
}
}
}
if (_outLen !== 0) {
loopFlag = true;
if (_outLen < 0) {
outLen = outLen + _outLen;
for (let i = outLen; i > Math.abs(outLen + _outLen); i--) {
const _key = Object.keys(flowchartData.operators[key].properties.outputs)[i];
delete flowchartData.operators[key].properties.outputs[_key];
}
} else {
for (let i = (outLen - 1); i < (outLen + _outLen); i++) {
flowchartData.operators[key].properties.outputs['output_' + i] = {
label: 'OUT_' + (i + 1)
};
}
}
}
if (loopFlag) {
$flowchart.flowchart('setData', flowchartData);
}
}
});
}
private advanceOption() {
const self = this;
const $flowchart = $('#' + this.currentElement);
const $container = $flowchart.parent();
const cx = $flowchart.width() / 2;
const cy = $flowchart.height() / 2;
// Panzoom initialization...
$flowchart.panzoom();
// Centering panzoom
$flowchart.panzoom('pan', -cx + $container.width() / 2, -cy + $container.height() / 2);
// Panzoom zoom handling...
const possibleZooms = [0.5, 0.75, 1, 2, 3];
let currentZoom = 2;
$container.on('mousewheel.focal', function (e) {
e.preventDefault();
const delta = (e.delta || e.originalEvent.wheelDelta) || e.originalEvent.detail;
const zoomOut: any = delta ? delta < 0 : e.originalEvent.deltaY > 0;
currentZoom = Math.max(0, Math.min(possibleZooms.length - 1, (currentZoom + (zoomOut * 2 - 1))));
$flowchart.flowchart('setPositionRatio', possibleZooms[currentZoom]);
$flowchart.panzoom('zoom', possibleZooms[currentZoom], {
animate: false,
focal: e
});
});
// Apply the plugin on a standard, empty div...
$flowchart.flowchart({
data: this.initFlowchartData(),
onOperatorCreate: function (operatorId, operatorData, fullElement) {
// console.log(fullElement);
// console.log('New operator created. Operator ID: "' + operatorId + '", operator title: "' + operatorData.properties.title + '".');
return true;
},
onOperatorSelect: function (operatorId, data) {
self.componentSelect.emit({
id: operatorId,
title: $flowchart.flowchart('getOperatorTitle', operatorId),
data: $flowchart.flowchart('getOperatorData', operatorId)
});
return true;
},
// Operators Unselected
onOperatorUnselect: function () {
self.componentDeSelected.emit({
action: 'operator-deselected'
});
return true;
},
onOperatorDelete: function (operatorId) {
const operatorType = $flowchart.flowchart('getOperatorData', operatorId).type;
if (operatorType.eventName) {
/** Once remove event from editor need to enable it again in event panel */
self.ngFlowchartService.send({
action: 'ng-event-deleted',
data: {
eventName: operatorType.eventName,
operatorId: operatorId
}
});
}
return true;
},
onLinkDelete: function (linkId, forced) {
// delete self.flowchartData['links'][linkId];
// console.log('Link deleted. Link ID: "' + linkId + '", link color: "' + $flowchart.flowchart('getLinkMainColor', linkId) + '".');
return true;
},
onLinkCreate: function (linkId, linkData) {
if (self.rule && self.rule.isEnabled) {
const fromoprtype = $flowchart.flowchart('getOperatorData', linkData.fromOperator).type;
const tooprtype = $flowchart.flowchart('getOperatorData', linkData.toOperator).type;
const rule = self.rule.rules['rule'];
const firstRule = fromoprtype['type'] || 'Component';
const secondRule = tooprtype['type'] || 'Component';
return rule[firstRule + ':' + secondRule];
}
self.flowchartData['links'][linkId] = linkData;
delete self.flowchartData['links'][linkId]['internal'];
// console.log('New link created. Link ID: "' + linkId + '", link color: "' + linkData.color + '".');
return true;
},
onLinkSelect: function (linkId) {
const flowchartObject = $flowchart.flowchart('getData');
const linkObject = flowchartObject.links[linkId];
const fromOperator = flowchartObject.operators[linkObject.fromOperator];
const toOperator = flowchartObject.operators[linkObject.toOperator];
const fromType = fromOperator.type.hasOwnProperty('eventName') ? 'Event' : 'Component';
const toType = toOperator.type.hasOwnProperty('eventName') ? 'Event' : 'Component';
const title = `Link: ${fromOperator.properties.title} - ${toOperator.properties.title}`;
self.componentSelect.emit({
id: linkId,
title: title,
data: {
type: {
isLink: true,
data: linkObject,
linkData: {
fromOperator: fromOperator.properties.title,
fromType: fromType,
toOperator: toOperator.properties.title,
toType: toType
}
}
}
});
return true;
}
});
const $draggableOperators = $('.draggable_operator');
$draggableOperators.draggable({
cursor: 'move',
opacity: 0.7,
// helper: 'clone',
appendTo: 'body',
zIndex: 1000,
});
}
private addNewComponent(event: any) {
// console.log(event);
const $flowchart = $('#' + this.currentElement);
let componentTitle;
componentTitle = (event.dragData.eventName) ? `${event.dragData.displayName}` : `${event.dragData.name}`;
this.operatorId = this.helperService.getUniqueId();
if (event.dragData.eventName) {
/** Component is an 'event' type */
const flowchartObject = $flowchart.flowchart('getData');
const operators = flowchartObject.operators;
for (let i = 0; i < Object.keys(operators).length; i++) {
const component = operators[Object.keys(operators)[i]];
if (event.dragData.eventName === component.type.eventName) {
return;
}
}
/** 1. Send operator details back to event panel for disabling the dropped event
* 2. Once remove event from editor need to enable it again in event panel
* 2. After rerender pipeline we need to set it again (pending)
*/
this.ngFlowchartService.send({
action: 'ng-event-added',
data: {
eventName: event.dragData.eventName,
operatorId: this.operatorId
}
});
}
const inputObject = {};
const outputObject = {};
if (event.dragData.hasOwnProperty('componentUIMetaData') && event.dragData.componentUIMetaData.hasOwnProperty('connector')) {
const iCount = event.dragData.componentUIMetaData.connector.endpoint.in || 0;
const oCount = event.dragData.componentUIMetaData.connector.endpoint.out || 0;
for (let i = 0; i < iCount; i++) {
inputObject['input_' + i] = {
label: 'IN_' + (i + 1)
};
}
for (let i = 0; i < oCount; i++) {
outputObject['output_' + i] = {
label: 'OUT_' + (i + 1)
};
}
}
const operatorData = {
top: event.nativeEvent.offsetY - 20,
left: event.nativeEvent.offsetX - 20,
type: event.dragData,
properties: {
title: componentTitle,
inputs: inputObject,
outputs: outputObject,
}
};
// this.operatorId++;
$flowchart.flowchart('createOperator', this.operatorId, operatorData);
if (event.dragData.hasOwnProperty('type') && (event.dragData.type === 'Event')) {
const cssClass = 'flowchart-operator-title ui-draggable-handle titleClass';
event.nativeEvent.currentTarget.childNodes[1].lastChild.children[0].classList.value = cssClass;
}
}
onComponentDrop(event: any) {
// console.log(event);
this.addNewComponent(event);
}
deleteSelected() {
const $flowchart = $('#' + this.currentElement);
$flowchart.flowchart('deleteSelected');
}
getFlowchartData() {
const $flowchart = $('#' + this.currentElement);
return $flowchart.flowchart('getData');
}
ngOnDestroy() {
if (this.subscribeInterval) {
this.subscribeInterval.unsubscribe();
}
}
}
<div class="flowchart-example">
<div id="chart_container">
<div class="zoom-panel-container">
</div>
<div class="flowchart-example-container" id="dp_flowchart" droppable [dropScope]="'component'" (onDrop)="onComponentDrop($event)"></div>
</div>
</div>