@ichigo_san/graphing
Version:
A lightweight UML-style diagram editor built with React Flow and Tailwind CSS
279 lines (268 loc) • 10.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.DiagramService = void 0;
class DiagramService {
constructor(validationService, layoutService, exportService, technicalDetailsService) {
this.validationService = validationService;
this.layoutService = layoutService;
this.exportService = exportService;
this.technicalDetailsService = technicalDetailsService;
}
/**
* Create a new diagram from data
* @param {Object} diagramData - The diagram data
* @param {Object} options - Creation options
* @returns {Promise<Object>} The created diagram
*/
async createDiagram(diagramData, options = {}) {
try {
// Validate the diagram data
const validatedData = await this.validationService.validateDiagram(diagramData);
// Apply auto-layout if requested
let processedData = validatedData;
if (options.autoLayout !== false) {
processedData = await this.layoutService.layout(validatedData);
}
// Enrich with technical details if requested
if (options.includeTechnicalDetails !== false) {
processedData = await this.technicalDetailsService.enrichDiagram(processedData);
}
return {
success: true,
diagram: processedData,
metadata: {
createdAt: new Date().toISOString(),
version: '1.0',
autoLayout: options.autoLayout !== false,
technicalDetails: options.includeTechnicalDetails !== false
}
};
} catch (error) {
return {
success: false,
error: error.message,
diagram: null
};
}
}
/**
* Convert JSON diagram data to React Flow format
* @param {Object} config - The diagram configuration
* @returns {Object} React Flow compatible data
*/
jsonToReactFlow(config) {
const {
containers = [],
nodes = [],
connections = []
} = config;
const reactFlowNodes = [];
const reactFlowEdges = [];
// Convert containers to React Flow nodes
containers.forEach(container => {
var _container$size, _container$size2, _container$size3, _container$size4;
reactFlowNodes.push({
id: container.id,
type: 'container',
position: container.position,
data: {
label: container.label,
color: container.color || '#E3F2FD',
bgColor: container.bgColor || '#ffffff',
borderColor: container.borderColor || '#ddd',
icon: container.icon,
description: container.description,
width: ((_container$size = container.size) === null || _container$size === void 0 ? void 0 : _container$size.width) || 400,
height: ((_container$size2 = container.size) === null || _container$size2 === void 0 ? void 0 : _container$size2.height) || 300
},
style: {
width: ((_container$size3 = container.size) === null || _container$size3 === void 0 ? void 0 : _container$size3.width) || 400,
height: ((_container$size4 = container.size) === null || _container$size4 === void 0 ? void 0 : _container$size4.height) || 300,
zIndex: container.zIndex || 1
},
zIndex: container.zIndex || 1
});
});
// Convert nodes to React Flow nodes
nodes.forEach(node => {
var _node$size, _node$size2, _node$size3, _node$size4;
reactFlowNodes.push({
id: node.id,
type: node.type || 'component',
position: node.position,
parentNode: node.parentContainer,
data: {
label: node.label,
color: node.color || '#E3F2FD',
borderColor: node.borderColor || '#ddd',
icon: node.icon,
description: node.description,
width: ((_node$size = node.size) === null || _node$size === void 0 ? void 0 : _node$size.width) || 150,
height: ((_node$size2 = node.size) === null || _node$size2 === void 0 ? void 0 : _node$size2.height) || 80
},
style: {
width: ((_node$size3 = node.size) === null || _node$size3 === void 0 ? void 0 : _node$size3.width) || 150,
height: ((_node$size4 = node.size) === null || _node$size4 === void 0 ? void 0 : _node$size4.height) || 80,
zIndex: node.zIndex || 10
},
zIndex: node.zIndex || 10
});
});
// Convert connections to React Flow edges
connections.forEach(connection => {
var _connection$style, _connection$style2, _connection$style3;
reactFlowEdges.push({
id: connection.id,
source: connection.source,
target: connection.target,
label: connection.label,
type: connection.type || 'adjustable',
animated: connection.animated || false,
data: {
label: connection.label,
description: connection.description,
waypoints: connection.waypoints,
intersection: connection.intersection
},
markerStart: connection.markerStart,
markerEnd: connection.markerEnd,
style: {
strokeWidth: ((_connection$style = connection.style) === null || _connection$style === void 0 ? void 0 : _connection$style.strokeWidth) || 2,
strokeDasharray: (_connection$style2 = connection.style) === null || _connection$style2 === void 0 ? void 0 : _connection$style2.strokeDasharray,
stroke: ((_connection$style3 = connection.style) === null || _connection$style3 === void 0 ? void 0 : _connection$style3.stroke) || '#000000'
},
zIndex: connection.zIndex || 5
});
});
return {
nodes: reactFlowNodes,
edges: reactFlowEdges
};
}
/**
* Convert React Flow data to JSON format
* @param {Array} nodes - React Flow nodes
* @param {Array} edges - React Flow edges
* @returns {Object} JSON format diagram data
*/
reactFlowToJson(nodes, edges) {
const containers = nodes.filter(node => node.type === 'container').map(container => {
var _container$style, _container$style2;
return {
id: container.id,
label: container.data.label,
position: container.position,
size: {
width: ((_container$style = container.style) === null || _container$style === void 0 ? void 0 : _container$style.width) || 400,
height: ((_container$style2 = container.style) === null || _container$style2 === void 0 ? void 0 : _container$style2.height) || 300
},
color: container.data.color,
bgColor: container.data.bgColor,
borderColor: container.data.borderColor,
icon: container.data.icon,
description: container.data.description,
zIndex: container.zIndex || 1
};
});
const diagramNodes = nodes.filter(node => node.type !== 'container').map(node => {
var _node$style, _node$style2;
return {
id: node.id,
label: node.data.label,
type: node.type,
position: node.position,
parentContainer: node.parentNode,
size: {
width: ((_node$style = node.style) === null || _node$style === void 0 ? void 0 : _node$style.width) || 150,
height: ((_node$style2 = node.style) === null || _node$style2 === void 0 ? void 0 : _node$style2.height) || 80
},
color: node.data.color,
borderColor: node.data.borderColor,
icon: node.data.icon,
description: node.data.description,
zIndex: node.zIndex || 10
};
});
const connections = edges.map(edge => {
var _edge$data, _edge$data2, _edge$data3, _edge$data4, _edge$style, _edge$style2;
return {
id: edge.id,
source: edge.source,
target: edge.target,
label: (_edge$data = edge.data) === null || _edge$data === void 0 ? void 0 : _edge$data.label,
type: edge.type,
animated: edge.animated,
description: (_edge$data2 = edge.data) === null || _edge$data2 === void 0 ? void 0 : _edge$data2.description,
waypoints: (_edge$data3 = edge.data) === null || _edge$data3 === void 0 ? void 0 : _edge$data3.waypoints,
markerStart: edge.markerStart,
markerEnd: edge.markerEnd,
intersection: (_edge$data4 = edge.data) === null || _edge$data4 === void 0 ? void 0 : _edge$data4.intersection,
style: {
strokeWidth: ((_edge$style = edge.style) === null || _edge$style === void 0 ? void 0 : _edge$style.strokeWidth) || 2,
strokeDasharray: (_edge$style2 = edge.style) === null || _edge$style2 === void 0 ? void 0 : _edge$style2.strokeDasharray
},
zIndex: edge.zIndex || 5
};
});
return {
metadata: {
name: 'Architecture Diagram',
description: 'Exported architecture diagram',
version: '1.0',
exportDate: new Date().toISOString()
},
containers,
nodes: diagramNodes,
connections
};
}
/**
* Get diagram bounds for viewport calculations
* @param {Array} nodes - React Flow nodes
* @returns {Object} Bounds object with x, y, width, height
*/
getDiagramBounds(nodes) {
if (nodes.length === 0) {
return {
x: 0,
y: 0,
width: 0,
height: 0
};
}
let minX = Infinity,
minY = Infinity,
maxX = -Infinity,
maxY = -Infinity;
nodes.forEach(node => {
var _node$__rf, _node$style3, _node$__rf2, _node$style4;
const width = ((_node$__rf = node.__rf) === null || _node$__rf === void 0 ? void 0 : _node$__rf.width) || ((_node$style3 = node.style) === null || _node$style3 === void 0 ? void 0 : _node$style3.width) || 150;
const height = ((_node$__rf2 = node.__rf) === null || _node$__rf2 === void 0 ? void 0 : _node$__rf2.height) || ((_node$style4 = node.style) === null || _node$style4 === void 0 ? void 0 : _node$style4.height) || 80;
minX = Math.min(minX, node.position.x);
minY = Math.min(minY, node.position.y);
maxX = Math.max(maxX, node.position.x + width);
maxY = Math.max(maxY, node.position.y + height);
});
return {
x: minX,
y: minY,
width: maxX - minX,
height: maxY - minY
};
}
/**
* Validate diagram data against schema
* @param {Object} diagramData - The diagram data to validate
* @returns {boolean} True if valid, throws error if invalid
*/
validateDiagramData(diagramData) {
// Simple validation - check if required fields exist
if (!diagramData || !diagramData.containers || !diagramData.nodes || !diagramData.connections) {
throw new Error('Validation failed: Missing required fields (containers, nodes, connections)');
}
return true;
}
}
exports.DiagramService = DiagramService;