You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

195 lines
5.6 KiB
TypeScript

import { Observable } from 'rxjs';
import { InjectionToken, Type } from '@angular/core';
import { FcNodeComponent } from './node.component';
export const FC_NODE_COMPONENT_CONFIG = new InjectionToken<FcNodeComponentConfig>('fc-node.component.config');
export interface FcNodeComponentConfig {
nodeComponentType: Type<FcNodeComponent>;
}
const htmlPrefix = 'fc';
const leftConnectorType = 'leftConnector';
const rightConnectorType = 'rightConnector';
export const FlowchartConstants = {
htmlPrefix,
leftConnectorType,
rightConnectorType,
curvedStyle: 'curved',
lineStyle: 'line',
dragAnimationRepaint: 'repaint',
dragAnimationShadow: 'shadow',
canvasClass: htmlPrefix + '-canvas',
selectedClass: htmlPrefix + '-selected',
editClass: htmlPrefix + '-edit',
activeClass: htmlPrefix + '-active',
hoverClass: htmlPrefix + '-hover',
draggingClass: htmlPrefix + '-dragging',
edgeClass: htmlPrefix + '-edge',
edgeLabelClass: htmlPrefix + '-edge-label',
connectorClass: htmlPrefix + '-connector',
magnetClass: htmlPrefix + '-magnet',
nodeClass: htmlPrefix + '-node',
nodeOverlayClass: htmlPrefix + '-node-overlay',
leftConnectorClass: htmlPrefix + '-' + leftConnectorType + 's',
rightConnectorClass: htmlPrefix + '-' + rightConnectorType + 's',
canvasResizeThreshold: 200,
canvasResizeStep: 200
};
export interface FcCoords {
x?: number;
y?: number;
}
export interface FcRectBox {
top: number;
left: number;
right: number;
bottom: number;
}
export interface FcConnector {
id: string;
type: string;
}
export interface FcNode extends FcCoords {
id: string;
name: string;
connectors: Array<FcConnector>;
readonly?: boolean;
[key: string]: any;
}
export interface FcEdge {
label?: string;
source?: string;
destination?: string;
active?: boolean;
}
export interface FcItemInfo {
node?: FcNode;
edge?: FcEdge;
}
export interface FcModel {
nodes: Array<FcNode>;
edges: Array<FcEdge>;
}
export interface UserCallbacks {
dropNode?: (event: Event, node: FcNode) => void;
createEdge?: (event: Event, edge: FcEdge) => Observable<FcEdge>;
edgeAdded?: (edge: FcEdge) => void;
nodeRemoved?: (node: FcNode) => void;
edgeRemoved?: (edge: FcEdge) => void;
edgeDoubleClick?: (event: MouseEvent, edge: FcEdge) => void;
edgeMouseOver?: (event: MouseEvent, edge: FcEdge) => void;
isValidEdge?: (source: FcConnector, destination: FcConnector) => boolean;
edgeEdit?: (event: Event, edge: FcEdge) => void;
nodeCallbacks?: UserNodeCallbacks;
}
export interface UserNodeCallbacks {
nodeEdit?: (event: MouseEvent, node: FcNode) => void;
doubleClick?: (event: MouseEvent, node: FcNode) => void;
mouseDown?: (event: MouseEvent, node: FcNode) => void;
mouseEnter?: (event: MouseEvent, node: FcNode) => void;
mouseLeave?: (event: MouseEvent, node: FcNode) => void;
}
export interface FcCallbacks {
nodeDragstart: (event: DragEvent, node: FcNode) => void;
nodeDragend: (event: DragEvent) => void;
edgeDragstart: (event: DragEvent, connector: FcConnector) => void;
edgeDragend: (event: DragEvent) => void;
edgeDrop: (event: DragEvent, targetConnector: FcConnector) => boolean;
edgeDragoverConnector: (event: DragEvent, connector: FcConnector) => boolean;
edgeDragoverMagnet: (event: DragEvent, connector: FcConnector) => boolean;
edgeDragleaveMagnet: (event: DragEvent) => void;
nodeMouseOver: (event: MouseEvent, node: FcNode) => void;
nodeMouseOut: (event: MouseEvent, node: FcNode) => void;
connectorMouseEnter: (event: MouseEvent, connector: FcConnector) => void;
connectorMouseLeave: (event: MouseEvent, connector: FcConnector) => void;
nodeClicked: (event: MouseEvent, node: FcNode) => void;
}
export interface FcAdjacentList {
[id: string]: {
incoming: number;
outgoing: Array<string>;
};
}
class BaseError {
constructor() {
Error.apply(this, arguments);
}
}
Object.defineProperty(BaseError, 'prototype', new Error());
export class ModelvalidationError extends BaseError {
constructor(public message: string) {
super();
}
}
export function fcTopSort(graph: FcModel): Array<string> | null {
const adjacentList: FcAdjacentList = {};
graph.nodes.forEach((node) => {
adjacentList[node.id] = {incoming: 0, outgoing: []};
});
graph.edges.forEach((edge) => {
const sourceNode = graph.nodes.filter((node) => {
return node.connectors.some((connector) => {
return connector.id === edge.source;
});
})[0];
const destinationNode = graph.nodes.filter((node) => {
return node.connectors.some((connector) => {
return connector.id === edge.destination;
});
})[0];
adjacentList[sourceNode.id].outgoing.push(destinationNode.id);
adjacentList[destinationNode.id].incoming++;
});
const orderedNodes: string[] = [];
const sourceNodes: string[] = [];
for (const node of Object.keys(adjacentList)) {
const edges = adjacentList[node];
if (edges.incoming === 0) {
sourceNodes.push(node);
}
}
while (sourceNodes.length !== 0) {
const sourceNode = sourceNodes.pop();
for (let i = 0; i < adjacentList[sourceNode].outgoing.length; i++) {
const destinationNode = adjacentList[sourceNode].outgoing[i];
adjacentList[destinationNode].incoming--;
if (adjacentList[destinationNode].incoming === 0) {
sourceNodes.push(destinationNode);
}
adjacentList[sourceNode].outgoing.splice(i, 1);
i--;
}
orderedNodes.push(sourceNode);
}
let hasEdges = false;
for (const node of Object.keys(adjacentList)) {
const edges = adjacentList[node];
if (edges.incoming !== 0) {
hasEdges = true;
}
}
if (hasEdges) {
return null;
} else {
return orderedNodes;
}
}