import { ApartmentOutlined, FullscreenExitOutlined, FullscreenOutlined, MenuOutlined, ShareAltOutlined, LineHeightOutlined, } from '@ant-design/icons'; import { Graph } from '@antv/x6'; import '@antv/x6-react-shape'; import { css, cx } from '@emotion/css'; import { SchemaOptionsContext } from '@formily/react'; import { APIClientProvider, collection, CollectionManagerProvider, CurrentAppInfoContext, SchemaComponent, SchemaComponentOptions, useAPIClient, useCollectionManager, useCompile, useCurrentAppInfo, Select, CollectionManagerContext, } from '@nocobase/client'; import { useFullscreen } from 'ahooks'; import { Button, Input, Layout, Menu, Popover, Tooltip, Switch } from 'antd'; import dagre from 'dagre'; import { drop, groupBy, last, maxBy, minBy, take, debounce } from 'lodash'; import React, { createContext, forwardRef, useContext, useEffect, useLayoutEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useAsyncDataSource, useCreateActionAndRefreshCM } from './action-hooks'; import { AddCollectionAction } from './components/AddCollectionAction'; import Entity from './components/Entity'; import { collectionListClass, graphCollectionContainerClass } from './style'; import { formatData, getDiffEdge, getDiffNode, getInheritCollections, getChildrenCollections } from './utils'; import { SimpleNodeView } from './components/ViewNode'; const LINE_HEIGHT = 40; const NODE_WIDTH = 250; let targetGraph; let targetNode; let dir = 'TB'; // LR RL TB BT 横排 export enum DirectionType { Both = 'both', Target = 'target', Source = 'source', } export enum ConnectionType { Both = 'both', Inherit = 'inherited', Entity = 'entity', } const getGridData = (num, arr) => { const newArr = []; while (arr.length > 0 && num) { newArr.push(arr.splice(0, num)); } return newArr; }; //初始布局 async function layout(createPositions) { const { positions } = targetGraph; let graphPositions = []; const nodes: any[] = targetGraph.getNodes(); const edges = targetGraph.getEdges(); const g: any = new dagre.graphlib.Graph(); g.setGraph({ rankdir: dir, nodesep: 50, edgesep: 50, rankSep: 50, align: 'DL', controlPoints: true }); g.setDefaultEdgeLabel(() => ({})); nodes.forEach((node, i) => { const width = NODE_WIDTH; const height = node.getPorts().length * 32 + 30; g.setNode(node.id, { width, height }); }); dagre.layout(g); targetGraph.freeze(); const dNodes = getGridData(15, g.nodes()); dNodes.forEach((arr, row) => { arr.forEach((id, index) => { const node = targetGraph.getCell(id); const col = index % 15; if (node) { const targetPosition = (positions && positions.find((v) => { return v.collectionName === node.store.data.name; })) || {}; const calculatedPosition = { x: col * 325 + 50, y: row * 400 + 60 }; node.position(targetPosition.x || calculatedPosition.x, targetPosition.y || calculatedPosition.y); if (positions && !positions.find((v) => v.collectionName === node.store.data.name)) { // 位置表中没有的表都自动保存 graphPositions.push({ collectionName: node.store.data.name, x: calculatedPosition.x, y: calculatedPosition.y, }); } } }); }); edges.forEach((edge) => { optimizeEdge(edge); }); targetGraph.unfreeze(); if (targetNode) { typeof targetNode === 'string' ? targetGraph.positionCell(last(nodes), 'top', { padding: 100 }) : targetGraph.positionCell(targetNode, 'top', { padding: 100 }); } else { targetGraph.positionCell(nodes[0], 'top-left', { padding: 100 }); } if (graphPositions.length > 0) { await createPositions(graphPositions); graphPositions = []; } } function optimizeEdge(edge) { const { store: { data: { connectionType }, }, } = edge; const source = edge.getSource(); const target = edge.getTarget(); const sorceNodeX = targetGraph.getCell(source.cell).position().x; const targeNodeX = targetGraph.getCell(target.cell).position().x; const leftAnchor = connectionType ? { name: 'topLeft', args: { dy: -20, }, } : { name: 'left', }; const rightAnchor = connectionType ? { name: 'topRight', args: { dy: -20, }, } : 'right'; const router = connectionType ? 'normal' : 'er'; const vertices = edge.getVertices(); vertices.forEach(() => { return edge.removeVertexAt(0); }); if (sorceNodeX - 100 > targeNodeX) { edge.setSource({ cell: source.cell, port: source.port, anchor: leftAnchor, }); edge.setTarget({ cell: target.cell, port: target.port, anchor: rightAnchor, }); edge.setRouter(router, { direction: 'H', }); } else if (Math.abs(sorceNodeX - targeNodeX) < 100) { const sourceCell = targetGraph.getCell(source.cell); const targetCell = targetGraph.getCell(target.cell); edge.setSource({ cell: source.cell, port: source.port, anchor: leftAnchor, }); edge.setTarget({ cell: target.cell, port: target.port, anchor: leftAnchor, }); if (connectionType) { edge.setVertices([ { x: sourceCell.position().x - 30, y: sourceCell.position().y + 20 }, { x: targetCell.position().x - 30, y: targetCell.position().y + 20 }, ]); edge.setRouter('normal'); } else { edge.setRouter('oneSide', { side: 'left' }); } } else { edge.setSource({ cell: source.cell, port: source.port, anchor: rightAnchor, }); edge.setTarget({ cell: target.cell, port: target.port, anchor: leftAnchor, }); edge.setRouter(router, { direction: 'H', }); } } function getNodes(nodes) { targetGraph.addNodes(nodes); } function getEdges(edges) { edges.forEach((item) => { if (item.source && item.target) { targetGraph.addEdge({ ...item, connector: { name: 'normal', zIndex: 1000, }, }); } }); } const getPopupContainer = () => { return document.getElementById('graph_container'); }; const CollapsedContext = createContext({}); const formatNodeData = () => { const layoutNodes = []; const edges = targetGraph.getEdges(); const nodes = targetGraph.getNodes(); edges.forEach((edge) => { layoutNodes.push(edge.getSourceCellId()); layoutNodes.push(edge.getTargetCellId()); }); const nodeGroup = groupBy(nodes, (v) => { if (layoutNodes.includes(v.id)) { return 'linkNodes'; } else { return 'rawNodes'; } }); return nodeGroup; }; //自动布局 const handelResetLayout = () => { const { linkNodes = [], rawNodes } = formatNodeData(); const { positions } = targetGraph; const nodes = linkNodes.concat(rawNodes); const edges = targetGraph.getEdges(); const g = new dagre.graphlib.Graph(); let alternateNum; let rawEntity; let num; let minX; let maxY; const updatePositionData = []; g.setGraph({ rankdir: 'TB', nodesep: 50, edgesep: 50, rankSep: 50, align: 'DL', controlPoints: true }); const width = 250; const height = 400; nodes.forEach((node) => { g.setNode(node.id, { width, height }); }); edges.forEach((edge) => { const source = edge.getSource(); const target = edge.getTarget(); g.setEdge(source.cell, target.cell, {}); }); dagre.layout(g); targetGraph.freeze(); const gNodes = g.nodes(); const nodeWithEdges = take(gNodes, linkNodes.length); const nodeWithoutEdges = drop(gNodes, linkNodes.length); nodeWithEdges.forEach((id) => { const node = targetGraph.getCell(id); const positionId = positions.find((v) => v.collectionName === node.id)?.id; if (node) { const pos = g.node(id); updatePositionData.push({ id: positionId, x: pos.x, y: pos.y }); node.position(pos?.x, pos?.y); } }); if (nodeWithEdges.length) { maxY = targetGraph .getCellById( maxBy(nodeWithEdges, (k) => { return targetGraph.getCellById(k).position().y; }), ) .position().y; minX = targetGraph .getCellById( minBy(nodeWithEdges, (k) => { return targetGraph.getCellById(k).position().x; }), ) .position().x; const maxX = targetGraph .getCellById( maxBy(nodeWithEdges, (k) => { return targetGraph.getCellById(k).position().x; }), ) .position().x; const yNodes = nodeWithEdges.filter((v) => { return Math.abs(targetGraph.getCellById(v).position().y - maxY) < 50; }); let referenceNode: any = targetGraph .getCell(maxBy(yNodes, (k) => targetGraph.getCellById(k).position().x)) ?.position(); num = Math.round(maxX / 320) || 1; alternateNum = Math.floor((4500 - (maxX + 100 - referenceNode.x)) / 280); rawEntity = getGridData(num, rawNodes); if (alternateNum >= 1) { const alternateNodes = take(nodeWithoutEdges, alternateNum); rawEntity = getGridData(num, drop(nodeWithoutEdges, alternateNum)); alternateNodes.forEach((id, index) => { const node = targetGraph.getCell(id); if (node) { const calculatedPosition = { x: referenceNode.x + 320 * index + 280, y: referenceNode.y }; node.position(calculatedPosition.x, calculatedPosition.y); const positionId = positions.find((v) => v.collectionName === node.id)?.id; updatePositionData.push({ id: positionId, x: calculatedPosition.x, y: calculatedPosition.y }); } }); } } else { num = 15; alternateNum = 0; rawEntity = getGridData(15, rawNodes); minX = 50; maxY = 50; } rawEntity.forEach((arr, row) => { arr.forEach((id, index) => { const node = targetGraph.getCell(id); const col = index % num; if (node) { const calculatedPosition = { x: col * 325 + minX, y: row * 300 + maxY + 300 }; node.position(calculatedPosition.x, calculatedPosition.y); const positionId = positions.find((v) => v.collectionName === node.id)?.id; updatePositionData.push({ id: positionId, x: calculatedPosition.x, y: calculatedPosition.y }); } }); }); edges.forEach((edge) => { optimizeEdge(edge); }); targetGraph.unfreeze(); targetGraph.positionCell(nodes[0], 'top-left', { padding: 100 }); targetGraph.updatePositionAction(updatePositionData, true); }; export const GraphDrawPage = React.memo(() => { let options = useContext(SchemaOptionsContext); const ctx = useContext(CollectionManagerContext); const api = useAPIClient(); const compile = useCompile(); const { t } = useTranslation('graphPositions'); const [collectionData, setCollectionData] = useState([]); const [collectionList, setCollectionList] = useState([]); const { refreshCM } = useCollectionManager(); const { data: { database }, } = useCurrentAppInfo(); const scope = { ...options?.scope }; const components = { ...options?.components }; const useSaveGraphPositionAction = async (data) => { await api.resource('graphPositions').create({ values: data }); await refreshPositions(); }; const useUpdatePositionAction = async (data, isbatch = false) => { if (isbatch) { await api.resource('graphPositions').update({ values: data, }); } else { await api.resource('graphPositions').update({ filter: { collectionName: data.collectionName }, values: { ...data }, }); } await refreshPositions(); }; const refreshPositions = async () => { const { data } = await api.resource('graphPositions').list({ paginate: false }); targetGraph.positions = data.data; return Promise.resolve(); }; const setTargetNode = (node) => { targetNode = node; if (node === 'destory') { refreshPositions(); } }; const refreshGM = async () => { const data = await refreshCM(); console.log(data); targetGraph.collections = data; targetGraph.updatePositionAction = useUpdatePositionAction; const currentNodes = targetGraph.getNodes(); setCollectionData(data); setCollectionList(data); if (!currentNodes.length) { renderInitGraphCollection(data); } else { renderDiffGraphCollection(data); } }; const initGraphCollections = () => { targetGraph = new Graph({ container: document.getElementById('container')!, moveThreshold: 0, scroller: { enabled: true, pannable: true, padding: { top: 0, left: 500, right: 300, bottom: 400 }, }, selecting: { enabled: false, multiple: true, rubberband: true, movable: true, className: 'node-selecting', modifiers: 'shift', }, minimap: { enabled: true, container: document.getElementById('graph-minimap'), width: 300, height: 250, padding: 10, graphOptions: { async: true, getCellView(cell) { if (cell.isNode()) { return SimpleNodeView; } }, createCellView(cell) { if (cell.isEdge()) { return null; } }, }, }, connecting: { anchor: { name: 'midSide', }, }, mousewheel: { enabled: true, modifiers: ['ctrl', 'meta'], }, snapline: { enabled: !0, }, keyboard: { enabled: false, }, clipboard: { enabled: false, }, interacting: { magnetConnectable: false, }, async: true, preventDefaultBlankAction: true, }); targetGraph.connectionType = ConnectionType.Both; targetGraph.direction = DirectionType.Target; targetGraph.cacheCollection = {}; Graph.registerPortLayout( 'erPortPosition', (portsPositionArgs) => { return portsPositionArgs.map((_, index) => { return { position: { x: 0, y: (index + 1) * LINE_HEIGHT, }, angle: 0, }; }); }, true, ); Graph.registerNode( 'er-rect', { inherit: 'react-shape', component: (node) => (
), ports: { groups: { list: { markup: [ { tagName: 'rect', selector: 'portBody', }, ], attrs: { portBody: { width: NODE_WIDTH, height: LINE_HEIGHT, strokeWidth: 1, // magnet: true, visibility: 'hidden', }, }, position: 'erPortPosition', }, }, }, body: { refWidth: 100, refHeight: 100, }, }, true, ); targetGraph.on('edge:mouseleave', ({ e, edge: targetEdge }) => { e.stopPropagation(); handleEdgeUnActive(targetEdge); }); targetGraph.on('node:moved', ({ e, node }) => { e.stopPropagation(); const connectEdges = targetGraph.getConnectedEdges(node); const currentPosition = node.position(); const oldPosition = targetGraph.positions.find((v) => v.collectionName === node.store.data.name); if (oldPosition) { (oldPosition.x !== currentPosition.x || oldPosition.y !== currentPosition.y) && useUpdatePositionAction({ collectionName: node.store.data.name, ...currentPosition, }); } else { useSaveGraphPositionAction({ collectionName: node.store.data.name, ...currentPosition, }); } connectEdges.forEach((edge) => { optimizeEdge(edge); }); }); targetGraph.on('cell:mouseenter', ({ e, cell, edge }) => { e.stopPropagation(); cell.toFront(); if (edge) { handleEdgeActive(edge); } }); targetGraph.on('blank:click', (e) => { if (targetGraph?.activeEdge) { handleEdgeUnActive(targetGraph?.activeEdge); } targetGraph.collapseNodes?.map((v) => { const node = targetGraph.getCell(Object.keys(v)[0]); Object.values(v)[0] && node.setData({ collapse: false }); }); targetGraph.cleanSelection(); }); targetGraph.on('node:selected', ({ e, node }) => { node.setProp({ select: true }); }); targetGraph.on('node:unselected', ({ e, node }) => { node.setProp({ select: false }); }); }; const handleEdgeUnActive = (targetEdge) => { targetGraph.activeEdge = null; const { m2m, connectionType } = targetEdge.store?.data; const m2mLineId = m2m?.find((v) => v !== targetEdge.id); const m2mEdge = targetGraph.getCellById(m2mLineId); const lightsOut = (edge) => { const targeNode = targetGraph.getCellById(edge.store.data.target.cell); const sourceNode = targetGraph.getCellById(edge.store.data.source.cell); targeNode.setProp({ targetPort: false, associated: null }); sourceNode.setProp({ sourcePort: false, associated: null }); edge.setAttrs({ line: { stroke: '#ddd', targetMarker: connectionType === ConnectionType.Inherit ? { name: 'classic', fill: '#ddd' } : null, }, }); edge.setLabels( edge.getLabels().map((v) => { return { ...v, attrs: { labelText: { ...v.attrs.labelText, fill: 'rgba(0, 0, 0, 0.3)', }, labelBody: { ...v.attrs.labelBody, stroke: '#ddd', }, }, }; }), ); }; lightsOut(targetEdge); m2mEdge && lightsOut(m2mEdge); }; const handleEdgeActive = (targetEdge) => { targetGraph.activeEdge = targetEdge; const { associated, m2m, connectionType } = targetEdge.store?.data; const m2mLineId = m2m?.find((v) => v !== targetEdge.id); const m2mEdge = targetGraph.getCellById(m2mLineId); const lightUp = (edge) => { edge.toFront(); edge.setAttrs({ line: { stroke: '#1890ff', strokeWidth: 1, textAnchor: 'middle', textVerticalAnchor: 'middle', sourceMarker: null, targetMarker: connectionType === ConnectionType.Inherit ? { name: 'classic', fill: '#1890ff' } : null, }, }); edge.setLabels( edge.getLabels().map((v) => { return { ...v, attrs: { labelText: { ...v.attrs.labelText, fill: '#1890ff', }, labelBody: { ...v.attrs.labelBody, stroke: '#1890ff', }, }, }; }), ); const targeNode = targetGraph.getCellById(edge.store.data.target.cell); const sourceNode = targetGraph.getCellById(edge.store.data.source.cell); targeNode.toFront(); sourceNode.toFront(); targeNode.setProp({ targetPort: edge.store.data.target.port, associated, }); sourceNode.setProp({ sourcePort: edge.store.data.source.port, associated, }); }; lightUp(targetEdge); m2mEdge && lightUp(m2mEdge); }; // 首次渲染 const renderInitGraphCollection = (rawData) => { const { nodesData, edgesData, inheritEdges } = formatData(rawData); targetGraph.data = { nodes: nodesData, edges: edgesData }; getNodes(nodesData); getEdges(edgesData); getEdges(inheritEdges); layout(useSaveGraphPositionAction); }; // 增量渲染 const renderDiffGraphCollection = (rawData) => { const { positions }: { positions: { x: number; y: number }[] } = targetGraph; const { nodesData, edgesData, inheritEdges } = formatData(rawData); const currentNodes = targetGraph.getNodes().map((v) => v.store.data); const totalEdges = targetGraph.getEdges().map((v) => v.store.data); const currentEdgesGroup = groupBy(totalEdges, (v) => { if (v.connectionType) { return 'currentInheritEdges'; } else { return 'currentRelateEdges'; } }); const diffNodes = getDiffNode(nodesData, currentNodes); const diffEdges = getDiffEdge(edgesData, currentEdgesGroup.currentRelateEdges || []); const diffInheritEdge = getDiffEdge(inheritEdges, currentEdgesGroup.currentInheritEdges || []); diffNodes.forEach(({ status, node, port }) => { const updateNode = targetGraph.getCellById(node.id); switch (status) { case 'add': const maxY = maxBy(positions, 'y').y; const yNodes = positions.filter((v) => { return Math.abs(v.y - maxY) < 100; }); let referenceNode: any = maxBy(yNodes, 'x'); let position; if (referenceNode.x > 4500) { const minX = minBy(positions, 'x').x; referenceNode = minBy(yNodes, 'x'); position = { x: minX, y: referenceNode.y + 400 }; } else { position = { x: referenceNode.x + 350, y: referenceNode.y }; } targetNode = targetGraph.addNode({ ...node, position, }); useSaveGraphPositionAction({ collectionName: node.name, ...position, }); targetGraph && targetGraph.positionCell(targetNode, 'top', { padding: 200 }); break; case 'insertPort': updateNode.insertPort(port.index, port.port); break; case 'deletePort': updateNode.removePort(port.id); break; case 'updateNode': updateNode.setProp({ title: node.title }); break; case 'delete': targetGraph.removeCell(node.id); default: return null; } }); const renderDiffEdges = (data) => { data.forEach(({ status, edge }) => { switch (status) { case 'add': const newEdge = targetGraph.addEdge({ ...edge, }); optimizeEdge(newEdge); break; case 'delete': targetGraph.removeCell(edge.id); break; default: return null; } }); }; setTimeout(() => { renderDiffEdges(diffEdges.concat(diffInheritEdge)); }); }; const handleSearchCollection = (e) => { const value = e.target.value.toLowerCase(); if (value) { const targetCollections = collectionData.filter((v) => { const collectionTitle = compile(v.title).toLowerCase(); return collectionTitle.includes(value); }); setCollectionList(targetCollections); } else { setCollectionList(collectionData); } }; // 处理不同方向的继承关系表 const hanleHighlightInheritedNode = (key, direction) => { if (direction === DirectionType.Target) { const INodes = getInheritCollections(targetGraph.collections, key); INodes.forEach((v) => { targetGraph.getCellById(v)?.setAttrs({ hightLight: true, direction, connectionType: ConnectionType.Inherit, }); }); } else { const INodes = getChildrenCollections(targetGraph.collections, key); INodes.forEach((v) => { targetGraph.getCellById(v.name)?.setAttrs({ hightLight: true, direction, connectionType: ConnectionType.Inherit, }); }); } }; // target index entity relation const handelTargetIndexEntity: any = (key) => { const node = targetGraph.getCellById(key); targetGraph.cacheCollection[key] = true; const connectedEdges = targetGraph.getConnectedEdges(node); let visibleEdges = connectedEdges.filter((v) => !v.store.data?.connectionType && v.getTargetCellId() === key); visibleEdges.forEach((v) => { if (v.store.data.m2m) { v.store.data.m2m.forEach((i) => { const m2mEdge = targetGraph.getCellById(i); if (m2mEdge.getTargetCellId() === key) { const sourceId = m2mEdge.getSourceCellId(); const node = targetGraph.getCellById(sourceId); if (!node.store.data.attrs?.hightLight) { node.setAttrs({ hightLight: true, direction: DirectionType.Target, connectionType: ConnectionType.Entity, }); handelTargetIndexEntity(sourceId); } } }); } const sourceId = v.getSourceCellId(); const node = targetGraph.getCellById(sourceId); if (!node.store.data.attrs?.hightLight) { node.setAttrs({ hightLight: true, direction: DirectionType.Target, connectionType: ConnectionType.Entity, }); handelTargetIndexEntity(sourceId); } }); }; // source index entity relation const handelSourceIndexEntity: any = (key) => { const node = targetGraph.getCellById(key); const connectedEdges = targetGraph.getConnectedEdges(node); const visibleEdges = connectedEdges.filter((v) => !v.store.data?.connectionType && v.getSourceCellId() === key); visibleEdges.forEach((v) => { if (v.store.data.m2m) { v.store.data.m2m.forEach((i) => { const m2mEdge = targetGraph.getCellById(i); if (m2mEdge.getSourceCellId() === key) { const targetId = m2mEdge.getTargetCellId(); const node = targetGraph.getCellById(targetId); if (!node.store.data.attrs?.hightLight) { node.setAttrs({ hightLight: true, direction: DirectionType.Source, connectionType: ConnectionType.Entity, }); handelSourceIndexEntity(targetId); } } }); } const targetId = v.getTargetCellId(); const node = targetGraph.getCellById(targetId); if (!node.store.data.attrs?.hightLight) { node.setAttrs({ hightLight: true, direction: DirectionType.Source, connectionType: ConnectionType.Entity, }); handelSourceIndexEntity(targetId); } }); }; // 处理不同方向的实体关系表 const handleHighlightRelationNodes = (nodekey, direction) => { if (direction === DirectionType.Target) { handelTargetIndexEntity(nodekey); } else { handelSourceIndexEntity(nodekey); } }; const handleCleanHighlight = (key?, currentDirection?, currentConnectionType?) => { const nodes = targetGraph.getNodes().filter((v) => v.store.data.attrs?.hightLight); const length = nodes.length; for (let i = 0; i < length; i++) { const { direction, connectionType } = nodes[i].getAttrs(); const filterFlag = nodes[i].id !== key; const directionFlag = key && targetGraph.filterConfig?.key === key ? direction !== currentDirection : true; const renltionshipFlag = key && targetGraph.filterConfig?.key === key ? connectionType !== currentConnectionType : true; if (nodes[i].id !== key) { setTimeout(() => { filterFlag && (directionFlag || renltionshipFlag) && nodes[i].setAttrs({ hightLight: false, }); }, 0); } } }; const handleFiterCollections = (value) => { const { connectionType, direction, filterConfig } = targetGraph; const directionBothFlag1 = value === filterConfig?.key && direction === DirectionType.Both; const relationshipBothFlag = value === filterConfig?.key && (connectionType === ConnectionType.Both || connectionType === filterConfig.connectionType); if (value) { (!directionBothFlag1 || !relationshipBothFlag) && handleCleanHighlight(value, direction, connectionType); targetNode = targetGraph.getCellById(value); targetGraph.positionCell(targetNode, 'center', { padding: 0 }); targetNode.setAttrs({ hightLight: true, connectionType: connectionType, }); setTimeout(() => { if ([ConnectionType.Entity, ConnectionType.Both].includes(connectionType)) { if (direction === DirectionType.Both) { handleHighlightRelationNodes(value, DirectionType.Target); handleHighlightRelationNodes(value, DirectionType.Source); } else { direction === DirectionType.Target && handleHighlightRelationNodes(value, direction); direction === DirectionType.Source && handleHighlightRelationNodes(value, direction); } } if ([ConnectionType.Inherit, ConnectionType.Both].includes(connectionType)) { if (direction === DirectionType.Both) { hanleHighlightInheritedNode(value, DirectionType.Target); hanleHighlightInheritedNode(value, DirectionType.Source); } else { hanleHighlightInheritedNode(value, direction); } } targetGraph.filterConfig = { key: value, direction: direction, connectionType, }; }, 0); } else { handleCleanHighlight(); } }; const handleSetRelationshipType = (type) => { handleSetEdgeVisible(type); }; const handleSetEdgeVisible = (type) => { targetNode = null; const edges = targetGraph.getEdges(); edges.forEach((v) => { const { store: { data: { connectionType }, }, } = v; if (type === ConnectionType.Entity) { if (connectionType) { v.setVisible(false); } else { v.setVisible(true); } } else if (type === ConnectionType.Inherit) { if (!connectionType) { v.setVisible(false); } else { v.setVisible(true); } } else { v.setVisible(true); } }); }; useLayoutEffect(() => { initGraphCollections(); return () => { targetGraph.off('cell:mouseenter'); targetGraph.off('edge:mouseleave'); targetGraph.off('node:moved'); targetGraph.off('blank:click'); targetGraph = null; targetNode = null; }; }, []); useEffect(() => { refreshPositions().then(() => { refreshGM(); }); }, []); const loadCollections = async () => { return targetGraph.collections?.map((collection: any) => ({ label: compile(collection.title), value: collection.name, })); }; return (
( {collectionList.map((v) => { return ( { if (e.key !== selectedKeys[0]) { setSelectKey([e.key]); handleFiterCollections(e.key); } else { targetGraph.filterConfig = null; handleFiterCollections(false); setSelectKey([]); } }} > {compile(v.title)} ); })}
); return ( ); }, 'x-component-props': { icon: 'MenuOutlined', useAction: () => { return { run() {}, }; }, }, }, autoLayout: { type: 'void', 'x-component': 'Action', 'x-component-props': { component: forwardRef(() => { return ( ); }), useAction: () => { return { run() {}, }; }, }, }, connectionType: { type: 'void', 'x-component': () => { const menuItems = [ { key: ConnectionType.Both, label: 'All relationships', }, { key: ConnectionType.Entity, label: 'Entity relationship only', }, { key: ConnectionType.Inherit, label: 'Inheritance relationship only', }, ]; const content = (
{menuItems.map((v) => { return ( { targetGraph.connectionType = v.key; const { filterConfig } = targetGraph; filterConfig && handleFiterCollections(filterConfig.key); handleSetRelationshipType(v.key); }} > {t(v.label)} ); })}
); return ( ); }, 'x-component-props': { icon: 'MenuOutlined', useAction: () => { return { run() {}, }; }, }, }, direction: { type: 'void', 'x-component': () => { const menuItems = [ { key: DirectionType.Both, label: 'All directions', }, { key: DirectionType.Target, label: 'Target index', }, { key: DirectionType.Source, label: 'Source index', }, ]; const content = (
{menuItems.map((v) => { return ( { targetGraph.direction = v.key; const { filterConfig } = targetGraph; if (filterConfig) { handleFiterCollections(filterConfig.key); } }} > {t(v.label)} ); })}
); return ( ); }, }, selectMode: { type: 'void', 'x-component': () => { return ( { targetGraph.toggleSelection(); }} /> ); }, }, }, }, }, }, }, }} scope={{ useAsyncDataSource, loadCollections, useCreateActionAndRefreshCM: () => useCreateActionAndRefreshCM(setTargetNode), enableInherits: database?.dialect === 'postgres', }} />
); });