306 lines
10 KiB
TypeScript
306 lines
10 KiB
TypeScript
import log from "loglevel";
|
|
import {Color3, Scene, Vector3} from "@babylonjs/core";
|
|
import {DiagramManager} from "../diagram/diagramManager";
|
|
import {DiagramEventType} from "../diagram/types/diagramEntity";
|
|
|
|
type DrawIOEntity = {
|
|
text?: string,
|
|
id?: string,
|
|
parent?: string,
|
|
parentEntity?: DrawIOEntity,
|
|
geometry?: DrawIOGeometry,
|
|
|
|
}
|
|
type DrawIOGeometry = {
|
|
zIndex?: number,
|
|
x: number,
|
|
y: number,
|
|
width: number,
|
|
height: number
|
|
|
|
}
|
|
|
|
class EntityTree {
|
|
private readonly logger = log.getLogger('EntityTree');
|
|
private root: DrawIOEntity;
|
|
private readonly nodes: Map<string, DrawIOEntity> = new Map<string, DrawIOEntity>();
|
|
private readonly unparented: Array<DrawIOEntity> = new Array<DrawIOEntity>();
|
|
|
|
constructor() {
|
|
this.root = {};
|
|
}
|
|
|
|
public getNodes(): Array<DrawIOEntity> {
|
|
this.reparent();
|
|
const output: Array<DrawIOEntity> = new Array<DrawIOEntity>();
|
|
this.nodes.forEach((node) => {
|
|
if (node.parentEntity) {
|
|
node.geometry = this.computeOffset(node);
|
|
}
|
|
output.push(node);
|
|
});
|
|
return output;
|
|
}
|
|
|
|
public reparent() {
|
|
this.unparented.forEach((node) => {
|
|
if (this.nodes.has(node.parent)) {
|
|
this.logger.debug('reparenting node: ' + node.id + ' to parent: ' + node.parent);
|
|
node.parentEntity = this.nodes.get(node.parent);
|
|
} else {
|
|
this.logger.warn('parent node does not exist for id: ' + node.id +
|
|
' parent id: ' + node.parent);
|
|
}
|
|
});
|
|
}
|
|
|
|
public addNode(node: DrawIOEntity) {
|
|
if (this.nodes.has(node.id)) {
|
|
this.logger.warn('node already exists for id: ' + node.id);
|
|
} else {
|
|
if (node.parent) {
|
|
if (this.nodes.has(node.parent)) {
|
|
node.parentEntity = this.nodes.get(node.parent);
|
|
this.nodes.set(node.id, node);
|
|
} else {
|
|
this.logger.warn('parent node does not exist for id: ' + node.id +
|
|
' parent id: ' + node.parent);
|
|
this.unparented.push(node);
|
|
}
|
|
} else {
|
|
this.logger.warn('no parent for node id: ' + node.id + 'setting as root');
|
|
this.nodes.set(node.id, node);
|
|
this.root = node;
|
|
}
|
|
}
|
|
}
|
|
|
|
private computeOffset(node: DrawIOEntity): DrawIOGeometry {
|
|
if (node.parentEntity) {
|
|
const parentgeo = this.computeOffset(node.parentEntity);
|
|
if (parentgeo) {
|
|
const parentzIndex = 1 + parentgeo.zIndex ? parentgeo.zIndex : 0;
|
|
return {
|
|
x: node.geometry.x,
|
|
y: node.geometry.y,
|
|
width: node.geometry.width,
|
|
height: node.geometry.height,
|
|
zIndex: node.geometry.zIndex ? node.geometry.zIndex + parentzIndex : parentzIndex + 1
|
|
};
|
|
} else {
|
|
return {
|
|
x: node.geometry.x,
|
|
y: node.geometry.y,
|
|
width: node.geometry.width,
|
|
height: node.geometry.height,
|
|
zIndex: node.geometry.zIndex ? node.geometry.zIndex : 0
|
|
};
|
|
}
|
|
|
|
} else {
|
|
if (node.geometry) {
|
|
if (node.geometry.zIndex === undefined) {
|
|
node.geometry.zIndex = 0;
|
|
}
|
|
return node.geometry;
|
|
} else {
|
|
return {
|
|
x: 0,
|
|
y: 0,
|
|
width: 0,
|
|
height: 0,
|
|
zIndex: 0
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
type DrawIOConnector = {
|
|
id: string,
|
|
source: string,
|
|
target: string,
|
|
text: string
|
|
}
|
|
|
|
export class DrawioManager {
|
|
private diagramManager: DiagramManager;
|
|
private connectors: Array<DrawIOConnector> = [];
|
|
private readonly scene: Scene;
|
|
private readonly logger = log.getLogger('DrawioManager');
|
|
private minY = 0;
|
|
private minX = 0;
|
|
private maxX = 0;
|
|
private maxY = 0;
|
|
private maxZ = 0;
|
|
|
|
constructor(scene: Scene, diagramManager: DiagramManager) {
|
|
this.scene = scene;
|
|
this.diagramManager = diagramManager;
|
|
this.buildGraph();
|
|
}
|
|
|
|
|
|
private async fetchData(url: string): Promise<Document> {
|
|
this.logger.debug("starting to get graph");
|
|
const graph = await fetch(url);
|
|
this.logger.debug('got graph');
|
|
const graphXml = await graph.text();
|
|
return new DOMParser().parseFromString(graphXml, 'text/html');
|
|
}
|
|
|
|
private getDiagram(doc: Document, index: number): Element {
|
|
const firstDiagram = doc.querySelectorAll('diagram')[index];
|
|
return firstDiagram.querySelector('mxGraphModel');
|
|
}
|
|
|
|
private parseDiagram(mxDiagram: Element): EntityTree {
|
|
const entityTree = new EntityTree();
|
|
|
|
mxDiagram.querySelectorAll('mxCell').forEach((cell) => {
|
|
const value = cell.getAttribute('value');
|
|
let ent = null;
|
|
if (value) {
|
|
ent = new DOMParser().parseFromString(value, 'text/html');
|
|
const errorNode = ent.querySelector("parsererror");
|
|
if (errorNode) {
|
|
this.logger.error(value);
|
|
}
|
|
|
|
}
|
|
|
|
const text = ent ? this.getText(ent, '') : '';
|
|
const id = cell.getAttribute('id');
|
|
const parent = cell.getAttribute('parent');
|
|
const source = cell.getAttribute('source');
|
|
const target = cell.getAttribute('target');
|
|
const edge = cell.getAttribute('target');
|
|
if (source && target && edge) {
|
|
this.connectors.push({id: id, source: source, target: target, text: text});
|
|
} else {
|
|
|
|
const geo = cell.querySelector('[id="' + id + '"] > mxGeometry');
|
|
let geometry;
|
|
if (geo) {
|
|
geometry = {
|
|
x: Number.parseFloat(geo.getAttribute('x')),
|
|
y: Number.parseFloat(geo.getAttribute('y')),
|
|
width: Number.parseFloat(geo.getAttribute('width')),
|
|
height: Number.parseFloat(geo.getAttribute('height')),
|
|
}
|
|
} else {
|
|
geometry = {
|
|
x: 0,
|
|
y: 0,
|
|
width: 0,
|
|
height: 0
|
|
}
|
|
}
|
|
|
|
|
|
//entities.push({text: text, id: id, parent: parent, geometry: this.fixMinMax(geometry)});
|
|
if (text) {
|
|
this.logger.debug('Text' + text);
|
|
this.logger.debug('Geometry' + JSON.stringify(geometry));
|
|
}
|
|
if (geometry) {
|
|
if (Number.isNaN(geometry.x) || Number.isNaN(geometry.y)
|
|
|| Number.isNaN(geometry.width) ||
|
|
Number.isNaN(geometry.height)) {
|
|
this.logger.warn('invalid geometry for node: ' + id, geometry);
|
|
} else {
|
|
entityTree.addNode({text: text, id: id, parent: parent, geometry: geometry});
|
|
|
|
}
|
|
}
|
|
}
|
|
});
|
|
return entityTree
|
|
}
|
|
|
|
private async buildGraph() {
|
|
|
|
const doc = await this.fetchData('/arch_demo.xml');
|
|
const mxDiagram = this.getDiagram(doc, 0);
|
|
this.logger.debug('begin parse');
|
|
const entities: EntityTree = this.parseDiagram(mxDiagram);
|
|
|
|
entities.getNodes().forEach((node) => {
|
|
if (node.geometry.x < this.minX) {
|
|
this.minX = node.geometry.x;
|
|
this.logger.debug('minX: ' + this.minX);
|
|
}
|
|
if (node.geometry.y < this.minY) {
|
|
this.minY = node.geometry.y;
|
|
this.logger.debug('minY: ' + this.minY);
|
|
}
|
|
if (node.geometry.x + node.geometry.width > this.maxX) {
|
|
this.maxX = node.geometry.x + node.geometry.width;
|
|
this.logger.debug('maxX: ' + this.maxX);
|
|
}
|
|
if (node.geometry.y + node.geometry.height > this.maxY) {
|
|
this.maxY = node.geometry.y + node.geometry.height;
|
|
this.logger.debug('maxY: ' + this.maxY);
|
|
}
|
|
if (node.geometry.zIndex > this.maxZ) {
|
|
this.maxZ = node.geometry.zIndex;
|
|
this.logger.debug('maxZ: ' + this.maxZ);
|
|
}
|
|
|
|
});
|
|
this.logger.info('minX: ' + this.minX + ' minY: ' + this.minY + ' maxX: ' + this.maxX + ' maxY: ' + this.maxY);
|
|
|
|
|
|
this.logger.debug('done parsing');
|
|
this.logger.debug(this.connectors);
|
|
this.createSceneData(entities.getNodes());
|
|
|
|
}
|
|
|
|
private createSceneData(nodes) {
|
|
const yOffset = 20;
|
|
const scale = .001;
|
|
nodes.forEach((entity) => {
|
|
this.diagramManager.onDiagramEventObservable.notifyObservers(
|
|
{
|
|
type: DiagramEventType.ADD,
|
|
entity: {
|
|
text: entity.text,
|
|
id: entity.id,
|
|
position: new Vector3(
|
|
(entity.geometry.x * scale) - (entity.geometry.width * scale / 2),
|
|
yOffset - (entity.geometry.y * scale) + (entity.geometry.height * scale / 2),
|
|
entity.geometry.zIndex * .1),
|
|
scale: new Vector3(entity.geometry.width * scale, entity.geometry.height * scale, .05),
|
|
color: Color3.Blue().toHexString(),
|
|
template: '#box-template'
|
|
}
|
|
}
|
|
);
|
|
});
|
|
|
|
|
|
}
|
|
|
|
private getText(obj: Node, text: string): string {
|
|
if (obj.nodeType == Node.TEXT_NODE) {
|
|
if (obj.textContent) {
|
|
return obj.textContent.trim();
|
|
} else {
|
|
return '';
|
|
}
|
|
} else {
|
|
if (obj.childNodes) {
|
|
let t = '';
|
|
obj.childNodes.forEach((child) => {
|
|
t += ' ' + this.getText(child, '');
|
|
});
|
|
return text.trim() + ' ' + t.trim();
|
|
} else {
|
|
return '';
|
|
}
|
|
}
|
|
}
|
|
}
|