@nocobase/plugin-graph-collection-manager 0.10.0-alpha.5 → 0.11.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/client.d.ts +2 -3
  2. package/client.js +1 -30
  3. package/lib/client/GraphCollectionProvider.js +1 -14
  4. package/lib/client/GraphCollectionShortcut.js +2 -9
  5. package/lib/client/GraphDrawPage.js +44 -45
  6. package/lib/client/action-hooks.js +6 -7
  7. package/lib/client/components/AddFieldAction.d.ts +2 -1
  8. package/lib/client/components/AddFieldAction.js +3 -1
  9. package/lib/client/components/EditCollectionAction.js +1 -8
  10. package/lib/client/components/Entity.js +19 -24
  11. package/lib/client/components/FieldSummary.d.ts +1 -1
  12. package/lib/client/components/FieldSummary.js +9 -16
  13. package/lib/client/index.d.ts +5 -1
  14. package/lib/client/index.js +21 -6
  15. package/lib/client/style.js +12 -12
  16. package/lib/client/utils.d.ts +2 -1
  17. package/lib/client/utils.js +15 -11
  18. package/package.json +27 -6
  19. package/server.d.ts +2 -3
  20. package/server.js +1 -30
  21. package/src/client/GraphCollectionProvider.tsx +33 -0
  22. package/src/client/GraphCollectionShortcut.tsx +141 -0
  23. package/src/client/GraphDrawPage.tsx +1375 -0
  24. package/src/client/action-hooks.tsx +232 -0
  25. package/src/client/components/AddCollectionAction.tsx +28 -0
  26. package/src/client/components/AddFieldAction.tsx +37 -0
  27. package/src/client/components/CollectionNodeProvder.tsx +28 -0
  28. package/src/client/components/EditCollectionAction.tsx +33 -0
  29. package/src/client/components/EditFieldAction.tsx +30 -0
  30. package/src/client/components/Entity.tsx +510 -0
  31. package/src/client/components/FieldSummary.tsx +42 -0
  32. package/src/client/components/OverrideFieldAction.tsx +30 -0
  33. package/src/client/components/ViewFieldAction.tsx +12 -0
  34. package/src/client/components/ViewNode.tsx +22 -0
  35. package/src/client/index.tsx +10 -0
  36. package/src/client/locale/en-US.ts +15 -0
  37. package/src/client/locale/es-ES.ts +15 -0
  38. package/src/client/locale/index.ts +3 -0
  39. package/src/client/locale/ja-JP.ts +13 -0
  40. package/src/client/locale/pt-BR.ts +15 -0
  41. package/src/client/locale/zh-CN.ts +16 -0
  42. package/src/client/style.tsx +206 -0
  43. package/src/client/utils.tsx +548 -0
  44. package/src/index.ts +1 -0
  45. package/src/server/actions/.gitkeep +0 -0
  46. package/src/server/collections/.gitkeep +0 -0
  47. package/src/server/collections/graphPositions.ts +22 -0
  48. package/src/server/index.ts +13 -0
  49. package/src/server/models/.gitkeep +0 -0
  50. package/src/server/repositories/.gitkeep +0 -0
