@redocly/theme
Version:
Shared UI components lib
165 lines • 7.35 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.useGraph = useGraph;
const react_1 = require("react");
const react_2 = require("@xyflow/react");
const catalog_1 = require("../../constants/catalog");
// Note: This isn't final implementation, leaved comments for future reference.
function useGraph({ entity, relations }) {
const rootNodeId = entity.id;
// Compute final label for a relation considering its role
const getRelationLabel = (0, react_1.useCallback)((relation) => {
const relationType = relation.relationType;
if (!relationType) {
return 'related';
}
return relation.relationRole === 'source' ? catalog_1.reverseRelationMap[relationType] : relationType;
}, []);
const processedRelations = (0, react_1.useMemo)(() => {
// Exclude self-relations and deduplicate by id
const seenIds = new Set();
const filtered = (relations !== null && relations !== void 0 ? relations : []).filter((r) => r.id !== rootNodeId && r.key !== entity.key);
const unique = [];
for (const r of filtered) {
if (seenIds.has(r.id))
continue;
seenIds.add(r.id);
unique.push({
id: r.id,
title: r.title,
entityType: r.type, // Group by entity type, not relation type
relationLabel: getRelationLabel(r),
key: r.key,
});
}
return unique;
}, [relations, getRelationLabel, rootNodeId, entity.key]);
// Entity data type for layout
const computedNodes = (0, react_1.useMemo)(() => {
var _a;
if (!processedRelations.length) {
return [
{
id: rootNodeId,
type: catalog_1.GraphCustomNodeType.CatalogEntity,
position: { x: 0, y: 0 },
data: {
label: entity.title,
entityType: entity.type,
isRoot: true,
entityKey: entity.key,
},
sourcePosition: react_2.Position.Bottom,
targetPosition: react_2.Position.Top,
},
];
}
// Group entities by their entity type
const entityTypeGroups = new Map();
for (const rel of processedRelations) {
const entityData = {
id: rel.id,
title: rel.title,
entityType: rel.entityType,
relationLabel: rel.relationLabel,
key: rel.key,
};
const current = entityTypeGroups.get(rel.entityType);
if (current) {
current.push(entityData);
}
else {
entityTypeGroups.set(rel.entityType, [entityData]);
}
}
// Sort entity types for consistent ordering
const entityTypes = Array.from(entityTypeGroups.keys()).sort();
// Layout constants
const rootY = 0;
const verticalGap = 80; // Gap between entities of same type (vertical)
const horizontalGap = 250; // Gap between different entity types (horizontal)
const topMargin = 240; // Distance from root to first row of entities
// Special handling for single entity type group - root on left, entities on right
const isSingleGroup = entityTypes.length === 1;
let rootX = 0;
let startX = 0;
if (isSingleGroup) {
// Position root on the left, entities on the right
rootX = -horizontalGap / 2;
startX = horizontalGap / 2;
}
else {
// Calculate starting X position to center all groups (original behavior)
const totalWidth = (entityTypes.length - 1) * horizontalGap;
startX = -totalWidth / 2;
}
const nodes = [
// Root entity
{
id: rootNodeId,
type: catalog_1.GraphCustomNodeType.CatalogEntity,
position: { x: rootX, y: rootY },
data: { label: entity.title, entityType: entity.type, isRoot: true, entityKey: entity.key },
sourcePosition: react_2.Position.Bottom,
targetPosition: react_2.Position.Top,
},
];
// Position entities by type groups
for (let typeIndex = 0; typeIndex < entityTypes.length; typeIndex++) {
const entityType = entityTypes[typeIndex];
const entitiesOfType = (_a = entityTypeGroups.get(entityType)) !== null && _a !== void 0 ? _a : [];
// Calculate X position for this entity type group
const groupX = startX + typeIndex * horizontalGap;
// Calculate starting Y position to center entities vertically within the group
const groupHeight = (entitiesOfType.length - 1) * verticalGap;
const groupStartY = rootY + topMargin - groupHeight / 2;
// Position each entity within the group
for (let entityIndex = 0; entityIndex < entitiesOfType.length; entityIndex++) {
const entityData = entitiesOfType[entityIndex];
const entityY = groupStartY + entityIndex * verticalGap;
nodes.push({
id: entityData.id,
type: catalog_1.GraphCustomNodeType.CatalogEntity,
position: { x: groupX, y: entityY },
data: {
label: entityData.title,
entityType: entityData.entityType,
isRoot: false,
entityKey: entityData.key,
},
sourcePosition: react_2.Position.Bottom,
targetPosition: react_2.Position.Top,
});
}
}
return nodes;
}, [rootNodeId, entity.title, entity.type, entity.key, processedRelations]);
const computedEdges = (0, react_1.useMemo)(() => {
return processedRelations.map((relation) => ({
id: `e-${rootNodeId}-${relation.id}`,
source: rootNodeId,
target: relation.id,
sourceHandle: catalog_1.GraphHandleType.Source, // Use the bottom handle of the center node
targetHandle: catalog_1.GraphHandleType.Target, // Use the target handle (top) of related nodes
type: catalog_1.GraphCustomEdgeType.CatalogEdge,
label: relation.relationLabel,
}));
}, [rootNodeId, processedRelations]);
const [nodes, setNodes, onNodesChange] = (0, react_2.useNodesState)(computedNodes);
const [edges, setEdges, onEdgesChange] = (0, react_2.useEdgesState)(computedEdges);
(0, react_1.useEffect)(() => {
setNodes(computedNodes);
}, [computedNodes, setNodes]);
(0, react_1.useEffect)(() => {
setEdges(computedEdges);
}, [computedEdges, setEdges]);
const onConnect = (0, react_1.useCallback)((params) => setEdges((edgesSnapshot) => (0, react_2.addEdge)(params, edgesSnapshot)), [setEdges]);
return {
nodes,
edges,
onNodesChange,
onEdgesChange,
onConnect,
};
}
//# sourceMappingURL=use-graph.js.map