Files
twenty/packages/twenty-front/src/modules/settings/data-model/graph-overview/components/SettingsDataModelOverviewEffect.tsx
T
Félix Malfait 464a480043 Continue ESLINT9 Migration (#13795)
Might already fix #13793
2025-08-10 23:25:58 +02:00

109 lines
3.3 KiB
TypeScript

import { useTheme } from '@emotion/react';
import { type Edge, type Node } from '@xyflow/react';
import { useEffect } from 'react';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { isDefined } from 'twenty-shared/utils';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
type SettingsDataModelOverviewEffectProps = {
setEdges: (edges: Edge[]) => void;
setNodes: (nodes: Node[]) => void;
};
export const SettingsDataModelOverviewEffect = ({
setEdges,
setNodes,
}: SettingsDataModelOverviewEffectProps) => {
const theme = useTheme();
const { activeNonSystemObjectMetadataItems: items } =
useFilteredObjectMetadataItems();
useEffect(() => {
const loadDagreAndLayout = async () => {
const dagre = await import('@dagrejs/dagre');
const g = new dagre.default.graphlib.Graph();
g.setGraph({ rankdir: 'LR' });
g.setDefaultEdgeLabel(() => ({}));
const edges: Edge[] = [];
const nodes: Node[] = [];
let i = 0;
for (const object of items) {
nodes.push({
id: object.namePlural,
width: 220,
height: 100,
position: { x: i * 300, y: 0 },
data: object,
type: 'object',
});
g.setNode(object.namePlural, { width: 220, height: 100 });
for (const field of object.fields) {
if (
isDefined(field.relation) &&
isDefined(
items.find(
(x) => x.id === field.relation?.targetObjectMetadata.id,
),
)
) {
const sourceObj = field.relation?.sourceObjectMetadata.namePlural;
const targetObj = field.relation?.targetObjectMetadata.namePlural;
edges.push({
id: `${sourceObj}-${targetObj}`,
source: object.namePlural,
sourceHandle: `${field.id}-right`,
target: field.relation.targetObjectMetadata.namePlural,
targetHandle: `${field.relation.targetObjectMetadata}-left`,
type: 'smoothstep',
style: {
strokeWidth: 1,
stroke: theme.color.gray,
},
markerEnd: 'marker',
markerStart: 'marker',
data: {
sourceField: field.id,
targetField: field.relation.targetFieldMetadata.id,
relation: field.relation.type,
sourceObject: sourceObj,
targetObject: targetObj,
},
});
if (
!isUndefinedOrNull(sourceObj) &&
!isUndefinedOrNull(targetObj)
) {
g.setEdge(sourceObj, targetObj);
}
}
}
i++;
}
dagre.default.layout(g);
nodes.forEach((node) => {
const nodeWithPosition = g.node(node.id);
node.position = {
// We are shifting the dagre node position (anchor=center center) to the top left
// so it matches the React Flow node anchor point (top left).
x: nodeWithPosition.x - (node.width ?? 0) / 2,
y: nodeWithPosition.y - (node.height ?? 0) / 2,
};
});
setNodes(nodes);
setEdges(edges);
};
loadDagreAndLayout();
}, [items, setEdges, setNodes, theme]);
return <></>;
};