@@ -0,0 +1,1375 @@
1
+ import {
2
+ ApartmentOutlined,
3
+ FullscreenExitOutlined,
4
+ FullscreenOutlined,
5
+ LineHeightOutlined,
6
+ MenuOutlined,
7
+ ShareAltOutlined,
8
+ } from '@ant-design/icons';
9
+ import { Graph } from '@antv/x6';
10
+ import '@antv/x6-react-shape';
11
+ import { SchemaOptionsContext } from '@formily/react';
12
+ import {
13
+ APIClientProvider,
14
+ collection,
15
+ CollectionCategroriesContext,
16
+ CollectionCategroriesProvider,
17
+ CollectionManagerContext,
18
+ CollectionManagerProvider,
19
+ css,
20
+ CurrentAppInfoContext,
21
+ cx,
22
+ SchemaComponent,
23
+ SchemaComponentOptions,
24
+ Select,
25
+ useAPIClient,
26
+ useCollectionManager,
27
+ useCompile,
28
+ useCurrentAppInfo,
29
+ } from '@nocobase/client';
30
+ import { lodash } from '@nocobase/utils/client';
31
+ import { useFullscreen } from 'ahooks';
32
+ import { Button, Input, Layout, Menu, Popover, Switch, Tooltip } from 'antd';
33
+ import dagre from 'dagre';
34
+ import React, { createContext, forwardRef, useContext, useEffect, useLayoutEffect, useState } from 'react';
35
+ import { useAsyncDataSource, useCreateActionAndRefreshCM } from './action-hooks';
36
+ import { AddCollectionAction } from './components/AddCollectionAction';
37
+ import Entity from './components/Entity';
38
+ import { SimpleNodeView } from './components/ViewNode';
39
+ import { collectionListClass, graphCollectionContainerClass } from './style';
40
+ import {
41
+ formatData,
42
+ getChildrenCollections,
43
+ getDiffEdge,
44
+ getDiffNode,
45
+ getInheritCollections,
46
+ getPopupContainer,
47
+ useGCMTranslation,
48
+ } from './utils';
49
+
50
+ const { drop, groupBy, last, maxBy, minBy, take } = lodash;
51
+
52
+ const LINE_HEIGHT = 40;
53
+ const NODE_WIDTH = 250;
54
+ let targetGraph;
55
+ let targetNode;
56
+ const dir = 'TB'; // LR RL TB BT 横排
57
+
58
+ export enum DirectionType {
59
+ Both = 'both',
60
+ Target = 'target',
61
+ Source = 'source',
62
+ }
63
+
64
+ export enum ConnectionType {
65
+ Both = 'both',
66
+ Inherit = 'inherited',
67
+ Entity = 'entity',
68
+ }
69
+ const getGridData = (num, arr) => {
70
+ const newArr = [];
71
+ while (arr.length > 0 && num) {
72
+ newArr.push(arr.splice(0, num));
73
+ }
74
+ return newArr;
75
+ };
76
+
77
+ //初始布局
78
+ async function layout(createPositions) {
79
+ const { positions } = targetGraph;
80
+ let graphPositions = [];
81
+ const nodes: any[] = targetGraph.getNodes();
82
+ const edges = targetGraph.getEdges();
83
+ const g: any = new dagre.graphlib.Graph();
84
+ g.setGraph({ rankdir: dir, nodesep: 50, edgesep: 50, rankSep: 50, align: 'DL', controlPoints: true });
85
+ g.setDefaultEdgeLabel(() => ({}));
86
+ nodes.forEach((node, i) => {
87
+ const width = NODE_WIDTH;
88
+ const height = node.getPorts().length * 32 + 30;
89
+ g.setNode(node.id, { width, height });
90
+ });
91
+ dagre.layout(g);
92
+ targetGraph.freeze();
93
+ const dNodes = getGridData(15, g.nodes());
94
+ dNodes.forEach((arr, row) => {
95
+ arr.forEach((id, index) => {
96
+ const node = targetGraph.getCell(id);
97
+ const col = index % 15;
98
+ if (node) {
99
+ const targetPosition =
100
+ (positions &&
101
+ positions.find((v) => {
102
+ return v.collectionName === node.store.data.name;
103
+ })) ||
104
+ {};
105
+ const calculatedPosition = { x: col * 325 + 50, y: row * 400 + 60 };
106
+ node.position(targetPosition.x || calculatedPosition.x, targetPosition.y || calculatedPosition.y);
107
+ if (positions && !positions.find((v) => v.collectionName === node.store.data.name)) {
108
+ // 位置表中没有的表都自动保存
109
+ graphPositions.push({
110
+ collectionName: node.store.data.name,
111
+ x: calculatedPosition.x,
112
+ y: calculatedPosition.y,
113
+ });
114
+ }
115
+ }
116
+ });
117
+ });
118
+ edges.forEach((edge) => {
119
+ optimizeEdge(edge);
120
+ });
121
+ targetGraph.unfreeze();
122
+ if (targetNode) {
123
+ typeof targetNode === 'string'
124
+ ? targetGraph.positionCell(last(nodes), 'top', { padding: 100 })
125
+ : targetGraph.positionCell(targetNode, 'top', { padding: 100 });
126
+ } else {
127
+ targetGraph.positionCell(nodes[0], 'top-left', { padding: 100 });
128
+ }
129
+ if (graphPositions.length > 0) {
130
+ await createPositions(graphPositions);
131
+ graphPositions = [];
132
+ }
133
+ }
134
+
135
+ function optimizeEdge(edge) {
136
+ const {
137
+ store: {
138
+ data: { connectionType },
139
+ },
140
+ } = edge;
141
+ const source = edge.getSource();
142
+ const target = edge.getTarget();
143
+ const sorceNodeX = targetGraph.getCell(source.cell).position().x;
144
+ const targeNodeX = targetGraph.getCell(target.cell).position().x;
145
+ const leftAnchor = connectionType
146
+ ? {
147
+ name: 'topLeft',
148
+ args: {
149
+ dy: -20,
150
+ },
151
+ }
152
+ : {
153
+ name: 'left',
154
+ };
155
+ const rightAnchor = connectionType
156
+ ? {
157
+ name: 'topRight',
158
+ args: {
159
+ dy: -20,
160
+ },
161
+ }
162
+ : 'right';
163
+ const router = connectionType ? 'normal' : 'er';
164
+ const vertices = edge.getVertices();
165
+ vertices.forEach(() => {
166
+ return edge.removeVertexAt(0);
167
+ });
168
+ if (sorceNodeX - 100 > targeNodeX) {
169
+ edge.setSource({
170
+ cell: source.cell,
171
+ port: source.port,
172
+ anchor: leftAnchor,
173
+ });
174
+ edge.setTarget({
175
+ cell: target.cell,
176
+ port: target.port,
177
+ anchor: rightAnchor,
178
+ });
179
+ edge.setRouter(router, {
180
+ direction: 'H',
181
+ });
182
+ } else if (Math.abs(sorceNodeX - targeNodeX) < 100) {
183
+ const sourceCell = targetGraph.getCell(source.cell);
184
+ const targetCell = targetGraph.getCell(target.cell);
185
+ edge.setSource({
186
+ cell: source.cell,
187
+ port: source.port,
188
+ anchor: leftAnchor,
189
+ });
190
+ edge.setTarget({
191
+ cell: target.cell,
192
+ port: target.port,
193
+ anchor: leftAnchor,
194
+ });
195
+ if (connectionType) {
196
+ edge.setVertices([
197
+ { x: sourceCell.position().x - 30, y: sourceCell.position().y + 20 },
198
+ { x: targetCell.position().x - 30, y: targetCell.position().y + 20 },
199
+ ]);
200
+ edge.setRouter('normal');
201
+ } else {
202
+ edge.setRouter('oneSide', { side: 'left' });
203
+ }
204
+ } else {
205
+ edge.setSource({
206
+ cell: source.cell,
207
+ port: source.port,
208
+ anchor: rightAnchor,
209
+ });
210
+ edge.setTarget({
211
+ cell: target.cell,
212
+ port: target.port,
213
+ anchor: leftAnchor,
214
+ });
215
+ edge.setRouter(router, {
216
+ direction: 'H',
217
+ });
218
+ }
219
+ }
220
+
221
+ function getNodes(nodes) {
222
+ targetGraph.addNodes(nodes);
223
+ }
224
+
225
+ function getEdges(edges) {
226
+ edges.forEach((item) => {
227
+ if (item.source && item.target) {
228
+ targetGraph.addEdge({
229
+ ...item,
230
+ connector: {
231
+ name: 'normal',
232
+ zIndex: 1000,
233
+ },
234
+ });
235
+ }
236
+ });
237
+ }
238
+
239
+ const CollapsedContext = createContext<any>({});
240
+ const formatNodeData = () => {
241
+ const layoutNodes = [];
242
+ const edges = targetGraph.getEdges();
243
+ const nodes = targetGraph.getNodes();
244
+ edges.forEach((edge) => {
245
+ layoutNodes.push(edge.getSourceCellId());
246
+ layoutNodes.push(edge.getTargetCellId());
247
+ });
248
+ const nodeGroup = groupBy(nodes, (v) => {
249
+ if (layoutNodes.includes(v.id)) {
250
+ return 'linkNodes';
251
+ } else {
252
+ return 'rawNodes';
253
+ }
254
+ });
255
+ return nodeGroup;
256
+ };
257
+ //自动布局
258
+ const handelResetLayout = () => {
259
+ const { linkNodes = [], rawNodes } = formatNodeData();
260
+ const { positions } = targetGraph;
261
+ const nodes = linkNodes.concat(rawNodes);
262
+ const edges = targetGraph.getEdges();
263
+ const g = new dagre.graphlib.Graph();
264
+ let alternateNum;
265
+ let rawEntity;
266
+ let num;
267
+ let minX;
268
+ let maxY;
269
+ const updatePositionData = [];
270
+ g.setGraph({ rankdir: 'TB', nodesep: 50, edgesep: 50, rankSep: 50, align: 'DL', controlPoints: true });
271
+ const width = 250;
272
+ const height = 400;
273
+ nodes.forEach((node) => {
274
+ g.setNode(node.id, { width, height });
275
+ });
276
+ edges.forEach((edge) => {
277
+ const source = edge.getSource();
278
+ const target = edge.getTarget();
279
+ g.setEdge(source.cell, target.cell, {});
280
+ });
281
+ dagre.layout(g);
282
+ targetGraph.freeze();
283
+ const gNodes = g.nodes();
284
+ const nodeWithEdges = take(gNodes, linkNodes.length);
285
+ const nodeWithoutEdges = drop(gNodes, linkNodes.length);
286
+ nodeWithEdges.forEach((id) => {
287
+ const node = targetGraph.getCell(id);
288
+ const positionId = positions.find((v) => v.collectionName === node.id)?.id;
289
+ if (node) {
290
+ const pos = g.node(id);
291
+ updatePositionData.push({ id: positionId, x: pos.x, y: pos.y });
292
+ node.position(pos?.x, pos?.y);
293
+ }
294
+ });
295
+ if (nodeWithEdges.length) {
296
+ maxY = targetGraph
297
+ .getCellById(
298
+ maxBy(nodeWithEdges, (k) => {
299
+ return targetGraph.getCellById(k).position().y;
300
+ }),
301
+ )
302
+ .position().y;
303
+ minX = targetGraph
304
+ .getCellById(
305
+ minBy(nodeWithEdges, (k) => {
306
+ return targetGraph.getCellById(k).position().x;
307
+ }),
308
+ )
309
+ .position().x;
310
+ const maxX = targetGraph
311
+ .getCellById(
312
+ maxBy(nodeWithEdges, (k) => {
313
+ return targetGraph.getCellById(k).position().x;
314
+ }),
315
+ )
316
+ .position().x;
317
+ const yNodes = nodeWithEdges.filter((v) => {
318
+ return Math.abs(targetGraph.getCellById(v).position().y - maxY) < 50;
319
+ });
320
+ const referenceNode: any = targetGraph
321
+ .getCell(maxBy(yNodes, (k) => targetGraph.getCellById(k).position().x))
322
+ ?.position();
323
+ num = Math.round(maxX / 320) || 1;
324
+ alternateNum = Math.floor((4500 - (maxX + 100 - referenceNode.x)) / 280);
325
+ rawEntity = getGridData(num, rawNodes);
326
+ if (alternateNum >= 1) {
327
+ const alternateNodes = take(nodeWithoutEdges, alternateNum);
328
+ rawEntity = getGridData(num, drop(nodeWithoutEdges, alternateNum));
329
+ alternateNodes.forEach((id, index) => {
330
+ const node = targetGraph.getCell(id);
331
+ if (node) {
332
+ const calculatedPosition = { x: referenceNode.x + 320 * index + 280, y: referenceNode.y };
333
+ node.position(calculatedPosition.x, calculatedPosition.y);
334
+ const positionId = positions.find((v) => v.collectionName === node.id)?.id;
335
+ updatePositionData.push({ id: positionId, x: calculatedPosition.x, y: calculatedPosition.y });
336
+ }
337
+ });
338
+ }
339
+ } else {
340
+ num = 15;
341
+ alternateNum = 0;
342
+ rawEntity = getGridData(15, rawNodes);
343
+ minX = 50;
344
+ maxY = 50;
345
+ }
346
+ rawEntity.forEach((arr, row) => {
347
+ arr.forEach((id, index) => {
348
+ const node = targetGraph.getCell(id);
349
+ const col = index % num;
350
+ if (node) {
351
+ const calculatedPosition = { x: col * 325 + minX, y: row * 300 + maxY + 300 };
352
+ node.position(calculatedPosition.x, calculatedPosition.y);
353
+ const positionId = positions.find((v) => v.collectionName === node.id)?.id;
354
+ updatePositionData.push({ id: positionId, x: calculatedPosition.x, y: calculatedPosition.y });
355
+ }
356
+ });
357
+ });
358
+ edges.forEach((edge) => {
359
+ optimizeEdge(edge);
360
+ });
361
+ targetGraph.unfreeze();
362
+ targetGraph.positionCell(nodes[0], 'top-left', { padding: 100 });
363
+ targetGraph.updatePositionAction(updatePositionData, true);
364
+ };
365
+
366
+ export const GraphDrawPage = React.memo(() => {
367
+ const options = useContext(SchemaOptionsContext);
368
+ const ctx = useContext(CollectionManagerContext);
369
+ const api = useAPIClient();
370
+ const compile = useCompile();
371
+ const { t } = useGCMTranslation();
372
+ const [collectionData, setCollectionData] = useState<any>([]);
373
+ const [collectionList, setCollectionList] = useState<any>([]);
374
+ const { refreshCM } = useCollectionManager();
375
+ const {
376
+ data: { database },
377
+ } = useCurrentAppInfo();
378
+ const categoryCtx = useContext(CollectionCategroriesContext);
379
+ const scope = { ...options?.scope };
380
+ const components = { ...options?.components };
381
+ const useSaveGraphPositionAction = async (data) => {
382
+ await api.resource('graphPositions').create({ values: data });
383
+ await refreshPositions();
384
+ };
385
+ const useUpdatePositionAction = async (data, isbatch = false) => {
386
+ if (isbatch) {
387
+ await api.resource('graphPositions').update({
388
+ values: data,
389
+ });
390
+ } else {
391
+ await api.resource('graphPositions').update({
392
+ filter: { collectionName: data.collectionName },
393
+ values: { ...data },
394
+ });
395
+ }
396
+ await refreshPositions();
397
+ };
398
+ const refreshPositions = async () => {
399
+ const { data } = await api.resource('graphPositions').list({ paginate: false });
400
+ targetGraph.positions = data.data;
401
+ return Promise.resolve();
402
+ };
403
+ const setTargetNode = (node) => {
404
+ targetNode = node;
405
+ if (node === 'destory') {
406
+ refreshPositions();
407
+ }
408
+ };
409
+ const refreshGM = async () => {
410
+ const data = await refreshCM();
411
+ targetGraph.collections = data;
412
+ targetGraph.updatePositionAction = useUpdatePositionAction;
413
+ const currentNodes = targetGraph.getNodes();
414
+ setCollectionData(data);
415
+ setCollectionList(data);
416
+ if (!currentNodes.length) {
417
+ renderInitGraphCollection(data);
418
+ } else {
419
+ renderDiffGraphCollection(data);
420
+ }
421
+ };
422
+ const initGraphCollections = () => {
423
+ targetGraph = new Graph({
424
+ container: document.getElementById('container')!,
425
+ moveThreshold: 0,
426
+ scroller: {
427
+ enabled: true,
428
+ pannable: true,
429
+ padding: { top: 0, left: 500, right: 300, bottom: 400 },
430
+ },
431
+ selecting: {
432
+ enabled: false,
433
+ multiple: true,
434
+ rubberband: true,
435
+ movable: true,
436
+ className: 'node-selecting',
437
+ modifiers: 'shift',
438
+ },
439
+ minimap: {
440
+ enabled: true,
441
+ container: document.getElementById('graph-minimap'),
442
+ width: 300,
443
+ height: 250,
444
+ padding: 10,
445
+ graphOptions: {
446
+ async: true,
447
+ getCellView(cell) {
448
+ if (cell.isNode()) {
449
+ return SimpleNodeView;
450
+ }
451
+ },
452
+ createCellView(cell) {
453
+ if (cell.isEdge()) {
454
+ return null;
455
+ }
456
+ },
457
+ },
458
+ },
459
+ connecting: {
460
+ anchor: {
461
+ name: 'midSide',
462
+ },
463
+ },
464
+ mousewheel: {
465
+ enabled: true,
466
+ modifiers: ['ctrl', 'meta'],
467
+ },
468
+ snapline: {
469
+ enabled: !0,
470
+ },
471
+ keyboard: {
472
+ enabled: false,
473
+ },
474
+ clipboard: {
475
+ enabled: false,
476
+ },
477
+ interacting: {
478
+ magnetConnectable: false,
479
+ },
480
+ async: true,
481
+ preventDefaultBlankAction: true,
482
+ });
483
+ targetGraph.connectionType = ConnectionType.Both;
484
+ targetGraph.direction = DirectionType.Target;
485
+ targetGraph.cacheCollection = {};
486
+ Graph.registerPortLayout(
487
+ 'erPortPosition',
488
+ (portsPositionArgs) => {
489
+ return portsPositionArgs.map((_, index) => {
490
+ return {
491
+ position: {
492
+ x: 0,
493
+ y: (index + 1) * LINE_HEIGHT,
494
+ },
495
+ angle: 0,
496
+ };
497
+ });
498
+ },
499
+ true,
500
+ );
501
+ Graph.registerNode(
502
+ 'er-rect',
503
+ {
504
+ inherit: 'react-shape',
505
+ component: (node) => (
506
+ <CurrentAppInfoContext.Provider value={database}>
507
+ <APIClientProvider apiClient={api}>
508
+ <SchemaComponentOptions inherit scope={scope} components={components}>
509
+ <CollectionCategroriesProvider {...categoryCtx}>
510
+ <CollectionManagerProvider
511
+ collections={targetGraph?.collections}
512
+ refreshCM={refreshGM}
513
+ interfaces={ctx.interfaces}
514
+ >
515
+ <div style={{ height: 'auto' }}>
516
+ <Entity node={node} setTargetNode={setTargetNode} targetGraph={targetGraph} />
517
+ </div>
518
+ </CollectionManagerProvider>
519
+ </CollectionCategroriesProvider>
520
+ </SchemaComponentOptions>
521
+ </APIClientProvider>
522
+ </CurrentAppInfoContext.Provider>
523
+ ),
524
+ ports: {
525
+ groups: {
526
+ list: {
527
+ markup: [
528
+ {
529
+ tagName: 'rect',
530
+ selector: 'portBody',
531
+ },
532
+ ],
533
+ attrs: {
534
+ portBody: {
535
+ width: NODE_WIDTH,
536
+ height: LINE_HEIGHT,
537
+ strokeWidth: 1,
538
+ // magnet: true,
539
+ visibility: 'hidden',
540
+ },
541
+ },
542
+ position: 'erPortPosition',
543
+ },
544
+ },
545
+ },
546
+ body: {
547
+ refWidth: 100,
548
+ refHeight: 100,
549
+ },
550
+ },
551
+ true,
552
+ );
553
+ targetGraph.on('edge:mouseleave', ({ e, edge: targetEdge }) => {
554
+ e.stopPropagation();
555
+ handleEdgeUnActive(targetEdge);
556
+ });
557
+ targetGraph.on('node:moved', ({ e, node }) => {
558
+ e.stopPropagation();
559
+ const connectEdges = targetGraph.getConnectedEdges(node);
560
+ const currentPosition = node.position();
561
+ const oldPosition = targetGraph.positions.find((v) => v.collectionName === node.store.data.name);
562
+ if (oldPosition) {
563
+ (oldPosition.x !== currentPosition.x || oldPosition.y !== currentPosition.y) &&
564
+ useUpdatePositionAction({
565
+ collectionName: node.store.data.name,
566
+ ...currentPosition,
567
+ });
568
+ } else {
569
+ useSaveGraphPositionAction({
570
+ collectionName: node.store.data.name,
571
+ ...currentPosition,
572
+ });
573
+ }
574
+ connectEdges.forEach((edge) => {
575
+ optimizeEdge(edge);
576
+ });
577
+ });
578
+ targetGraph.on('cell:mouseenter', ({ e, cell, edge }) => {
579
+ e.stopPropagation();
580
+ cell.toFront();
581
+ if (edge) {
582
+ handleEdgeActive(edge);
583
+ }
584
+ });
585
+ targetGraph.on('blank:click', (e) => {
586
+ if (targetGraph?.activeEdge) {
587
+ handleEdgeUnActive(targetGraph?.activeEdge);
588
+ }
589
+ targetGraph.collapseNodes?.map((v) => {
590
+ const node = targetGraph.getCell(Object.keys(v)[0]);
591
+ Object.values(v)[0] && node.setData({ collapse: false });
592
+ });
593
+ targetGraph.cleanSelection();
594
+ });
595
+ targetGraph.on('node:selected', ({ e, node }) => {
596
+ node.setProp({ select: true });
597
+ });
598
+ targetGraph.on('node:unselected', ({ e, node }) => {
599
+ node.setProp({ select: false });
600
+ });
601
+ };
602
+
603
+ const handleEdgeUnActive = (targetEdge) => {
604
+ targetGraph.activeEdge = null;
605
+ const { m2m, connectionType } = targetEdge.store?.data;
606
+ const m2mLineId = m2m?.find((v) => v !== targetEdge.id);
607
+ const m2mEdge = targetGraph.getCellById(m2mLineId);
608
+ const lightsOut = (edge) => {
609
+ const targeNode = targetGraph.getCellById(edge.store.data.target.cell);
610
+ const sourceNode = targetGraph.getCellById(edge.store.data.source.cell);
611
+ targeNode.setProp({ targetPort: false, associated: null });
612
+ sourceNode.setProp({ sourcePort: false, associated: null });
613
+ edge.setAttrs({
614
+ line: {
615
+ stroke: '#ddd',
616
+ targetMarker: connectionType === ConnectionType.Inherit ? { name: 'classic', fill: '#ddd' } : null,
617
+ },
618
+ });
619
+ edge.setLabels(
620
+ edge.getLabels().map((v) => {
621
+ return {
622
+ ...v,
623
+ attrs: {
624
+ labelText: {
625
+ ...v.attrs.labelText,
626
+ fill: 'rgba(0, 0, 0, 0.3)',
627
+ },
628
+ labelBody: {
629
+ ...v.attrs.labelBody,
630
+ stroke: '#ddd',
631
+ },
632
+ },
633
+ };
634
+ }),
635
+ );
636
+ };
637
+ lightsOut(targetEdge);
638
+ m2mEdge && lightsOut(m2mEdge);
639
+ };
640
+ const handleEdgeActive = (targetEdge) => {
641
+ targetGraph.activeEdge = targetEdge;
642
+ const { associated, m2m, connectionType } = targetEdge.store?.data;
643
+ const m2mLineId = m2m?.find((v) => v !== targetEdge.id);
644
+ const m2mEdge = targetGraph.getCellById(m2mLineId);
645
+ const lightUp = (edge) => {
646
+ edge.toFront();
647
+ edge.setAttrs({
648
+ line: {
649
+ stroke: '#1890ff',
650
+ strokeWidth: 1,
651
+ textAnchor: 'middle',
652
+ textVerticalAnchor: 'middle',
653
+ sourceMarker: null,
654
+ targetMarker: connectionType === ConnectionType.Inherit ? { name: 'classic', fill: '#1890ff' } : null,
655
+ },
656
+ });
657
+ edge.setLabels(
658
+ edge.getLabels().map((v) => {
659
+ return {
660
+ ...v,
661
+ attrs: {
662
+ labelText: {
663
+ ...v.attrs.labelText,
664
+ fill: '#1890ff',
665
+ },
666
+ labelBody: {
667
+ ...v.attrs.labelBody,
668
+
669
+ stroke: '#1890ff',
670
+ },
671
+ },
672
+ };
673
+ }),
674
+ );
675
+ const targeNode = targetGraph.getCellById(edge.store.data.target.cell);
676
+ const sourceNode = targetGraph.getCellById(edge.store.data.source.cell);
677
+ targeNode.toFront();
678
+ sourceNode.toFront();
679
+ targeNode.setProp({
680
+ targetPort: edge.store.data.target.port,
681
+ associated,
682
+ });
683
+ sourceNode.setProp({
684
+ sourcePort: edge.store.data.source.port,
685
+ associated,
686
+ });
687
+ };
688
+ lightUp(targetEdge);
689
+ m2mEdge && lightUp(m2mEdge);
690
+ };
691
+ // 首次渲染
692
+ const renderInitGraphCollection = (rawData) => {
693
+ const { nodesData, edgesData, inheritEdges } = formatData(rawData);
694
+ targetGraph.data = { nodes: nodesData, edges: edgesData };
695
+ getNodes(nodesData);
696
+ getEdges(edgesData);
697
+ getEdges(inheritEdges);
698
+ layout(useSaveGraphPositionAction);
699
+ };
700
+
701
+ // 增量渲染
702
+ const renderDiffGraphCollection = (rawData) => {
703
+ const { positions }: { positions: { x: number; y: number }[] } = targetGraph;
704
+ const { nodesData, edgesData, inheritEdges } = formatData(rawData);
705
+ const currentNodes = targetGraph.getNodes().map((v) => v.store.data);
706
+ const totalEdges = targetGraph.getEdges().map((v) => v.store.data);
707
+ const currentEdgesGroup = groupBy(totalEdges, (v) => {
708
+ if (v.connectionType) {
709
+ return 'currentInheritEdges';
710
+ } else {
711
+ return 'currentRelateEdges';
712
+ }
713
+ });
714
+ const diffNodes = getDiffNode(nodesData, currentNodes);
715
+ const diffEdges = getDiffEdge(edgesData, currentEdgesGroup.currentRelateEdges || []);
716
+ const diffInheritEdge = getDiffEdge(inheritEdges, currentEdgesGroup.currentInheritEdges || []);
717
+ diffNodes.forEach(({ status, node, port }) => {
718
+ const updateNode = targetGraph.getCellById(node.id);
719
+ switch (status) {
720
+ case 'add':
721
+ const maxY = maxBy(positions, 'y').y;
722
+ const yNodes = positions.filter((v) => {
723
+ return Math.abs(v.y - maxY) < 100;
724
+ });
725
+ let referenceNode: any = maxBy(yNodes, 'x');
726
+ let position;
727
+ if (referenceNode.x > 4500) {
728
+ const minX = minBy(positions, 'x').x;
729
+ referenceNode = minBy(yNodes, 'x');
730
+ position = { x: minX, y: referenceNode.y + 400 };
731
+ } else {
732
+ position = { x: referenceNode.x + 350, y: referenceNode.y };
733
+ }
734
+ targetNode = targetGraph.addNode({
735
+ ...node,
736
+ position,
737
+ });
738
+ useSaveGraphPositionAction({
739
+ collectionName: node.name,
740
+ ...position,
741
+ });
742
+ targetGraph && targetGraph.positionCell(targetNode, 'top', { padding: 200 });
743
+ break;
744
+ case 'insertPort':
745
+ updateNode.insertPort(port.index, port.port);
746
+ break;
747
+ case 'deletePort':
748
+ updateNode.removePort(port.id);
749
+ break;
750
+ case 'updateNode':
751
+ updateNode.setProp({ title: node.title });
752
+ break;
753
+ case 'delete':
754
+ targetGraph.removeCell(node.id);
755
+ default:
756
+ return null;
757
+ }
758
+ });
759
+ const renderDiffEdges = (data) => {
760
+ data.forEach(({ status, edge }) => {
761
+ switch (status) {
762
+ case 'add':
763
+ const newEdge = targetGraph.addEdge({
764
+ ...edge,
765
+ });
766
+ optimizeEdge(newEdge);
767
+ break;
768
+ case 'delete':
769
+ targetGraph.removeCell(edge.id);
770
+ break;
771
+ default:
772
+ return null;
773
+ }
774
+ });
775
+ };
776
+ setTimeout(() => {
777
+ renderDiffEdges(diffEdges.concat(diffInheritEdge));
778
+ });
779
+ };
780
+
781
+ const handleSearchCollection = (e) => {
782
+ const value = e.target.value.toLowerCase();
783
+ if (value) {
784
+ const targetCollections = collectionData.filter((v) => {
785
+ const collectionTitle = compile(v.title).toLowerCase();
786
+ return collectionTitle.includes(value);
787
+ });
788
+ setCollectionList(targetCollections);
789
+ } else {
790
+ setCollectionList(collectionData);
791
+ }
792
+ };
793
+
794
+ // 处理不同方向的继承关系表
795
+ const hanleHighlightInheritedNode = (key, direction) => {
796
+ if (direction === DirectionType.Target) {
797
+ const INodes = getInheritCollections(targetGraph.collections, key);
798
+ INodes.forEach((v) => {
799
+ targetGraph.getCellById(v)?.setAttrs({
800
+ hightLight: true,
801
+ direction,
802
+ connectionType: ConnectionType.Inherit,
803
+ });
804
+ });
805
+ } else {
806
+ const INodes = getChildrenCollections(targetGraph.collections, key);
807
+ INodes.forEach((v) => {
808
+ targetGraph.getCellById(v.name)?.setAttrs({
809
+ hightLight: true,
810
+ direction,
811
+ connectionType: ConnectionType.Inherit,
812
+ });
813
+ });
814
+ }
815
+ };
816
+
817
+ // target index entity relation
818
+ const handelTargetIndexEntity: any = (key) => {
819
+ const node = targetGraph.getCellById(key);
820
+ targetGraph.cacheCollection[key] = true;
821
+ const connectedEdges = targetGraph.getConnectedEdges(node);
822
+ const visibleEdges = connectedEdges.filter((v) => !v.store.data?.connectionType && v.getTargetCellId() === key);
823
+ visibleEdges.forEach((v) => {
824
+ if (v.store.data.m2m) {
825
+ v.store.data.m2m.forEach((i) => {
826
+ const m2mEdge = targetGraph.getCellById(i);
827
+ if (m2mEdge.getTargetCellId() === key) {
828
+ const sourceId = m2mEdge.getSourceCellId();
829
+ const node = targetGraph.getCellById(sourceId);
830
+ if (!node.store.data.attrs?.hightLight) {
831
+ node.setAttrs({
832
+ hightLight: true,
833
+ direction: DirectionType.Target,
834
+ connectionType: ConnectionType.Entity,
835
+ });
836
+ handelTargetIndexEntity(sourceId);
837
+ }
838
+ }
839
+ });
840
+ }
841
+ const sourceId = v.getSourceCellId();
842
+ const node = targetGraph.getCellById(sourceId);
843
+ if (!node.store.data.attrs?.hightLight) {
844
+ node.setAttrs({
845
+ hightLight: true,
846
+ direction: DirectionType.Target,
847
+ connectionType: ConnectionType.Entity,
848
+ });
849
+ handelTargetIndexEntity(sourceId);
850
+ }
851
+ });
852
+ };
853
+
854
+ // source index entity relation
855
+ const handelSourceIndexEntity: any = (key) => {
856
+ const node = targetGraph.getCellById(key);
857
+ const connectedEdges = targetGraph.getConnectedEdges(node);
858
+ const visibleEdges = connectedEdges.filter((v) => !v.store.data?.connectionType && v.getSourceCellId() === key);
859
+ visibleEdges.forEach((v) => {
860
+ if (v.store.data.m2m) {
861
+ v.store.data.m2m.forEach((i) => {
862
+ const m2mEdge = targetGraph.getCellById(i);
863
+ if (m2mEdge.getSourceCellId() === key) {
864
+ const targetId = m2mEdge.getTargetCellId();
865
+ const node = targetGraph.getCellById(targetId);
866
+ if (!node.store.data.attrs?.hightLight) {
867
+ node.setAttrs({
868
+ hightLight: true,
869
+ direction: DirectionType.Source,
870
+ connectionType: ConnectionType.Entity,
871
+ });
872
+ handelSourceIndexEntity(targetId);
873
+ }
874
+ }
875
+ });
876
+ }
877
+ const targetId = v.getTargetCellId();
878
+ const node = targetGraph.getCellById(targetId);
879
+ if (!node.store.data.attrs?.hightLight) {
880
+ node.setAttrs({
881
+ hightLight: true,
882
+ direction: DirectionType.Source,
883
+ connectionType: ConnectionType.Entity,
884
+ });
885
+ handelSourceIndexEntity(targetId);
886
+ }
887
+ });
888
+ };
889
+
890
+ // 处理不同方向的实体关系表
891
+ const handleHighlightRelationNodes = (nodekey, direction) => {
892
+ if (direction === DirectionType.Target) {
893
+ handelTargetIndexEntity(nodekey);
894
+ } else {
895
+ handelSourceIndexEntity(nodekey);
896
+ }
897
+ };
898
+ const handleCleanHighlight = (key?, currentDirection?, currentConnectionType?) => {
899
+ const nodes = targetGraph.getNodes().filter((v) => v.store.data.attrs?.hightLight);
900
+ const length = nodes.length;
901
+ for (let i = 0; i < length; i++) {
902
+ const { direction, connectionType } = nodes[i].getAttrs();
903
+ const filterFlag = nodes[i].id !== key;
904
+ const directionFlag = key && targetGraph.filterConfig?.key === key ? direction !== currentDirection : true;
905
+ const renltionshipFlag =
906
+ key && targetGraph.filterConfig?.key === key ? connectionType !== currentConnectionType : true;
907
+ if (nodes[i].id !== key) {
908
+ setTimeout(() => {
909
+ filterFlag &&
910
+ (directionFlag || renltionshipFlag) &&
911
+ nodes[i].setAttrs({
912
+ hightLight: false,
913
+ });
914
+ }, 0);
915
+ }
916
+ }
917
+ };
918
+
919
+ const handleFiterCollections = (value) => {
920
+ const { connectionType, direction, filterConfig } = targetGraph;
921
+ const directionBothFlag1 = value === filterConfig?.key && direction === DirectionType.Both;
922
+ const relationshipBothFlag =
923
+ value === filterConfig?.key &&
924
+ (connectionType === ConnectionType.Both || connectionType === filterConfig.connectionType);
925
+ if (value) {
926
+ (!directionBothFlag1 || !relationshipBothFlag) && handleCleanHighlight(value, direction, connectionType);
927
+ targetNode = targetGraph.getCellById(value);
928
+ targetGraph.positionCell(targetNode, 'center', { padding: 0 });
929
+ targetNode.setAttrs({
930
+ hightLight: true,
931
+ connectionType: connectionType,
932
+ });
933
+ setTimeout(() => {
934
+ if ([ConnectionType.Entity, ConnectionType.Both].includes(connectionType)) {
935
+ if (direction === DirectionType.Both) {
936
+ handleHighlightRelationNodes(value, DirectionType.Target);
937
+ handleHighlightRelationNodes(value, DirectionType.Source);
938
+ } else {
939
+ direction === DirectionType.Target && handleHighlightRelationNodes(value, direction);
940
+ direction === DirectionType.Source && handleHighlightRelationNodes(value, direction);
941
+ }
942
+ }
943
+ if ([ConnectionType.Inherit, ConnectionType.Both].includes(connectionType)) {
944
+ if (direction === DirectionType.Both) {
945
+ hanleHighlightInheritedNode(value, DirectionType.Target);
946
+ hanleHighlightInheritedNode(value, DirectionType.Source);
947
+ } else {
948
+ hanleHighlightInheritedNode(value, direction);
949
+ }
950
+ }
951
+ targetGraph.filterConfig = {
952
+ key: value,
953
+ direction: direction,
954
+ connectionType,
955
+ };
956
+ }, 0);
957
+ } else {
958
+ handleCleanHighlight();
959
+ }
960
+ };
961
+
962
+ const handleSetRelationshipType = (type) => {
963
+ handleSetEdgeVisible(type);
964
+ };
965
+
966
+ const handleSetEdgeVisible = (type) => {
967
+ targetNode = null;
968
+ const edges = targetGraph.getEdges();
969
+ edges.forEach((v) => {
970
+ const {
971
+ store: {
972
+ data: { connectionType },
973
+ },
974
+ } = v;
975
+ if (type === ConnectionType.Entity) {
976
+ if (connectionType) {
977
+ v.setVisible(false);
978
+ } else {
979
+ v.setVisible(true);
980
+ }
981
+ } else if (type === ConnectionType.Inherit) {
982
+ if (!connectionType) {
983
+ v.setVisible(false);
984
+ } else {
985
+ v.setVisible(true);
986
+ }
987
+ } else {
988
+ v.setVisible(true);
989
+ }
990
+ });
991
+ };
992
+
993
+ useLayoutEffect(() => {
994
+ initGraphCollections();
995
+ return () => {
996
+ targetGraph.off('cell:mouseenter');
997
+ targetGraph.off('edge:mouseleave');
998
+ targetGraph.off('node:moved');
999
+ targetGraph.off('blank:click');
1000
+ targetGraph = null;
1001
+ targetNode = null;
1002
+ };
1003
+ }, []);
1004
+
1005
+ useEffect(() => {
1006
+ refreshPositions().then(() => {
1007
+ refreshGM();
1008
+ });
1009
+ }, []);
1010
+ const loadCollections = async () => {
1011
+ return targetGraph.collections?.map((collection: any) => ({
1012
+ label: compile(collection.title),
1013
+ value: collection.name,
1014
+ }));
1015
+ };
1016
+ return (
1017
+ <Layout>
1018
+ <div className={cx(graphCollectionContainerClass)}>
1019
+ <CollectionManagerProvider collections={targetGraph?.collections} refreshCM={refreshGM}>
1020
+ <CollapsedContext.Provider value={{ collectionList, handleSearchCollection }}>
1021
+ <div className={cx(collectionListClass)}>
1022
+ <SchemaComponent
1023
+ components={{
1024
+ Select: (props) => (
1025
+ <Select popupMatchSelectWidth={false} {...props} getPopupContainer={getPopupContainer} />
1026
+ ),
1027
+ AddCollectionAction,
1028
+ }}
1029
+ schema={{
1030
+ type: 'void',
1031
+ properties: {
1032
+ block1: {
1033
+ type: 'void',
1034
+ 'x-collection': 'collections',
1035
+ 'x-decorator': 'ResourceActionProvider',
1036
+ 'x-decorator-props': {
1037
+ collection,
1038
+ request: {
1039
+ resource: 'collections',
1040
+ action: 'list',
1041
+ params: {
1042
+ pageSize: 50,
1043
+ filter: {
1044
+ inherit: false,
1045
+ },
1046
+ sort: ['sort'],
1047
+ appends: [],
1048
+ },
1049
+ },
1050
+ },
1051
+ properties: {
1052
+ actions: {
1053
+ type: 'void',
1054
+ 'x-component': 'ActionBar',
1055
+ 'x-component-props': {
1056
+ style: {
1057
+ fontSize: 16,
1058
+ },
1059
+ },
1060
+ properties: {
1061
+ create: {
1062
+ type: 'void',
1063
+ title: '{{ t("Create collection") }}',
1064
+ 'x-component': 'AddCollectionAction',
1065
+ 'x-component-props': {
1066
+ type: 'primary',
1067
+ },
1068
+ },
1069
+ fullScreen: {
1070
+ type: 'void',
1071
+ 'x-component': 'Action',
1072
+ 'x-component-props': {
1073
+ component: forwardRef(() => {
1074
+ const [isFullscreen, { toggleFullscreen }] = useFullscreen(
1075
+ document.getElementById('graph_container'),
1076
+ );
1077
+ return (
1078
+ <Tooltip title={t('Full Screen')} getPopupContainer={getPopupContainer}>
1079
+ <Button
1080
+ onClick={() => {
1081
+ toggleFullscreen();
1082
+ }}
1083
+ >
1084
+ {isFullscreen ? <FullscreenExitOutlined /> : <FullscreenOutlined />}
1085
+ </Button>
1086
+ </Tooltip>
1087
+ );
1088
+ }),
1089
+ useAction: () => {
1090
+ return {
1091
+ run() {},
1092
+ };
1093
+ },
1094
+ },
1095
+ },
1096
+ collectionList: {
1097
+ type: 'void',
1098
+ 'x-component': function Com() {
1099
+ const { handleSearchCollection, collectionList } = useContext(CollapsedContext);
1100
+ const [selectedKeys, setSelectKey] = useState([]);
1101
+ const content = (
1102
+ <div>
1103
+ <Input
1104
+ style={{ margin: '4px 0' }}
1105
+ bordered={false}
1106
+ placeholder={t('Collection Search')}
1107
+ onChange={handleSearchCollection}
1108
+ />
1109
+ <Menu
1110
+ selectedKeys={selectedKeys}
1111
+ selectable={true}
1112
+ className={css`
1113
+ .ant-menu-item {
1114
+ height: 32px;
1115
+ line-height: 32px;
1116
+ }
1117
+ `}
1118
+ style={{ maxHeight: '70vh', overflowY: 'auto', border: 'none' }}
1119
+ items={[
1120
+ { type: 'divider' },
1121
+ ...collectionList.map((v) => {
1122
+ return {
1123
+ key: v.name,
1124
+ label: compile(v.title),
1125
+ onClick: (e: any) => {
1126
+ if (e.key !== selectedKeys[0]) {
1127
+ setSelectKey([e.key]);
1128
+ handleFiterCollections(e.key);
1129
+ } else {
1130
+ targetGraph.filterConfig = null;
1131
+ handleFiterCollections(false);
1132
+ setSelectKey([]);
1133
+ }
1134
+ },
1135
+ };
1136
+ }),
1137
+ ]}
1138
+ />
1139
+ </div>
1140
+ );
1141
+ return (
1142
+ <Popover
1143
+ content={content}
1144
+ autoAdjustOverflow
1145
+ placement="bottomRight"
1146
+ trigger={['click']}
1147
+ getPopupContainer={getPopupContainer}
1148
+ overlayClassName={css`
1149
+ .ant-popover-inner-content {
1150
+ padding: 0;
1151
+ }
1152
+ `}
1153
+ >
1154
+ <Button>
1155
+ <MenuOutlined />
1156
+ </Button>
1157
+ </Popover>
1158
+ );
1159
+ },
1160
+ 'x-component-props': {
1161
+ icon: 'MenuOutlined',
1162
+ useAction: () => {
1163
+ return {
1164
+ run() {},
1165
+ };
1166
+ },
1167
+ },
1168
+ },
1169
+ autoLayout: {
1170
+ type: 'void',
1171
+ 'x-component': 'Action',
1172
+ 'x-component-props': {
1173
+ component: forwardRef(() => {
1174
+ return (
1175
+ <Tooltip title={t('Auto layout')} getPopupContainer={getPopupContainer}>
1176
+ <Button
1177
+ onClick={() => {
1178
+ handelResetLayout();
1179
+ }}
1180
+ >
1181
+ <ApartmentOutlined />
1182
+ </Button>
1183
+ </Tooltip>
1184
+ );
1185
+ }),
1186
+ useAction: () => {
1187
+ return {
1188
+ run() {},
1189
+ };
1190
+ },
1191
+ },
1192
+ },
1193
+ connectionType: {
1194
+ type: 'void',
1195
+ 'x-component': () => {
1196
+ const menuItems = [
1197
+ {
1198
+ key: ConnectionType.Both,
1199
+ label: 'All relationships',
1200
+ },
1201
+ {
1202
+ key: ConnectionType.Entity,
1203
+ label: 'Entity relationship only',
1204
+ },
1205
+ {
1206
+ key: ConnectionType.Inherit,
1207
+ label: 'Inheritance relationship only',
1208
+ },
1209
+ ];
1210
+ const content = (
1211
+ <div>
1212
+ <Menu
1213
+ defaultSelectedKeys={[ConnectionType.Both]}
1214
+ selectable={true}
1215
+ className={css`
1216
+ .ant-menu-item {
1217
+ height: 32px;
1218
+ line-height: 32px;
1219
+ }
1220
+ `}
1221
+ style={{ maxHeight: '70vh', overflowY: 'auto', border: 'none' }}
1222
+ items={[
1223
+ { type: 'divider' },
1224
+ ...menuItems.map((v) => {
1225
+ return {
1226
+ key: v.key,
1227
+ label: t(v.label),
1228
+ onClick: (e: any) => {
1229
+ targetGraph.connectionType = v.key;
1230
+ const { filterConfig } = targetGraph;
1231
+ filterConfig && handleFiterCollections(filterConfig.key);
1232
+ handleSetRelationshipType(v.key);
1233
+ },
1234
+ };
1235
+ }),
1236
+ ]}
1237
+ />
1238
+ </div>
1239
+ );
1240
+ return (
1241
+ <Popover
1242
+ content={content}
1243
+ autoAdjustOverflow
1244
+ placement="bottomRight"
1245
+ trigger={['click']}
1246
+ getPopupContainer={getPopupContainer}
1247
+ overlayClassName={css`
1248
+ .ant-popover-inner-content {
1249
+ padding: 0;
1250
+ }
1251
+ `}
1252
+ >
1253
+ <Button>
1254
+ <ShareAltOutlined />
1255
+ </Button>
1256
+ </Popover>
1257
+ );
1258
+ },
1259
+ 'x-component-props': {
1260
+ icon: 'MenuOutlined',
1261
+ useAction: () => {
1262
+ return {
1263
+ run() {},
1264
+ };
1265
+ },
1266
+ },
1267
+ },
1268
+ direction: {
1269
+ type: 'void',
1270
+ 'x-component': () => {
1271
+ const menuItems = [
1272
+ {
1273
+ key: DirectionType.Both,
1274
+ label: 'All directions',
1275
+ },
1276
+ {
1277
+ key: DirectionType.Target,
1278
+ label: 'Target index',
1279
+ },
1280
+ {
1281
+ key: DirectionType.Source,
1282
+ label: 'Source index',
1283
+ },
1284
+ ];
1285
+ const content = (
1286
+ <div>
1287
+ <Menu
1288
+ defaultSelectedKeys={[DirectionType.Target]}
1289
+ selectable={true}
1290
+ className={css`
1291
+ .ant-menu-item {
1292
+ height: 32px;
1293
+ line-height: 32px;
1294
+ }
1295
+ `}
1296
+ style={{ maxHeight: '70vh', overflowY: 'auto', border: 'none' }}
1297
+ items={[
1298
+ { type: 'divider' },
1299
+ ...menuItems.map((v) => {
1300
+ return {
1301
+ key: v.key,
1302
+ label: t(v.label),
1303
+ onClick: (e: any) => {
1304
+ targetGraph.direction = v.key;
1305
+ const { filterConfig } = targetGraph;
1306
+ if (filterConfig) {
1307
+ handleFiterCollections(filterConfig.key);
1308
+ }
1309
+ },
1310
+ };
1311
+ }),
1312
+ ]}
1313
+ />
1314
+ </div>
1315
+ );
1316
+ return (
1317
+ <Popover
1318
+ content={content}
1319
+ autoAdjustOverflow
1320
+ placement="bottomRight"
1321
+ trigger={['click']}
1322
+ getPopupContainer={getPopupContainer}
1323
+ overlayClassName={css`
1324
+ .ant-popover-inner-content {
1325
+ padding: 0;
1326
+ }
1327
+ `}
1328
+ >
1329
+ <Button>
1330
+ <LineHeightOutlined />
1331
+ </Button>
1332
+ </Popover>
1333
+ );
1334
+ },
1335
+ },
1336
+ selectMode: {
1337
+ type: 'void',
1338
+ 'x-component': () => {
1339
+ return (
1340
+ <Tooltip title={t('Selection')}>
1341
+ <Switch
1342
+ onChange={(value) => {
1343
+ targetGraph.toggleSelection();
1344
+ }}
1345
+ />
1346
+ </Tooltip>
1347
+ );
1348
+ },
1349
+ },
1350
+ },
1351
+ },
1352
+ },
1353
+ },
1354
+ },
1355
+ }}
1356
+ scope={{
1357
+ useAsyncDataSource,
1358
+ loadCollections,
1359
+ useCreateActionAndRefreshCM: () => useCreateActionAndRefreshCM(setTargetNode),
1360
+ enableInherits: database?.dialect === 'postgres',
1361
+ }}
1362
+ />
1363
+ </div>
1364
+ </CollapsedContext.Provider>
1365
+ </CollectionManagerProvider>
1366
+ <div id="container" style={{ width: '100vw', height: '100vh' }}></div>
1367
+ <div
1368
+ id="graph-minimap"
1369
+ style={{ width: '300px', height: '250px', right: '10px', bottom: '20px', position: 'fixed' }}
1370
+ ></div>
1371
+ </div>
1372
+ </Layout>
1373
+ );
1374
+ });
1375
+ GraphDrawPage.displayName = 'GraphDrawPage';