import { SettingsDataModelOverviewEffect } from '@/settings/data-model/graph-overview/components/SettingsDataModelOverviewEffect'; import { SettingsDataModelOverviewObject } from '@/settings/data-model/graph-overview/components/SettingsDataModelOverviewObject'; import { SettingsDataModelOverviewRelationMarkers } from '@/settings/data-model/graph-overview/components/SettingsDataModelOverviewRelationMarkers'; import { calculateHandlePosition } from '@/settings/data-model/graph-overview/utils/calculateHandlePosition'; import styled from '@emotion/styled'; import { useCallback, useState } from 'react'; import ReactFlow, { Background, EdgeChange, NodeChange, applyEdgeChanges, applyNodeChanges, getIncomers, getOutgoers, useEdgesState, useNodesState, useReactFlow, } from 'reactflow'; import { Button, IconButtonGroup, IconLock, IconLockOpen, IconMaximize, IconMinus, IconPlus, IconX, } from 'twenty-ui'; import { isDefined } from '~/utils/isDefined'; const NodeTypes = { object: SettingsDataModelOverviewObject, }; const StyledContainer = styled.div` height: 100%; .react-flow__handle { border: 0 !important; background: transparent !important; width: 6px; height: 6px; min-height: 6px; min-width: 6px; pointer-events: none; } .left-handle { left: 0; top: 50%; transform: translateX(-50%) translateY(-50%); } .right-handle { right: 0; top: 50%; transform: translateX(50%) translateY(-50%); } .react-flow__node { z-index: -1 !important; } `; const StyledCloseButton = styled.div` position: absolute; top: ${({ theme }) => theme.spacing(3)}; left: ${({ theme }) => theme.spacing(3)}; z-index: 5; `; export const SettingsDataModelOverview = () => { const { fitView, zoomIn, zoomOut } = useReactFlow(); const [nodes, setNodes] = useNodesState([]); const [edges, setEdges] = useEdgesState([]); const [isInteractive, setInteractive] = useState(true); const onNodesChange = useCallback( (changes: NodeChange[]) => setNodes((nds) => applyNodeChanges(changes, nds)), [setNodes], ); const onEdgesChange = useCallback( (changes: EdgeChange[]) => setEdges((eds) => applyEdgeChanges(changes, eds)), [setEdges], ); const handleNodesChange = useCallback( (nodeChanges: any[]) => { nodeChanges.forEach((nodeChange) => { const node = nodes.find((node) => node.id === nodeChange.id); if (!node) { return; } const incomingNodes = getIncomers(node, nodes, edges); const newXPos = 'positionAbsolute' in nodeChange ? nodeChange.positionAbsolute?.x : node.position.x || 0; incomingNodes.forEach((incomingNode) => { const edge = edges.find((edge) => { return edge.target === node.id && edge.source === incomingNode.id; }); if (isDefined(newXPos)) { setEdges((eds) => eds.map((ed) => { if (isDefined(edge) && ed.id === edge.id) { const sourcePosition = calculateHandlePosition( incomingNode.width as number, incomingNode.position.x, node.width as number, newXPos, 'source', ); const targetPosition = calculateHandlePosition( incomingNode.width as number, incomingNode.position.x, node.width as number, newXPos, 'target', ); const sourceHandle = `${edge.data.sourceField}-${sourcePosition}`; const targetHandle = `${edge.data.targetField}-${targetPosition}`; ed.sourceHandle = sourceHandle; ed.targetHandle = targetHandle; ed.markerEnd = 'marker'; ed.markerStart = 'marker'; } return ed; }), ); } }); const outgoingNodes = getOutgoers(node, nodes, edges); outgoingNodes.forEach((targetNode) => { const edge = edges.find((edge) => { return edge.target === targetNode.id && edge.source === node.id; }); if (isDefined(newXPos)) { setEdges((eds) => eds.map((ed) => { if (isDefined(edge) && ed.id === edge.id) { const sourcePosition = calculateHandlePosition( node.width as number, newXPos, targetNode.width as number, targetNode.position.x, 'source', ); const targetPosition = calculateHandlePosition( node.width as number, newXPos, targetNode.width as number, targetNode.position.x, 'target', ); const sourceHandle = `${edge.data.sourceField}-${sourcePosition}`; const targetHandle = `${edge.data.targetField}-${targetPosition}`; ed.sourceHandle = sourceHandle; ed.targetHandle = targetHandle; ed.markerEnd = 'marker'; ed.markerStart = 'marker'; } return ed; }), ); } }); }); onNodesChange(nodeChanges); }, [onNodesChange, setEdges, nodes, edges], ); return ( zoomIn(), }, { Icon: IconMinus, onClick: () => zoomOut(), }, { Icon: IconMaximize, onClick: () => fitView(), }, { Icon: isInteractive ? IconLockOpen : IconLock, onClick: () => setInteractive(!isInteractive), }, ]} > ); };