@redocly/theme 0.57.0-next.1 → 0.57.0-next.2
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.
- package/lib/components/Catalog/CatalogCardView/CatalogCard.js +1 -0
- package/lib/components/Catalog/CatalogEntity/CatalogEntity.js +36 -25
- package/lib/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsEdge.d.ts +6 -0
- package/lib/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsEdge.js +38 -0
- package/lib/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph.d.ts +6 -0
- package/lib/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph.js +83 -0
- package/lib/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph.lazy.d.ts +2 -0
- package/lib/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph.lazy.js +29 -0
- package/lib/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsLinkedNode.d.ts +8 -0
- package/lib/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsLinkedNode.js +33 -0
- package/lib/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsNode.d.ts +16 -0
- package/lib/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsNode.js +24 -0
- package/lib/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsRootNode.d.ts +7 -0
- package/lib/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsRootNode.js +30 -0
- package/lib/components/Catalog/CatalogEntity/CatalogEntityMethodAndPath.js +5 -2
- package/lib/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityRelations.d.ts +1 -0
- package/lib/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityRelations.js +7 -3
- package/lib/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityRelationsTable.js +3 -8
- package/lib/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityRelationsTableContent.d.ts +22 -0
- package/lib/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityRelationsTableContent.js +18 -0
- package/lib/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityTeamRelations.js +2 -2
- package/lib/components/Catalog/CatalogEntityIcon.d.ts +2 -1
- package/lib/components/Catalog/CatalogEntityIcon.js +4 -6
- package/lib/components/Catalog/CatalogTableView/CatalogTableViewRow.js +1 -1
- package/lib/components/Catalog/variables.js +42 -0
- package/lib/components/CatalogClassic/CatalogClassicVirtualizedGroups.js +13 -8
- package/lib/core/constants/catalog.d.ts +10 -0
- package/lib/core/constants/catalog.js +14 -1
- package/lib/core/hooks/catalog/useGraph.d.ts +15 -0
- package/lib/core/hooks/catalog/useGraph.js +165 -0
- package/lib/core/hooks/index.d.ts +1 -0
- package/lib/core/hooks/index.js +1 -0
- package/lib/core/openapi/index.d.ts +1 -0
- package/lib/core/openapi/index.js +3 -1
- package/lib/core/styles/index.d.ts +1 -0
- package/lib/core/styles/index.js +3 -0
- package/lib/core/styles/xyflow.d.ts +1 -0
- package/lib/core/styles/xyflow.js +623 -0
- package/lib/core/utils/dynamic.d.ts +5 -3
- package/lib/core/utils/dynamic.js +1 -1
- package/package.json +3 -2
- package/src/components/Catalog/CatalogCardView/CatalogCard.tsx +1 -0
- package/src/components/Catalog/CatalogEntity/CatalogEntity.tsx +60 -42
- package/src/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsEdge.tsx +63 -0
- package/src/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph.lazy.tsx +7 -0
- package/src/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsGraph.tsx +91 -0
- package/src/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsLinkedNode.tsx +48 -0
- package/src/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsNode.tsx +45 -0
- package/src/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsRootNode.tsx +49 -0
- package/src/components/Catalog/CatalogEntity/CatalogEntityMethodAndPath.tsx +6 -2
- package/src/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityRelations.tsx +8 -2
- package/src/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityRelationsTable.tsx +24 -43
- package/src/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityRelationsTableContent.tsx +76 -0
- package/src/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityTeamRelations.tsx +2 -2
- package/src/components/Catalog/CatalogEntityIcon.tsx +7 -5
- package/src/components/Catalog/CatalogTableView/CatalogTableViewRow.tsx +1 -1
- package/src/components/Catalog/variables.ts +42 -0
- package/src/components/CatalogClassic/CatalogClassicVirtualizedGroups.tsx +29 -18
- package/src/core/constants/catalog.ts +13 -0
- package/src/core/hooks/catalog/useGraph.ts +236 -0
- package/src/core/hooks/index.ts +1 -0
- package/src/core/openapi/index.ts +1 -0
- package/src/core/styles/index.ts +1 -0
- package/src/core/styles/xyflow.ts +620 -0
- package/src/core/utils/dynamic.tsx +17 -15
package/src/components/Catalog/CatalogEntity/CatalogEntityRelations/CatalogEntityTeamRelations.tsx
CHANGED
|
@@ -88,7 +88,7 @@ export function CatalogEntityTeamRelations({
|
|
|
88
88
|
<Tabs forceReady={relations.length > 0} size={TabsSize.MEDIUM}>
|
|
89
89
|
<TabItem label="Members" icon={<PeopleIcon />} onClick={() => setFilter('type:user')}>
|
|
90
90
|
<CatalogEntityRelationsTable
|
|
91
|
-
key=
|
|
91
|
+
key="members-table"
|
|
92
92
|
entity={entity}
|
|
93
93
|
entitiesCatalogConfig={entitiesCatalogConfig}
|
|
94
94
|
catalogConfig={catalogConfig}
|
|
@@ -111,7 +111,7 @@ export function CatalogEntityTeamRelations({
|
|
|
111
111
|
onClick={() => setFilter('-type:user')}
|
|
112
112
|
>
|
|
113
113
|
<CatalogEntityDefaultRelations
|
|
114
|
-
key=
|
|
114
|
+
key="related-entities-table"
|
|
115
115
|
entity={entity}
|
|
116
116
|
relations={relations}
|
|
117
117
|
query={query}
|
|
@@ -11,14 +11,15 @@ import { MoleculesIcon } from '@redocly/theme/icons/MoleculesIcon/MoleculesIcon'
|
|
|
11
11
|
export type CatalogEntityIconProps = {
|
|
12
12
|
entityType: string;
|
|
13
13
|
defaultColor?: boolean;
|
|
14
|
+
forceColor?: string;
|
|
14
15
|
};
|
|
15
16
|
|
|
16
17
|
const getIconColor = (entityType: EntityType) => `var(--catalog-entity-icon-color-${entityType})`;
|
|
17
18
|
|
|
18
|
-
const getEntityIcon = ({ entityType, defaultColor }: CatalogEntityIconProps) => {
|
|
19
|
-
const iconColor =
|
|
20
|
-
|
|
21
|
-
: getIconColor(entityType as EntityType);
|
|
19
|
+
const getEntityIcon = ({ entityType, defaultColor, forceColor }: CatalogEntityIconProps) => {
|
|
20
|
+
const iconColor =
|
|
21
|
+
forceColor ??
|
|
22
|
+
(defaultColor ? `var(--catalog-entity-icon-color)` : getIconColor(entityType as EntityType));
|
|
22
23
|
|
|
23
24
|
const entityIconMap: Record<EntityType, JSX.Element> = {
|
|
24
25
|
service: <CodeIcon color={iconColor} />,
|
|
@@ -36,8 +37,9 @@ const getEntityIcon = ({ entityType, defaultColor }: CatalogEntityIconProps) =>
|
|
|
36
37
|
export function CatalogEntityIcon({
|
|
37
38
|
entityType,
|
|
38
39
|
defaultColor = false,
|
|
40
|
+
forceColor,
|
|
39
41
|
}: CatalogEntityIconProps): JSX.Element {
|
|
40
|
-
const icon = getEntityIcon({ entityType, defaultColor });
|
|
42
|
+
const icon = getEntityIcon({ entityType, defaultColor, forceColor });
|
|
41
43
|
|
|
42
44
|
if (!icon) {
|
|
43
45
|
throw new Error(`Unhandled entity type: ${entityType}`);
|
|
@@ -101,7 +101,7 @@ export const CatalogTableViewRow = <T extends BaseEntity>({
|
|
|
101
101
|
key={entity.id}
|
|
102
102
|
$columnsWidths={columns.map((column) => column.width || '1fr')}
|
|
103
103
|
$columnsMinWidths={columns.map((column) => column.minWidth || 'auto')}
|
|
104
|
-
to={getEntityDetailsLink()}
|
|
104
|
+
to={getEntityDetailsLink() + '?search='}
|
|
105
105
|
style={{ color: 'var(--catalog-page-wrapper-text-color)' }}
|
|
106
106
|
data-component-name="Catalog/CatalogTableView/CatalogTableViewRow"
|
|
107
107
|
>
|
|
@@ -382,4 +382,46 @@ export const catalog = css`
|
|
|
382
382
|
|
|
383
383
|
--catalog-avatar-bg-color: #ededf2;
|
|
384
384
|
// @tokens End
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* @tokens Catalog entity relations node
|
|
388
|
+
*/
|
|
389
|
+
--catalog-entity-relations-node-padding-vertical: 10px;
|
|
390
|
+
--catalog-entity-relations-node-padding-horizontal: 14px;
|
|
391
|
+
--catalog-entity-relations-node-padding: var(--catalog-entity-relations-node-padding-vertical) var(--catalog-entity-relations-node-padding-horizontal);
|
|
392
|
+
--catalog-entity-relations-node-gap: 8px;
|
|
393
|
+
--catalog-entity-relations-node-border-radius: 10px;
|
|
394
|
+
|
|
395
|
+
--catalog-entity-relations-node-bg-color: var(--layer-color);
|
|
396
|
+
--catalog-entity-relations-node-text-color: var(--catalog-card-text-color);
|
|
397
|
+
|
|
398
|
+
--catalog-entity-relations-node-border-color: var(--border-color-secondary);
|
|
399
|
+
--catalog-entity-relations-node-border-width: var(--border-width);
|
|
400
|
+
--catalog-entity-relations-node-border-style: var(--border-style);
|
|
401
|
+
|
|
402
|
+
--catalog-entity-relations-node-font-weight: var(--font-weight-regular);
|
|
403
|
+
--catalog-entity-relations-node-font-weight-root: 600;
|
|
404
|
+
|
|
405
|
+
--catalog-entity-relations-node-root-bg-color: var(--color-blue-6);
|
|
406
|
+
--catalog-entity-relations-node-root-text-color: #ffffff;
|
|
407
|
+
--catalog-entity-relations-node-root-icon-color: #ffffff;
|
|
408
|
+
// @tokens End
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* @tokens Catalog entity relations edge
|
|
412
|
+
*/
|
|
413
|
+
--catalog-entity-relations-edge-label-bg-color: var(--layer-color);
|
|
414
|
+
--catalog-entity-relations-edge-label-text-color: var(--catalog-card-text-color);
|
|
415
|
+
--catalog-entity-relations-edge-label-border-color: var(--border-color-secondary);
|
|
416
|
+
--catalog-entity-relations-edge-label-border-width: var(--border-width);
|
|
417
|
+
--catalog-entity-relations-edge-label-border-style: var(--border-style);
|
|
418
|
+
--catalog-entity-relations-edge-label-border-radius: 10px;
|
|
419
|
+
|
|
420
|
+
--catalog-entity-relations-edge-label-padding-vertical: 2px;
|
|
421
|
+
--catalog-entity-relations-edge-label-padding-horizontal: 8px;
|
|
422
|
+
--catalog-entity-relations-edge-label-padding: var(--catalog-entity-relations-edge-label-padding-vertical) var(--catalog-entity-relations-edge-label-padding-horizontal);
|
|
423
|
+
|
|
424
|
+
--catalog-entity-relations-edge-label-font-size: 12px;
|
|
425
|
+
--catalog-entity-relations-edge-label-line-height: 1.2;
|
|
426
|
+
// @tokens End
|
|
385
427
|
`;
|
|
@@ -106,6 +106,7 @@ export function CatalogClassicVirtualizedGroups({
|
|
|
106
106
|
return ESTIMATED_CARD_HEIGHT;
|
|
107
107
|
},
|
|
108
108
|
overscan: 5,
|
|
109
|
+
enabled: shouldVirtualize,
|
|
109
110
|
});
|
|
110
111
|
|
|
111
112
|
useEffect(() => {
|
|
@@ -117,27 +118,29 @@ export function CatalogClassicVirtualizedGroups({
|
|
|
117
118
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
118
119
|
}, [filters, filterTerm, size.width, shouldVirtualize]);
|
|
119
120
|
|
|
121
|
+
const renderRow = (rowData: VirtualRowData) => {
|
|
122
|
+
if (rowData.type === 'header') {
|
|
123
|
+
return (
|
|
124
|
+
<SSRHeaderRow key={rowData.key}>
|
|
125
|
+
<CatalogSeparatorLabel>{rowData.groupTitle}</CatalogSeparatorLabel>
|
|
126
|
+
<CounterTag borderless>{rowData.groupCount}</CounterTag>
|
|
127
|
+
</SSRHeaderRow>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
<SSRGridRow key={rowData.key}>
|
|
133
|
+
{rowData.items.map((item) => (
|
|
134
|
+
<CatalogClassicCard key={item.link} item={item} />
|
|
135
|
+
))}
|
|
136
|
+
</SSRGridRow>
|
|
137
|
+
);
|
|
138
|
+
};
|
|
139
|
+
|
|
120
140
|
if (!isClient) {
|
|
121
141
|
return (
|
|
122
142
|
<div ref={parentRef} data-component-name="CatalogClassic/CatalogClassicVirtualizedGroups">
|
|
123
|
-
{flatRows.slice(0, 15).map((rowData) =>
|
|
124
|
-
if (rowData.type === 'header') {
|
|
125
|
-
return (
|
|
126
|
-
<SSRHeaderRow key={rowData.key}>
|
|
127
|
-
<CatalogSeparatorLabel>{rowData.groupTitle}</CatalogSeparatorLabel>
|
|
128
|
-
<CounterTag borderless>{rowData.groupCount}</CounterTag>
|
|
129
|
-
</SSRHeaderRow>
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return (
|
|
134
|
-
<SSRGridRow key={rowData.key}>
|
|
135
|
-
{rowData.items.map((item) => (
|
|
136
|
-
<CatalogClassicCard key={item.link} item={item} />
|
|
137
|
-
))}
|
|
138
|
-
</SSRGridRow>
|
|
139
|
-
);
|
|
140
|
-
})}
|
|
143
|
+
{flatRows.slice(0, 15).map((rowData) => renderRow(rowData))}
|
|
141
144
|
<LoadingWrapper>
|
|
142
145
|
<SpinnerLoader color="var(--catalog-classic-description-text-color)" size="20px" />
|
|
143
146
|
</LoadingWrapper>
|
|
@@ -145,6 +148,14 @@ export function CatalogClassicVirtualizedGroups({
|
|
|
145
148
|
);
|
|
146
149
|
}
|
|
147
150
|
|
|
151
|
+
if (!shouldVirtualize) {
|
|
152
|
+
return (
|
|
153
|
+
<div ref={parentRef} data-component-name="CatalogClassic/CatalogClassicVirtualizedGroups">
|
|
154
|
+
{flatRows.map((rowData) => renderRow(rowData))}
|
|
155
|
+
</div>
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
148
159
|
return (
|
|
149
160
|
<div ref={parentRef} data-component-name="CatalogClassic/CatalogClassicVirtualizedGroups">
|
|
150
161
|
<div
|
|
@@ -57,3 +57,16 @@ export const reverseRelationMap: Record<EntityRelationType, EntityRelationType>
|
|
|
57
57
|
triggers: 'triggeredBy',
|
|
58
58
|
triggeredBy: 'triggers',
|
|
59
59
|
} as const;
|
|
60
|
+
|
|
61
|
+
export enum GraphHandleType {
|
|
62
|
+
Target = 'target',
|
|
63
|
+
Source = 'source',
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export enum GraphCustomNodeType {
|
|
67
|
+
CatalogEntity = 'catalogEntity',
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export enum GraphCustomEdgeType {
|
|
71
|
+
CatalogEdge = 'catalogEdge',
|
|
72
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
addEdge,
|
|
4
|
+
type Node,
|
|
5
|
+
type Edge,
|
|
6
|
+
type Connection,
|
|
7
|
+
useNodesState,
|
|
8
|
+
useEdgesState,
|
|
9
|
+
Position,
|
|
10
|
+
OnNodesChange,
|
|
11
|
+
OnEdgesChange,
|
|
12
|
+
} from '@xyflow/react';
|
|
13
|
+
|
|
14
|
+
import { type CatalogEntityNodeData } from '@redocly/theme/components/Catalog/CatalogEntity/CatalogEntityGraph/CatalogEntityRelationsNode';
|
|
15
|
+
|
|
16
|
+
import { BffCatalogEntity, BffCatalogRelatedEntity } from '../../types';
|
|
17
|
+
import {
|
|
18
|
+
GraphCustomEdgeType,
|
|
19
|
+
GraphCustomNodeType,
|
|
20
|
+
GraphHandleType,
|
|
21
|
+
reverseRelationMap,
|
|
22
|
+
} from '../../constants/catalog';
|
|
23
|
+
|
|
24
|
+
export type UseGraphProps = {
|
|
25
|
+
entity: BffCatalogEntity;
|
|
26
|
+
relations: BffCatalogRelatedEntity[];
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type UseGraphReturn = {
|
|
30
|
+
nodes: Node<CatalogEntityNodeData>[];
|
|
31
|
+
edges: Edge[];
|
|
32
|
+
onNodesChange: OnNodesChange<Node<CatalogEntityNodeData>>;
|
|
33
|
+
onEdgesChange: OnEdgesChange<Edge>;
|
|
34
|
+
onConnect: (params: Connection) => void;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
type EntityGraphData = {
|
|
38
|
+
id: string;
|
|
39
|
+
title: string;
|
|
40
|
+
entityType: string;
|
|
41
|
+
relationLabel: string;
|
|
42
|
+
key: string;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// TODO: This isn't final implementation, leaved comments for future reference.
|
|
46
|
+
export function useGraph({ entity, relations }: UseGraphProps): UseGraphReturn {
|
|
47
|
+
const rootNodeId = entity.id;
|
|
48
|
+
|
|
49
|
+
// Compute final label for a relation considering its role
|
|
50
|
+
const getRelationLabel = useCallback((relation: BffCatalogRelatedEntity): string => {
|
|
51
|
+
const relationType = relation.relationType;
|
|
52
|
+
if (!relationType) {
|
|
53
|
+
return 'related';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return relation.relationRole === 'source' ? reverseRelationMap[relationType] : relationType;
|
|
57
|
+
}, []);
|
|
58
|
+
|
|
59
|
+
const processedRelations = useMemo(() => {
|
|
60
|
+
// Exclude self-relations and deduplicate by id
|
|
61
|
+
const seenIds = new Set<string>();
|
|
62
|
+
const filtered = (relations ?? []).filter((r) => r.id !== rootNodeId && r.key !== entity.key);
|
|
63
|
+
|
|
64
|
+
const unique = [] as Array<{
|
|
65
|
+
id: string;
|
|
66
|
+
title: string;
|
|
67
|
+
entityType: string;
|
|
68
|
+
relationLabel: string;
|
|
69
|
+
key: string;
|
|
70
|
+
}>;
|
|
71
|
+
|
|
72
|
+
for (const r of filtered) {
|
|
73
|
+
if (seenIds.has(r.id)) continue;
|
|
74
|
+
seenIds.add(r.id);
|
|
75
|
+
unique.push({
|
|
76
|
+
id: r.id,
|
|
77
|
+
title: r.title,
|
|
78
|
+
entityType: r.type, // Group by entity type, not relation type
|
|
79
|
+
relationLabel: getRelationLabel(r),
|
|
80
|
+
key: r.key,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return unique;
|
|
85
|
+
}, [relations, getRelationLabel, rootNodeId, entity.key]);
|
|
86
|
+
|
|
87
|
+
// Entity data type for layout
|
|
88
|
+
|
|
89
|
+
const computedNodes = useMemo<Node<CatalogEntityNodeData>[]>(() => {
|
|
90
|
+
if (!processedRelations.length) {
|
|
91
|
+
return [
|
|
92
|
+
{
|
|
93
|
+
id: rootNodeId,
|
|
94
|
+
type: GraphCustomNodeType.CatalogEntity,
|
|
95
|
+
position: { x: 0, y: 0 },
|
|
96
|
+
data: {
|
|
97
|
+
label: entity.title,
|
|
98
|
+
entityType: entity.type,
|
|
99
|
+
isRoot: true,
|
|
100
|
+
entityKey: entity.key,
|
|
101
|
+
},
|
|
102
|
+
sourcePosition: Position.Bottom,
|
|
103
|
+
targetPosition: Position.Top,
|
|
104
|
+
},
|
|
105
|
+
];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Group entities by their entity type
|
|
109
|
+
const entityTypeGroups = new Map<string, EntityGraphData[]>();
|
|
110
|
+
for (const rel of processedRelations) {
|
|
111
|
+
const entityData: EntityGraphData = {
|
|
112
|
+
id: rel.id,
|
|
113
|
+
title: rel.title,
|
|
114
|
+
entityType: rel.entityType,
|
|
115
|
+
relationLabel: rel.relationLabel,
|
|
116
|
+
key: rel.key,
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const current = entityTypeGroups.get(rel.entityType);
|
|
120
|
+
if (current) {
|
|
121
|
+
current.push(entityData);
|
|
122
|
+
} else {
|
|
123
|
+
entityTypeGroups.set(rel.entityType, [entityData]);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Sort entity types for consistent ordering
|
|
128
|
+
const entityTypes = Array.from(entityTypeGroups.keys()).sort();
|
|
129
|
+
|
|
130
|
+
// Layout constants
|
|
131
|
+
const rootY = 0;
|
|
132
|
+
const verticalGap = 80; // Gap between entities of same type (vertical)
|
|
133
|
+
const horizontalGap = 250; // Gap between different entity types (horizontal)
|
|
134
|
+
const topMargin = 240; // Distance from root to first row of entities
|
|
135
|
+
|
|
136
|
+
// Special handling for single entity type group - root on left, entities on right
|
|
137
|
+
const isSingleGroup = entityTypes.length === 1;
|
|
138
|
+
|
|
139
|
+
let rootX = 0;
|
|
140
|
+
let startX = 0;
|
|
141
|
+
|
|
142
|
+
if (isSingleGroup) {
|
|
143
|
+
// Position root on the left, entities on the right
|
|
144
|
+
rootX = -horizontalGap / 2;
|
|
145
|
+
startX = horizontalGap / 2;
|
|
146
|
+
} else {
|
|
147
|
+
// Calculate starting X position to center all groups (original behavior)
|
|
148
|
+
const totalWidth = (entityTypes.length - 1) * horizontalGap;
|
|
149
|
+
startX = -totalWidth / 2;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const nodes: Node<CatalogEntityNodeData>[] = [
|
|
153
|
+
// Root entity
|
|
154
|
+
{
|
|
155
|
+
id: rootNodeId,
|
|
156
|
+
type: GraphCustomNodeType.CatalogEntity,
|
|
157
|
+
position: { x: rootX, y: rootY },
|
|
158
|
+
data: { label: entity.title, entityType: entity.type, isRoot: true, entityKey: entity.key },
|
|
159
|
+
sourcePosition: Position.Bottom,
|
|
160
|
+
targetPosition: Position.Top,
|
|
161
|
+
},
|
|
162
|
+
];
|
|
163
|
+
|
|
164
|
+
// Position entities by type groups
|
|
165
|
+
for (let typeIndex = 0; typeIndex < entityTypes.length; typeIndex++) {
|
|
166
|
+
const entityType = entityTypes[typeIndex];
|
|
167
|
+
const entitiesOfType = entityTypeGroups.get(entityType) ?? [];
|
|
168
|
+
|
|
169
|
+
// Calculate X position for this entity type group
|
|
170
|
+
const groupX = startX + typeIndex * horizontalGap;
|
|
171
|
+
|
|
172
|
+
// Calculate starting Y position to center entities vertically within the group
|
|
173
|
+
const groupHeight = (entitiesOfType.length - 1) * verticalGap;
|
|
174
|
+
const groupStartY = rootY + topMargin - groupHeight / 2;
|
|
175
|
+
|
|
176
|
+
// Position each entity within the group
|
|
177
|
+
for (let entityIndex = 0; entityIndex < entitiesOfType.length; entityIndex++) {
|
|
178
|
+
const entityData = entitiesOfType[entityIndex];
|
|
179
|
+
const entityY = groupStartY + entityIndex * verticalGap;
|
|
180
|
+
|
|
181
|
+
nodes.push({
|
|
182
|
+
id: entityData.id,
|
|
183
|
+
type: GraphCustomNodeType.CatalogEntity,
|
|
184
|
+
position: { x: groupX, y: entityY },
|
|
185
|
+
data: {
|
|
186
|
+
label: entityData.title,
|
|
187
|
+
entityType: entityData.entityType,
|
|
188
|
+
isRoot: false,
|
|
189
|
+
entityKey: entityData.key,
|
|
190
|
+
},
|
|
191
|
+
sourcePosition: Position.Bottom,
|
|
192
|
+
targetPosition: Position.Top,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return nodes;
|
|
198
|
+
}, [rootNodeId, entity.title, entity.type, entity.key, processedRelations]);
|
|
199
|
+
|
|
200
|
+
const computedEdges = useMemo<Edge[]>(() => {
|
|
201
|
+
return processedRelations.map((relation) => ({
|
|
202
|
+
id: `e-${rootNodeId}-${relation.id}`,
|
|
203
|
+
source: rootNodeId,
|
|
204
|
+
target: relation.id,
|
|
205
|
+
sourceHandle: GraphHandleType.Source, // Use the bottom handle of the center node
|
|
206
|
+
targetHandle: GraphHandleType.Target, // Use the target handle (top) of related nodes
|
|
207
|
+
type: GraphCustomEdgeType.CatalogEdge,
|
|
208
|
+
label: relation.relationLabel,
|
|
209
|
+
}));
|
|
210
|
+
}, [rootNodeId, processedRelations]);
|
|
211
|
+
|
|
212
|
+
const [nodes, setNodes, onNodesChange] =
|
|
213
|
+
useNodesState<Node<CatalogEntityNodeData>>(computedNodes);
|
|
214
|
+
const [edges, setEdges, onEdgesChange] = useEdgesState<Edge>(computedEdges);
|
|
215
|
+
|
|
216
|
+
useEffect(() => {
|
|
217
|
+
setNodes(computedNodes);
|
|
218
|
+
}, [computedNodes, setNodes]);
|
|
219
|
+
|
|
220
|
+
useEffect(() => {
|
|
221
|
+
setEdges(computedEdges);
|
|
222
|
+
}, [computedEdges, setEdges]);
|
|
223
|
+
|
|
224
|
+
const onConnect = useCallback(
|
|
225
|
+
(params: Connection) => setEdges((edgesSnapshot) => addEdge(params, edgesSnapshot)),
|
|
226
|
+
[setEdges],
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
nodes,
|
|
231
|
+
edges,
|
|
232
|
+
onNodesChange,
|
|
233
|
+
onEdgesChange,
|
|
234
|
+
onConnect,
|
|
235
|
+
};
|
|
236
|
+
}
|
package/src/core/hooks/index.ts
CHANGED
|
@@ -37,6 +37,7 @@ export * from './use-element-size';
|
|
|
37
37
|
export * from './use-time-ago';
|
|
38
38
|
export * from './use-input-key-commands';
|
|
39
39
|
export * from './catalog/useCatalogEntities';
|
|
40
|
+
export * from './catalog/useGraph';
|
|
40
41
|
export * from './use-active-page-version';
|
|
41
42
|
export * from './use-page-versions';
|
|
42
43
|
export * from './use-user-teams';
|
|
@@ -29,3 +29,4 @@ export { useModalScrollLock } from '../hooks/use-modal-scroll-lock';
|
|
|
29
29
|
export { SecurityVariablesEnvSuffix } from '../constants/environments';
|
|
30
30
|
export { isUndefined, isString, isNotNull, isObject } from '../utils/type-guards';
|
|
31
31
|
export { ThemeDataContext, type ThemeDataTransferObject } from '../contexts/ThemeDataContext';
|
|
32
|
+
export { ENTITY_RELATION_TYPES } from '../constants/catalog';
|
package/src/core/styles/index.ts
CHANGED