@eventcatalog/core 2.65.0 → 3.0.0-beta.0

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 (130) hide show
  1. package/README.md +1 -26
  2. package/dist/analytics/analytics.cjs +1 -1
  3. package/dist/analytics/analytics.js +2 -2
  4. package/dist/analytics/log-build.cjs +1 -1
  5. package/dist/analytics/log-build.js +3 -3
  6. package/dist/{chunk-NK6OYMRD.js → chunk-JB4YT5JY.js} +1 -1
  7. package/dist/{chunk-BMDTX5IN.js → chunk-TQ4HZREX.js} +1 -1
  8. package/dist/{chunk-IJRFYF4B.js → chunk-X4W4YC3U.js} +1 -1
  9. package/dist/constants.cjs +1 -1
  10. package/dist/constants.js +1 -1
  11. package/dist/eventcatalog.cjs +1 -21
  12. package/dist/eventcatalog.config.d.cts +10 -0
  13. package/dist/eventcatalog.config.d.ts +10 -0
  14. package/dist/eventcatalog.js +3 -20
  15. package/eventcatalog/src/components/CopyAsMarkdown.tsx +19 -1
  16. package/eventcatalog/src/components/FavoriteButton.tsx +54 -0
  17. package/eventcatalog/src/components/Grids/DomainGrid.tsx +386 -362
  18. package/eventcatalog/src/components/Grids/MessageGrid.tsx +166 -518
  19. package/eventcatalog/src/components/Header.astro +48 -23
  20. package/eventcatalog/src/components/Lists/VersionList.astro +2 -2
  21. package/eventcatalog/src/components/MDX/Design/Design.astro +4 -1
  22. package/eventcatalog/src/components/MDX/Flow/Flow.astro +2 -1
  23. package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.astro +3 -3
  24. package/eventcatalog/src/components/SchemaExplorer/SchemaDetailsPanel.tsx +8 -2
  25. package/eventcatalog/src/components/SchemaExplorer/SchemaPageViewer.tsx +37 -0
  26. package/eventcatalog/src/components/Search/Search.astro +48 -28
  27. package/eventcatalog/src/components/Search/SearchModal.tsx +393 -702
  28. package/eventcatalog/src/components/SideNav/NestedSideBar/SearchBar.tsx +298 -0
  29. package/eventcatalog/src/components/SideNav/NestedSideBar/builders/container.ts +66 -0
  30. package/eventcatalog/src/components/SideNav/NestedSideBar/builders/domain.ts +101 -0
  31. package/eventcatalog/src/components/SideNav/NestedSideBar/builders/flow.ts +29 -0
  32. package/eventcatalog/src/components/SideNav/NestedSideBar/builders/message.ts +84 -0
  33. package/eventcatalog/src/components/SideNav/NestedSideBar/builders/service.ts +147 -0
  34. package/eventcatalog/src/components/SideNav/NestedSideBar/builders/shared.ts +146 -0
  35. package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +1073 -0
  36. package/eventcatalog/src/components/SideNav/NestedSideBar/sidebar-builder.ts +365 -0
  37. package/eventcatalog/src/components/SideNav/NestedSideBar/storage.ts +90 -0
  38. package/eventcatalog/src/components/SideNav/SideNav.astro +18 -28
  39. package/eventcatalog/src/content.config.ts +2 -0
  40. package/eventcatalog/src/enterprise/custom-documentation/pages/docs/custom/index.astro +10 -4
  41. package/eventcatalog/src/enterprise/eventcatalog-chat/pages/chat/index.astro +3 -3
  42. package/eventcatalog/src/layouts/DirectoryLayout.astro +2 -2
  43. package/eventcatalog/src/layouts/DiscoverLayout.astro +3 -3
  44. package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +85 -63
  45. package/eventcatalog/src/layouts/VisualiserLayout.astro +3 -3
  46. package/eventcatalog/src/pages/_index.astro +530 -110
  47. package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/_index.data.ts +64 -0
  48. package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/index.astro +29 -0
  49. package/eventcatalog/src/pages/directory/[type]/_index.data.ts +4 -4
  50. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/_index.data.ts +1 -4
  51. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/changelog/_index.data.ts +3 -3
  52. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/changelog/index.astro +1 -5
  53. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +362 -190
  54. package/eventcatalog/src/pages/docs/[type]/[id]/[version].md.ts +1 -1
  55. package/eventcatalog/src/pages/docs/[type]/[id]/index.astro +4 -4
  56. package/eventcatalog/src/pages/docs/[type]/[id]/language/_index.data.ts +1 -4
  57. package/eventcatalog/src/pages/docs/[type]/[id]/language/index.astro +3 -27
  58. package/eventcatalog/src/pages/docs/teams/[id]/_index.data.ts +2 -2
  59. package/eventcatalog/src/pages/docs/users/[id]/_index.data.ts +2 -2
  60. package/eventcatalog/src/pages/index.astro +14 -5
  61. package/eventcatalog/src/pages/nav-index.json.ts +30 -0
  62. package/eventcatalog/src/pages/schemas/[type]/[id]/[version]/_index.data.ts +77 -0
  63. package/eventcatalog/src/pages/schemas/[type]/[id]/[version]/index.astro +90 -0
  64. package/eventcatalog/src/pages/schemas/{index.astro → explorer/index.astro} +3 -3
  65. package/eventcatalog/src/pages/studio.astro +3 -3
  66. package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/_index.data.ts +4 -3
  67. package/eventcatalog/src/pages/visualiser/[type]/[id]/index.astro +2 -2
  68. package/eventcatalog/src/pages/visualiser/domains/[id]/[version]/entity-map/_index.data.ts +4 -3
  69. package/eventcatalog/src/stores/favorites-store.ts +83 -0
  70. package/eventcatalog/src/stores/sidebar-store.ts +8 -0
  71. package/eventcatalog/src/utils/collections/changelogs.ts +7 -4
  72. package/eventcatalog/src/utils/{channels.ts → collections/channels.ts} +81 -31
  73. package/eventcatalog/src/utils/collections/commands.ts +134 -0
  74. package/eventcatalog/src/utils/collections/containers.ts +44 -33
  75. package/eventcatalog/src/utils/collections/domains.ts +204 -62
  76. package/eventcatalog/src/utils/{entities.ts → collections/entities.ts} +44 -24
  77. package/eventcatalog/src/utils/collections/events.ts +136 -0
  78. package/eventcatalog/src/utils/collections/flows.ts +59 -25
  79. package/eventcatalog/src/utils/{messages.ts → collections/messages.ts} +13 -4
  80. package/eventcatalog/src/utils/{queries.ts → collections/queries.ts} +49 -28
  81. package/eventcatalog/src/utils/collections/services.ts +100 -68
  82. package/eventcatalog/src/utils/collections/teams.ts +94 -0
  83. package/eventcatalog/src/utils/collections/users.ts +122 -0
  84. package/eventcatalog/src/utils/collections/util.ts +57 -1
  85. package/eventcatalog/src/utils/feature.ts +3 -1
  86. package/eventcatalog/src/utils/{collections/file-diffs.ts → file-diffs.ts} +1 -1
  87. package/eventcatalog/src/utils/node-graphs/container-node-graph.ts +2 -0
  88. package/eventcatalog/src/utils/node-graphs/domain-entity-map.ts +16 -6
  89. package/eventcatalog/src/utils/node-graphs/domains-canvas.ts +14 -10
  90. package/eventcatalog/src/utils/node-graphs/domains-node-graph.ts +36 -64
  91. package/eventcatalog/src/utils/node-graphs/flows-node-graph.ts +23 -19
  92. package/eventcatalog/src/utils/node-graphs/message-node-graph.ts +36 -49
  93. package/eventcatalog/src/utils/node-graphs/services-node-graph.ts +22 -18
  94. package/eventcatalog/src/utils/page-loaders/page-data-loader.ts +4 -4
  95. package/eventcatalog/tailwind.config.mjs +14 -0
  96. package/eventcatalog/tsconfig.json +2 -1
  97. package/package.json +7 -4
  98. package/eventcatalog/public/logo_old.png +0 -0
  99. package/eventcatalog/src/components/DiscoverInsight.astro +0 -61
  100. package/eventcatalog/src/components/Grids/ServiceGrid.tsx +0 -534
  101. package/eventcatalog/src/components/Lists/CustomSideBarSectionList.astro +0 -55
  102. package/eventcatalog/src/components/Lists/ProtocolList.tsx +0 -74
  103. package/eventcatalog/src/components/Lists/RepositoryList.astro +0 -37
  104. package/eventcatalog/src/components/Lists/SpecificationsList.astro +0 -67
  105. package/eventcatalog/src/components/SideBars/ChannelSideBar.astro +0 -204
  106. package/eventcatalog/src/components/SideBars/ContainerSideBar.astro +0 -180
  107. package/eventcatalog/src/components/SideBars/DomainSideBar.astro +0 -273
  108. package/eventcatalog/src/components/SideBars/EntitySideBar.astro +0 -139
  109. package/eventcatalog/src/components/SideBars/FlowSideBar.astro +0 -128
  110. package/eventcatalog/src/components/SideBars/MessageSideBar.astro +0 -248
  111. package/eventcatalog/src/components/SideBars/ServiceSideBar.astro +0 -294
  112. package/eventcatalog/src/components/SideNav/ListViewSideBar/components/CollapsibleGroup.tsx +0 -46
  113. package/eventcatalog/src/components/SideNav/ListViewSideBar/components/MessageList.tsx +0 -78
  114. package/eventcatalog/src/components/SideNav/ListViewSideBar/components/SpecificationList.tsx +0 -83
  115. package/eventcatalog/src/components/SideNav/ListViewSideBar/index.tsx +0 -1250
  116. package/eventcatalog/src/components/SideNav/ListViewSideBar/types.ts +0 -91
  117. package/eventcatalog/src/components/SideNav/ListViewSideBar/utils.ts +0 -201
  118. package/eventcatalog/src/components/SideNav/TreeView/getTreeView.ts +0 -190
  119. package/eventcatalog/src/components/SideNav/TreeView/index.tsx +0 -94
  120. package/eventcatalog/src/components/TreeView/index.tsx +0 -328
  121. package/eventcatalog/src/components/TreeView/styles.module.css +0 -264
  122. package/eventcatalog/src/components/TreeView/useSlots.ts +0 -95
  123. package/eventcatalog/src/pages/architecture/[type]/index.astro +0 -14
  124. package/eventcatalog/src/pages/architecture/architecture.astro +0 -101
  125. package/eventcatalog/src/pages/architecture/docs/[type]/index.astro +0 -14
  126. package/eventcatalog/src/utils/commands.ts +0 -112
  127. package/eventcatalog/src/utils/events.ts +0 -108
  128. package/eventcatalog/src/utils/generators/index.ts +0 -10
  129. package/eventcatalog/src/utils/teams.ts +0 -72
  130. package/eventcatalog/src/utils/users.ts +0 -72
@@ -1,1250 +0,0 @@
1
- import React, { useState, useEffect, useRef, useMemo, useCallback } from 'react';
2
- import { ChevronDownIcon, ChevronDoubleDownIcon, ChevronDoubleUpIcon, XMarkIcon } from '@heroicons/react/24/outline';
3
- import { buildUrl, buildUrlWithParams } from '@utils/url-builder';
4
- import CollapsibleGroup from './components/CollapsibleGroup';
5
- import MessageList from './components/MessageList';
6
- import SpecificationsList from './components/SpecificationList';
7
- import type { MessageItem, ServiceItem, ListViewSideBarProps, DomainItem, FlowItem, Resources } from './types';
8
- import { PanelLeft } from 'lucide-react';
9
- const STORAGE_KEY = 'EventCatalog:catalogSidebarCollapsedGroups';
10
- const DEBOUNCE_DELAY = 300; // 300ms debounce delay
11
-
12
- const HighlightedText = React.memo(({ text, searchTerm }: { text: string; searchTerm: string }) => {
13
- if (!searchTerm) return <>{text}</>;
14
-
15
- const regex = new RegExp(`(${searchTerm})`, 'gi');
16
- const parts = text.split(regex);
17
-
18
- return (
19
- <>
20
- {parts.map((part, index) =>
21
- regex.test(part) ? (
22
- <span key={index} className="bg-yellow-200 text-gray-900 font-semibold">
23
- {part}
24
- </span>
25
- ) : (
26
- <span key={index}>{part}</span>
27
- )
28
- )}
29
- </>
30
- );
31
- });
32
-
33
- export const getMessageColorByCollection = (collection: string) => {
34
- if (collection === 'commands') return 'bg-blue-50 text-blue-600';
35
- if (collection === 'queries') return 'bg-green-50 text-green-600';
36
- if (collection === 'events') return 'bg-orange-50 text-orange-600';
37
- if (collection === 'entities') return 'bg-purple-50 text-purple-600';
38
- return 'text-gray-600';
39
- };
40
-
41
- export const getMessageCollectionName = (collection: string, item: any) => {
42
- if (collection === 'commands') return 'Command';
43
- if (collection === 'queries') return 'Query';
44
- if (collection === 'events') return 'Event';
45
- if (collection === 'entities' && item.data.aggregateRoot) return 'Entity (Root)';
46
- if (collection === 'entities') return 'Entity';
47
- return collection.slice(0, collection.length - 1).toUpperCase();
48
- };
49
-
50
- const NoResultsFound = React.memo(({ searchTerm }: { searchTerm: string }) => (
51
- <div className="px-4 py-6 text-center">
52
- <div className="text-gray-400 text-sm mb-2">No results found for "{searchTerm}"</div>
53
- <div className="text-gray-400 text-xs">
54
- Try:
55
- <ul className="mt-2 space-y-1 text-left list-disc pl-4">
56
- <li>Checking for typos</li>
57
- <li>Using fewer keywords</li>
58
- <li>Using more general terms</li>
59
- </ul>
60
- </div>
61
- </div>
62
- ));
63
-
64
- const ServiceItem = React.memo(
65
- ({
66
- item,
67
- decodedCurrentPath,
68
- collapsedGroups,
69
- toggleGroupCollapse,
70
- isVisualizer,
71
- searchTerm,
72
- }: {
73
- item: ServiceItem;
74
- decodedCurrentPath: string;
75
- collapsedGroups: { [key: string]: boolean };
76
- toggleGroupCollapse: (group: string) => void;
77
- isVisualizer: boolean;
78
- searchTerm: string;
79
- }) => {
80
- const readsAndWritesTo = item.writesTo.filter((writeTo) => item.readsFrom.some((readFrom) => readFrom.id === writeTo.id));
81
- const resourceReads = item.readsFrom.filter((readFrom) => !readsAndWritesTo.some((writeTo) => writeTo.id === readFrom.id));
82
- const resourceWrites = item.writesTo.filter((writeTo) => !readsAndWritesTo.some((readFrom) => readFrom.id === writeTo.id));
83
- const hasData = item.writesTo.length > 0 || item.readsFrom.length > 0;
84
-
85
- const sendsMessages = item.sends && item.sends.length > 0;
86
- const receivesMessages = item.receives && item.receives.length > 0;
87
-
88
- return (
89
- <CollapsibleGroup
90
- isCollapsed={collapsedGroups[item.href]}
91
- onToggle={() => toggleGroupCollapse(item.href)}
92
- title={
93
- <button
94
- onClick={(e) => {
95
- e.stopPropagation();
96
- toggleGroupCollapse(item.href);
97
- }}
98
- className="flex justify-between items-center pl-2 w-full text-xs"
99
- title={item.label}
100
- >
101
- <span className="truncate text-xs font-bold">
102
- <HighlightedText text={item.label} searchTerm={searchTerm} />
103
- <span className="text-xs text-gray-400">{item.draft ? ' (DRAFT)' : ''}</span>
104
- </span>
105
- <span
106
- style={{
107
- color: item.sidebar?.color || '#9333ea',
108
- backgroundColor: item.sidebar?.backgroundColor || '#faf5ff',
109
- }}
110
- className="ml-2 rounded bg-purple-50 px-2 py-0.5 text-[10px] font-medium text-purple-600"
111
- >
112
- {item.sidebar?.badge ? item.sidebar.badge?.toUpperCase() : 'SERVICE'}
113
- </span>
114
- </button>
115
- }
116
- >
117
- <div className="space-y-0.5 border-gray-200/80 border-l pl-3 ml-[9px] mt-1">
118
- <a
119
- href={`${item.href}`}
120
- data-active={decodedCurrentPath === item.href}
121
- className={`flex items-center px-2 py-1.5 text-xs text-gray-600 hover:bg-purple-100 rounded-md ${
122
- decodedCurrentPath === item.href ? 'bg-purple-100' : 'hover:bg-purple-100'
123
- }`}
124
- >
125
- <span className="truncate">Overview</span>
126
- </a>
127
- {isVisualizer && hasData && (
128
- <a
129
- href={buildUrl(`/${item.href}/data`)}
130
- data-active={decodedCurrentPath === `${item.href}/data`}
131
- className={`flex items-center px-2 py-1.5 text-xs text-gray-600 hover:bg-purple-100 rounded-md ${
132
- decodedCurrentPath === `${item.href}/data` ? 'bg-purple-100 ' : 'hover:bg-purple-100'
133
- }`}
134
- >
135
- <span className="truncate">Data Diagram</span>
136
- </a>
137
- )}
138
- {!isVisualizer && (
139
- <a
140
- href={buildUrlWithParams('/architecture/docs/messages', {
141
- serviceName: item.name,
142
- serviceId: item.id,
143
- })}
144
- data-active={window.location.href.includes(`serviceId=${item.id}`)}
145
- className={`flex items-center px-2 py-1.5 text-xs text-gray-600 hover:bg-purple-100 rounded-md ${
146
- window.location.href.includes(`serviceId=${item.id}`) ? 'bg-purple-100' : 'hover:bg-purple-100'
147
- }`}
148
- >
149
- <span className="truncate flex items-center gap-1">Architecture</span>
150
- </a>
151
- )}
152
-
153
- {!isVisualizer && item.specifications && item.specifications.length > 0 && (
154
- <CollapsibleGroup
155
- isCollapsed={collapsedGroups[`${item.href}-specifications`]}
156
- onToggle={() => toggleGroupCollapse(`${item.href}-specifications`)}
157
- title={
158
- <button
159
- onClick={(e) => {
160
- e.stopPropagation();
161
- toggleGroupCollapse(`${item.href}-specifications`);
162
- }}
163
- className="truncate underline ml-2 text-xs mb-1 py-1"
164
- >
165
- Specifications ({item.specifications?.length})
166
- </button>
167
- }
168
- >
169
- <SpecificationsList specifications={item.specifications} id={item.id} version={item.version} />
170
- </CollapsibleGroup>
171
- )}
172
-
173
- {receivesMessages && (
174
- <CollapsibleGroup
175
- isCollapsed={collapsedGroups[`${item.href}-receives`]}
176
- onToggle={() => toggleGroupCollapse(`${item.href}-receives`)}
177
- title={
178
- <button
179
- onClick={(e) => {
180
- e.stopPropagation();
181
- toggleGroupCollapse(`${item.href}-receives`);
182
- }}
183
- className="truncate underline ml-2 text-xs mb-1 py-1"
184
- >
185
- Receives messages ({item.receives.length})
186
- </button>
187
- }
188
- >
189
- <MessageList messages={item.receives} decodedCurrentPath={decodedCurrentPath} searchTerm={searchTerm} />
190
- </CollapsibleGroup>
191
- )}
192
- {sendsMessages && (
193
- <CollapsibleGroup
194
- isCollapsed={collapsedGroups[`${item.href}-sends`]}
195
- onToggle={() => toggleGroupCollapse(`${item.href}-sends`)}
196
- title={
197
- <button
198
- onClick={(e) => {
199
- e.stopPropagation();
200
- toggleGroupCollapse(`${item.href}-sends`);
201
- }}
202
- className="truncate underline ml-2 text-xs mb-1 py-1"
203
- >
204
- Sends messages ({item.sends.length})
205
- </button>
206
- }
207
- >
208
- <MessageList messages={item.sends} decodedCurrentPath={decodedCurrentPath} searchTerm={searchTerm} />
209
- </CollapsibleGroup>
210
- )}
211
- {!isVisualizer && hasData && (
212
- <CollapsibleGroup
213
- isCollapsed={collapsedGroups[`${item.href}-data`]}
214
- onToggle={() => toggleGroupCollapse(`${item.href}-data`)}
215
- title={
216
- <button
217
- onClick={(e) => {
218
- e.stopPropagation();
219
- toggleGroupCollapse(`${item.href}-data`);
220
- }}
221
- className="truncate underline ml-2 text-xs mb-1 py-1"
222
- >
223
- Data Stores ({readsAndWritesTo.length + resourceWrites.length + resourceReads.length})
224
- </button>
225
- }
226
- >
227
- {readsAndWritesTo.length > 0 && (
228
- <CollapsibleGroup
229
- className="ml-4"
230
- isCollapsed={collapsedGroups[`${item.href}-writesTo-data`]}
231
- onToggle={() => toggleGroupCollapse(`${item.href}-writesTo-data`)}
232
- title={
233
- <button
234
- onClick={(e) => {
235
- e.stopPropagation();
236
- toggleGroupCollapse(`${item.href}-writesTo-data`);
237
- }}
238
- className="truncate underline ml-2 text-xs mb-1 py-1"
239
- >
240
- Reads and writes to
241
- </button>
242
- }
243
- >
244
- <MessageList messages={readsAndWritesTo} decodedCurrentPath={decodedCurrentPath} searchTerm={searchTerm} />
245
- </CollapsibleGroup>
246
- )}
247
- {resourceWrites.length > 0 && (
248
- <CollapsibleGroup
249
- className="ml-4"
250
- isCollapsed={collapsedGroups[`${item.href}-writesTo-data`]}
251
- onToggle={() => toggleGroupCollapse(`${item.href}-writesTo-data`)}
252
- title={
253
- <button
254
- onClick={(e) => {
255
- e.stopPropagation();
256
- toggleGroupCollapse(`${item.href}-writesTo-data`);
257
- }}
258
- className="truncate underline ml-2 text-xs mb-1 py-1"
259
- >
260
- Writes to
261
- </button>
262
- }
263
- >
264
- <MessageList messages={resourceWrites} decodedCurrentPath={decodedCurrentPath} searchTerm={searchTerm} />
265
- </CollapsibleGroup>
266
- )}
267
- {resourceReads.length > 0 && (
268
- <CollapsibleGroup
269
- className="ml-4"
270
- isCollapsed={collapsedGroups[`${item.href}-readsFrom-data`]}
271
- onToggle={() => toggleGroupCollapse(`${item.href}-readsFrom-data`)}
272
- title={
273
- <button
274
- onClick={(e) => {
275
- e.stopPropagation();
276
- toggleGroupCollapse(`${item.href}-readsFrom-data`);
277
- }}
278
- className="truncate underline ml-2 text-xs mb-1 py-1"
279
- >
280
- Reads From
281
- </button>
282
- }
283
- >
284
- <MessageList messages={resourceReads} decodedCurrentPath={decodedCurrentPath} searchTerm={searchTerm} />
285
- </CollapsibleGroup>
286
- )}
287
- </CollapsibleGroup>
288
- )}
289
- {!isVisualizer && item.entities.length > 0 && (
290
- <CollapsibleGroup
291
- isCollapsed={collapsedGroups[`${item.href}-entities`]}
292
- onToggle={() => toggleGroupCollapse(`${item.href}-entities`)}
293
- title={
294
- <button
295
- onClick={(e) => {
296
- e.stopPropagation();
297
- toggleGroupCollapse(`${item.href}-entities`);
298
- }}
299
- className="truncate underline ml-2 text-xs mb-1 py-1"
300
- >
301
- Entities ({item.entities.length})
302
- </button>
303
- }
304
- >
305
- <MessageList messages={item.entities} decodedCurrentPath={decodedCurrentPath} searchTerm={searchTerm} />
306
- </CollapsibleGroup>
307
- )}
308
- </div>
309
- </CollapsibleGroup>
310
- );
311
- }
312
- );
313
-
314
- const ListViewSideBar: React.FC<ListViewSideBarProps> = ({ resources, currentPath, showOrphanedMessages }) => {
315
- const navRef = useRef<HTMLElement>(null);
316
- const [data] = useState(resources);
317
- const [searchTerm, setSearchTerm] = useState('');
318
- const [debouncedSearchTerm, setDebouncedSearchTerm] = useState('');
319
- const [isInitialized, setIsInitialized] = useState(false);
320
- const [isExpanded, setIsExpanded] = useState(true);
321
- const [isSearchPinned, setIsSearchPinned] = useState(false);
322
- const [lastScrollTop, setLastScrollTop] = useState(0);
323
- const [collapsedGroups, setCollapsedGroups] = useState<{ [key: string]: boolean }>(() => {
324
- if (typeof window !== 'undefined') {
325
- const saved = window.localStorage.getItem(STORAGE_KEY);
326
- const savedState = saved ? JSON.parse(saved) : {};
327
- const currentPath = window.location.pathname;
328
-
329
- // Default all sections to collapsed
330
- const defaultCollapsedState: { [key: string]: boolean } = {
331
- 'all-services-group': true,
332
- 'flows-group': true,
333
- 'data-group': true,
334
- 'designs-group': true,
335
- 'messagesNotInService-group': true,
336
- };
337
-
338
- // Default all domains, services, and their subsections to collapsed
339
- resources.domains?.forEach((domain: any) => {
340
- const isDomainActive = currentPath.includes(domain.href);
341
- defaultCollapsedState[domain.href] = !isDomainActive;
342
- defaultCollapsedState[`${domain.href}-entities`] = true;
343
- defaultCollapsedState[`${domain.href}-subdomains`] = true;
344
- defaultCollapsedState[`${domain.href}-services`] = true;
345
- });
346
-
347
- resources.services?.forEach((service: any) => {
348
- const isServiceActive = currentPath.includes(service.href);
349
- defaultCollapsedState[service.href] = !isServiceActive;
350
- defaultCollapsedState[`${service.href}-specifications`] = true;
351
- defaultCollapsedState[`${service.href}-receives`] = true;
352
- defaultCollapsedState[`${service.href}-sends`] = true;
353
- defaultCollapsedState[`${service.href}-entities`] = true;
354
- defaultCollapsedState[`${service.href}-data`] = true;
355
- defaultCollapsedState[`${service.href}-writesTo-data`] = true;
356
- defaultCollapsedState[`${service.href}-readsFrom-data`] = true;
357
- });
358
-
359
- setIsInitialized(true);
360
- return { ...defaultCollapsedState, ...savedState };
361
- }
362
- return {};
363
- });
364
-
365
- const decodedCurrentPath = window.location.pathname;
366
- const isVisualizer = window.location.pathname.includes('/visualiser/');
367
-
368
- useEffect(() => {
369
- const timer = setTimeout(() => {
370
- setDebouncedSearchTerm(searchTerm.toLowerCase());
371
- }, DEBOUNCE_DELAY);
372
-
373
- return () => clearTimeout(timer);
374
- }, [searchTerm]);
375
-
376
- // Filter data based on search term
377
- const filteredData: Resources = useMemo(() => {
378
- if (!debouncedSearchTerm) return data;
379
-
380
- const filterItem = (item: { label: string; id?: string }) => {
381
- return (
382
- item.label.toLowerCase().includes(debouncedSearchTerm) || (item.id && item.id.toLowerCase().includes(debouncedSearchTerm))
383
- );
384
- };
385
-
386
- const filterMessages = (messages: MessageItem[]) => {
387
- return messages.filter(
388
- (message) =>
389
- message.data.name.toLowerCase().includes(debouncedSearchTerm) || message.id.toLowerCase().includes(debouncedSearchTerm)
390
- );
391
- };
392
-
393
- // Enhanced domain filtering that considers parent-subdomain relationships
394
- const filterDomains = (domains: any[]) => {
395
- const filteredDomains: any[] = [];
396
-
397
- domains.forEach((domain: any) => {
398
- const domainMatches = filterItem(domain);
399
-
400
- // Check if this domain is a subdomain of another domain
401
- const isSubdomain = domains.some((parentDomain: any) => {
402
- const subdomains = parentDomain.domains || [];
403
- return subdomains.some((subdomain: any) => subdomain.data.id === domain.id);
404
- });
405
-
406
- // If this is a parent domain, check if any of its subdomains match
407
- let hasMatchingSubdomains = false;
408
- if (!isSubdomain) {
409
- const subdomains = domain.domains || [];
410
- hasMatchingSubdomains = domains.some((potentialSubdomain: any) =>
411
- subdomains.some((subdomain: any) => subdomain.data.id === potentialSubdomain.id && filterItem(potentialSubdomain))
412
- );
413
- }
414
-
415
- // Include domain if:
416
- // 1. The domain itself matches the search
417
- // 2. It's a parent domain and has matching subdomains
418
- // 3. It's a subdomain and matches the search
419
- if (domainMatches || hasMatchingSubdomains || (isSubdomain && domainMatches)) {
420
- filteredDomains.push(domain);
421
- }
422
-
423
- // If this is a subdomain that matches, also include its parent domain
424
- if (isSubdomain && domainMatches) {
425
- const parentDomain = domains.find((parentDomain: any) => {
426
- const subdomains = parentDomain.domains || [];
427
- return subdomains.some((subdomain: any) => subdomain.data.id === domain.id);
428
- });
429
-
430
- if (parentDomain && !filteredDomains.some((d: any) => d.id === parentDomain.id)) {
431
- filteredDomains.push(parentDomain);
432
- }
433
- }
434
- });
435
-
436
- return filteredDomains;
437
- };
438
-
439
- return {
440
- 'context-map': data['context-map']?.filter(filterItem) || [],
441
- domains: data.domains ? filterDomains(data.domains) : [],
442
- services:
443
- data.services
444
- ?.map((service: ServiceItem) => ({
445
- ...service,
446
- sends: filterMessages(service.sends),
447
- receives: filterMessages(service.receives),
448
- isVisible:
449
- filterItem(service) ||
450
- service.sends.some(
451
- (msg: MessageItem) =>
452
- msg.data.name.toLowerCase().includes(debouncedSearchTerm) || msg.id.toLowerCase().includes(debouncedSearchTerm)
453
- ) ||
454
- service.receives.some(
455
- (msg: MessageItem) =>
456
- msg.data.name.toLowerCase().includes(debouncedSearchTerm) || msg.id.toLowerCase().includes(debouncedSearchTerm)
457
- ),
458
- }))
459
- .filter((service: ServiceItem & { isVisible: boolean }) => service.isVisible) || [],
460
- flows: data.flows?.filter(filterItem) || [],
461
- designs: data.designs?.filter(filterItem) || [],
462
- messagesNotInService:
463
- data.messagesNotInService?.filter(
464
- (msg: MessageItem) =>
465
- msg.label.toLowerCase().includes(debouncedSearchTerm) || msg.id.toLowerCase().includes(debouncedSearchTerm)
466
- ) || [],
467
- };
468
- }, [data, debouncedSearchTerm]);
469
-
470
- // Auto-expand groups when searching
471
- useEffect(() => {
472
- if (debouncedSearchTerm) {
473
- // Expand all groups when searching
474
- const newCollapsedState = { ...collapsedGroups };
475
- Object.keys(newCollapsedState).forEach((key) => {
476
- newCollapsedState[key] = false;
477
- });
478
- setCollapsedGroups(newCollapsedState);
479
- }
480
- }, [debouncedSearchTerm]);
481
-
482
- // Handle scroll for sticky search bar
483
- useEffect(() => {
484
- const nav = navRef.current;
485
- if (!nav) return;
486
-
487
- const handleScroll = () => {
488
- const scrollTop = nav.scrollTop;
489
- const scrollThreshold = 50; // Pin after scrolling 50px
490
-
491
- // Scrolling down past threshold
492
- if (scrollTop > scrollThreshold && scrollTop > lastScrollTop) {
493
- setIsSearchPinned(true);
494
- }
495
- // Scrolling up near the top
496
- else if (scrollTop <= scrollThreshold) {
497
- setIsSearchPinned(false);
498
- }
499
-
500
- setLastScrollTop(scrollTop);
501
- };
502
-
503
- nav.addEventListener('scroll', handleScroll);
504
- return () => nav.removeEventListener('scroll', handleScroll);
505
- }, [lastScrollTop]);
506
-
507
- // Store collapsed groups in local storage
508
- useEffect(() => {
509
- if (typeof window !== 'undefined') {
510
- window.localStorage.setItem(STORAGE_KEY, JSON.stringify(collapsedGroups));
511
- }
512
- }, [collapsedGroups]);
513
-
514
- // If we find a data-active element, scroll to it on mount and open its section
515
- useEffect(() => {
516
- const activeElement = document.querySelector('[data-active="true"]');
517
- if (activeElement) {
518
- // Add y offset to the scroll position
519
- activeElement.scrollIntoView({ behavior: 'instant', block: 'center' });
520
-
521
- // Check which section the active element belongs to and open it
522
- const newCollapsedState = { ...collapsedGroups };
523
-
524
- // Check if active page is in a domain
525
- data.domains?.forEach((domain: any) => {
526
- if (decodedCurrentPath.includes(domain.href)) {
527
- newCollapsedState[domain.href] = false;
528
-
529
- // Check if it's in domain entities
530
- const isInDomainEntities = domain.entities?.some((entity: any) => decodedCurrentPath.includes(entity.href));
531
- if (isInDomainEntities) {
532
- newCollapsedState[`${domain.href}-entities`] = false;
533
- }
534
-
535
- // Check if it's in domain services
536
- const isInDomainServices = domain.services?.some((service: any) => decodedCurrentPath.includes(service.data.id));
537
- if (isInDomainServices) {
538
- newCollapsedState[`${domain.href}-services`] = false;
539
- }
540
-
541
- // Check if it's in a subdomain
542
- const subdomains = domain.domains || [];
543
- subdomains.forEach((subdomain: any) => {
544
- const actualSubdomain = data.domains?.find((d: any) => d.id === subdomain.data.id);
545
- if (actualSubdomain && decodedCurrentPath.includes(actualSubdomain.href)) {
546
- newCollapsedState[`${domain.href}-subdomains`] = false;
547
- newCollapsedState[actualSubdomain.href] = false;
548
-
549
- // Check subdomain entities
550
- const isInSubdomainEntities = actualSubdomain.entities?.some((entity: any) =>
551
- decodedCurrentPath.includes(entity.href)
552
- );
553
- if (isInSubdomainEntities) {
554
- newCollapsedState[`${actualSubdomain.href}-entities`] = false;
555
- }
556
-
557
- // Check subdomain services
558
- const isInSubdomainServices = actualSubdomain.services?.some((service: any) =>
559
- decodedCurrentPath.includes(service.data.id)
560
- );
561
- if (isInSubdomainServices) {
562
- newCollapsedState[`${actualSubdomain.href}-services`] = false;
563
- }
564
- }
565
- });
566
- }
567
- });
568
-
569
- // Check if active page is a service
570
- data.services?.forEach((service: ServiceItem) => {
571
- if (decodedCurrentPath.includes(service.href)) {
572
- newCollapsedState['all-services-group'] = false;
573
- newCollapsedState[service.href] = false;
574
-
575
- // Open specific service sections if active
576
- if (decodedCurrentPath.includes('/data')) {
577
- newCollapsedState[`${service.href}-data`] = false;
578
- }
579
- }
580
- });
581
-
582
- // Check if active page is a flow
583
- const isFlow = data.flows?.some((flow: FlowItem) => decodedCurrentPath === flow.href);
584
- if (isFlow) {
585
- newCollapsedState['flows-group'] = false;
586
- }
587
-
588
- // Check if active page is a container/data store
589
- const isContainer = data.containers?.some((container: any) => decodedCurrentPath === container.href);
590
- if (isContainer) {
591
- newCollapsedState['data-group'] = false;
592
- }
593
-
594
- // Check if active page is a design
595
- const isDesign = data.designs?.some((design: any) => decodedCurrentPath === design.href);
596
- if (isDesign) {
597
- newCollapsedState['designs-group'] = false;
598
- }
599
-
600
- // Check if active page is an orphaned message
601
- const isOrphanedMessage = data.messagesNotInService?.some((msg: MessageItem) => decodedCurrentPath === msg.href);
602
- if (isOrphanedMessage) {
603
- newCollapsedState['messagesNotInService-group'] = false;
604
- }
605
-
606
- setCollapsedGroups(newCollapsedState);
607
- }
608
- }, []);
609
-
610
- const toggleGroupCollapse = useCallback((group: string) => {
611
- setCollapsedGroups((prev) => ({
612
- ...prev,
613
- [group]: !prev[group],
614
- }));
615
- }, []);
616
-
617
- const handleSearchChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
618
- setSearchTerm(e.target.value);
619
- }, []);
620
-
621
- const collapseAll = useCallback(() => {
622
- const newCollapsedState: { [key: string]: boolean } = {};
623
-
624
- // Collapse all domains
625
- filteredData.domains?.forEach((domain: any) => {
626
- newCollapsedState[domain.href] = true;
627
- newCollapsedState[`${domain.href}-entities`] = true;
628
- newCollapsedState[`${domain.href}-subdomains`] = true;
629
- newCollapsedState[`${domain.href}-services`] = true;
630
- });
631
-
632
- // Collapse all services
633
- filteredData.services?.forEach((service: any) => {
634
- newCollapsedState[service.href] = true;
635
- newCollapsedState[`${service.href}-specifications`] = true;
636
- newCollapsedState[`${service.href}-receives`] = true;
637
- newCollapsedState[`${service.href}-sends`] = true;
638
- newCollapsedState[`${service.href}-entities`] = true;
639
- });
640
-
641
- setCollapsedGroups(newCollapsedState);
642
- setIsExpanded(false);
643
- }, [filteredData]);
644
-
645
- const expandAll = useCallback(() => {
646
- const newCollapsedState: { [key: string]: boolean } = {};
647
-
648
- // Expand all domains
649
- filteredData.domains?.forEach((domain: any) => {
650
- newCollapsedState[domain.href] = false;
651
- newCollapsedState[`${domain.href}-entities`] = false;
652
- newCollapsedState[`${domain.href}-subdomains`] = false;
653
- newCollapsedState[`${domain.href}-services`] = false;
654
- });
655
-
656
- // Expand all services
657
- filteredData.services?.forEach((service: any) => {
658
- newCollapsedState[service.href] = false;
659
- newCollapsedState[`${service.href}-specifications`] = false;
660
- newCollapsedState[`${service.href}-receives`] = false;
661
- newCollapsedState[`${service.href}-sends`] = false;
662
- newCollapsedState[`${service.href}-entities`] = false;
663
- });
664
-
665
- setCollapsedGroups(newCollapsedState);
666
- setIsExpanded(true);
667
- }, [filteredData]);
668
-
669
- const toggleExpandCollapse = useCallback(() => {
670
- if (isExpanded) {
671
- collapseAll();
672
- } else {
673
- expandAll();
674
- }
675
- }, [isExpanded, collapseAll, expandAll]);
676
-
677
- const hideSidebar = useCallback(() => {
678
- // Dispatch custom event that the Astro layout will listen for
679
- window.dispatchEvent(new CustomEvent('sidebarToggle', { detail: { action: 'hide' } }));
680
- }, []);
681
-
682
- const isDomainSubDomain = useMemo(() => {
683
- return (domain: any) => {
684
- const domains = data.domains || [];
685
- return domains.some((d: any) => {
686
- const subdomains = d.domains || [];
687
- return subdomains.some((subdomain: any) => subdomain.data.id === domain.id);
688
- });
689
- };
690
- }, [data.domains]);
691
-
692
- // Helper function to get parent domains (domains that are not subdomains)
693
- const getParentDomains = useMemo(() => {
694
- return (domains: any[]) => {
695
- return domains.filter((domain: any) => !isDomainSubDomain(domain));
696
- };
697
- }, [isDomainSubDomain]);
698
-
699
- // Helper function to get subdomains for a specific parent domain
700
- const getSubdomainsForParent = useMemo(() => {
701
- return (parentDomain: any, allDomains: any[]) => {
702
- const subdomains = parentDomain.domains || [];
703
- return allDomains.filter((domain: any) => subdomains.some((subdomain: any) => subdomain.data.id === domain.id));
704
- };
705
- }, []);
706
-
707
- // Helper function to get services for a specific domain (only direct services, not from subdomains)
708
- const getServicesForDomain = useMemo(() => {
709
- return (domain: any, allServices: ServiceItem[], allDomains: any[]) => {
710
- const domainServices = domain.services || [];
711
- const subdomains = getSubdomainsForParent(domain, allDomains);
712
-
713
- // Get all service IDs from subdomains
714
- const subdomainServiceIds = subdomains.flatMap((subdomain: any) => (subdomain.services || []).map((s: any) => s.data.id));
715
-
716
- // Filter services that belong to this domain but NOT to any of its subdomains
717
- return allServices.filter(
718
- (service: ServiceItem) =>
719
- domainServices.some((domainService: any) => domainService.data.id === service.id) &&
720
- !subdomainServiceIds.includes(service.id)
721
- );
722
- };
723
- }, [getSubdomainsForParent]);
724
-
725
- // Component to render a single domain item
726
- const DomainItem = React.memo(
727
- ({ item, isSubdomain = false, nestingLevel = 0 }: { item: any; isSubdomain?: boolean; nestingLevel?: number }) => {
728
- const marginLeft = nestingLevel > 0 ? `ml-${nestingLevel * 4}` : '';
729
-
730
- return (
731
- <div className={`flex items-center ${marginLeft}`}>
732
- <button
733
- onClick={(e) => {
734
- e.stopPropagation();
735
- toggleGroupCollapse(item.href);
736
- }}
737
- className="p-1 hover:bg-gray-100 rounded-md"
738
- title={item.label}
739
- >
740
- <div className={`transition-transform duration-150 ${collapsedGroups[item.href] ? '' : 'rotate-180'}`}>
741
- <ChevronDownIcon className="h-3 w-3 text-gray-500" />
742
- </div>
743
- </button>
744
- <button
745
- onClick={(e) => {
746
- e.stopPropagation();
747
- toggleGroupCollapse(item.href);
748
- }}
749
- className={`flex-grow flex items-center justify-between px-2 py-0.5 text-xs font-bold rounded-md ${
750
- decodedCurrentPath === item.href ? 'bg-purple-100' : 'hover:bg-purple-100'
751
- }`}
752
- title={item.label}
753
- >
754
- <span className="truncate">
755
- <HighlightedText text={item.label} searchTerm={debouncedSearchTerm} />
756
- </span>
757
- <span className="text-yellow-600 ml-2 text-[10px] font-medium bg-yellow-50 px-2 py-0.5 rounded">
758
- {isSubdomain ? 'SUBDOMAIN' : 'DOMAIN'}
759
- </span>
760
- </button>
761
- </div>
762
- );
763
- }
764
- );
765
-
766
- // Component to render domain content (Overview, Architecture, etc.)
767
- const DomainContent = React.memo(
768
- ({
769
- item,
770
- nestingLevel = 0,
771
- className = '',
772
- isSubdomain = false,
773
- }: {
774
- item: any;
775
- nestingLevel?: number;
776
- className?: string;
777
- isSubdomain?: boolean;
778
- }) => {
779
- const marginLeft = nestingLevel > 0 ? `ml-${nestingLevel * 4}` : '';
780
- const hasEntities = item.entities && item.entities.length > 0;
781
-
782
- // Get services for this domain
783
- const domainServices = getServicesForDomain(item, filteredData['services'] || [], filteredData['domains'] || []);
784
-
785
- return (
786
- <div
787
- className={`overflow-hidden transition-[height] duration-150 ease-out ${collapsedGroups[item.href] ? 'h-0' : 'h-auto'} ${className}`}
788
- >
789
- <div className={`space-y-0.5 border-gray-200/80 border-l pl-4 mt-1 ${marginLeft ? marginLeft : 'ml-[9px]'}`}>
790
- <a
791
- href={`${item.href}`}
792
- data-active={decodedCurrentPath === item.href}
793
- className={`flex items-center px-2 py-1.5 text-xs text-gray-600 hover:bg-purple-100 rounded-md ${
794
- decodedCurrentPath === item.href ? 'bg-purple-100 ' : 'hover:bg-purple-100'
795
- }`}
796
- title={`${item.label} - Overview`}
797
- >
798
- <span className="truncate">Overview</span>
799
- </a>
800
-
801
- {isVisualizer && hasEntities && (
802
- <a
803
- href={buildUrl(`/${item.href}/entity-map`)}
804
- data-active={decodedCurrentPath === `${item.href}/entity-map`}
805
- className={`flex items-center px-2 py-1.5 text-xs text-gray-600 hover:bg-purple-100 rounded-md ${
806
- decodedCurrentPath === `${item.href}/entity-map` ? 'bg-purple-100 ' : 'hover:bg-purple-100'
807
- }`}
808
- >
809
- <span className="truncate">Entity Map</span>
810
- </a>
811
- )}
812
- {!isVisualizer && (
813
- <a
814
- href={buildUrlWithParams('/architecture/docs/services', {
815
- serviceIds: item.services.map((service: any) => service.data.id).join(','),
816
- domainId: item.id,
817
- domainName: item.name,
818
- })}
819
- data-active={window.location.href.includes(`domainId=${item.id}`)}
820
- className={`flex items-center px-2 py-1.5 text-xs text-gray-600 hover:bg-purple-100 rounded-md ${
821
- window.location.href.includes(`domainId=${item.id}`) ? 'bg-purple-100 ' : 'hover:bg-purple-100'
822
- }`}
823
- >
824
- <span className="truncate">Architecture</span>
825
- </a>
826
- )}
827
- {!isVisualizer && (
828
- <a
829
- href={buildUrl(`/docs/domains/${item.id}/language`)}
830
- data-active={decodedCurrentPath.includes(`/docs/domains/${item.id}/language`)}
831
- className={`flex items-center px-2 py-1.5 text-xs text-gray-600 hover:bg-purple-100 rounded-md ${
832
- decodedCurrentPath.includes(`/docs/domains/${item.id}/language`) ? 'bg-purple-100 ' : 'hover:bg-purple-100'
833
- }`}
834
- >
835
- <span className="truncate">Ubiquitous Language</span>
836
- </a>
837
- )}
838
-
839
- {/* Render services before entities */}
840
- {domainServices.length > 0 && (
841
- <CollapsibleGroup
842
- isCollapsed={collapsedGroups[`${item.href}-services`]}
843
- onToggle={() => toggleGroupCollapse(`${item.href}-services`)}
844
- title={
845
- <button
846
- onClick={(e) => {
847
- e.stopPropagation();
848
- toggleGroupCollapse(`${item.href}-services`);
849
- }}
850
- className="truncate underline ml-2 text-xs mb-1 py-1"
851
- >
852
- Services ({domainServices.length})
853
- </button>
854
- }
855
- >
856
- <div className="space-y-2 pl-4">
857
- {domainServices.map((service: ServiceItem) => (
858
- <ServiceItem
859
- key={service.href}
860
- item={service}
861
- decodedCurrentPath={decodedCurrentPath}
862
- collapsedGroups={collapsedGroups}
863
- toggleGroupCollapse={toggleGroupCollapse}
864
- isVisualizer={isVisualizer}
865
- searchTerm={debouncedSearchTerm}
866
- />
867
- ))}
868
- </div>
869
- </CollapsibleGroup>
870
- )}
871
-
872
- {item.entities.length > 0 && !isVisualizer && (
873
- <CollapsibleGroup
874
- isCollapsed={collapsedGroups[`${item.href}-entities`]}
875
- onToggle={() => toggleGroupCollapse(`${item.href}-entities`)}
876
- title={
877
- <button
878
- onClick={(e) => {
879
- e.stopPropagation();
880
- toggleGroupCollapse(`${item.href}-entities`);
881
- }}
882
- className="truncate underline ml-2 text-xs mb-1 py-1"
883
- >
884
- Entities ({item.entities.length})
885
- </button>
886
- }
887
- >
888
- <MessageList messages={item.entities} decodedCurrentPath={decodedCurrentPath} searchTerm={debouncedSearchTerm} />
889
- </CollapsibleGroup>
890
- )}
891
- </div>
892
- </div>
893
- );
894
- }
895
- );
896
-
897
- if (!isInitialized) return null;
898
-
899
- const hasNoResults =
900
- debouncedSearchTerm &&
901
- !filteredData['context-map']?.length &&
902
- !filteredData.domains?.length &&
903
- !filteredData.services?.length &&
904
- !filteredData.flows?.length &&
905
- !filteredData.messagesNotInService?.length;
906
-
907
- return (
908
- <nav ref={navRef} className="space-y-4 text-gray-800 px-3 py-4 overflow-auto h-full">
909
- <div
910
- className={`flex gap-2 transition-all duration-200 ${
911
- isSearchPinned ? 'sticky -top-5 z-10 bg-white shadow-md -mx-3 px-3 py-2 border-b border-gray-200' : ''
912
- }`}
913
- >
914
- <input
915
- type="text"
916
- value={searchTerm}
917
- onChange={handleSearchChange}
918
- placeholder="Quick search..."
919
- className="flex-1 p-2 text-sm rounded-md border border-gray-200 h-[30px]"
920
- />
921
- <div className="flex gap-1">
922
- <button
923
- onClick={toggleExpandCollapse}
924
- title={isExpanded ? 'Collapse All' : 'Expand All'}
925
- className="px-2 py-1 bg-gray-100 hover:bg-gray-200 rounded-md border border-gray-200 h-[30px] flex items-center justify-center"
926
- >
927
- {isExpanded ? (
928
- <ChevronDoubleUpIcon className="h-4 w-4 text-gray-600" />
929
- ) : (
930
- <ChevronDoubleDownIcon className="h-4 w-4 text-gray-600" />
931
- )}
932
- </button>
933
- <button
934
- onClick={hideSidebar}
935
- title="Hide Sidebar"
936
- className="px-2 py-1 bg-gray-100 hover:bg-gray-200 rounded-md border border-gray-200 h-[30px] flex items-center justify-center"
937
- >
938
- <PanelLeft className="h-4 w-4 text-gray-600" />
939
- </button>
940
- </div>
941
- </div>
942
- <div className="space-y-2 divide-y divide-gray-200/80">
943
- {hasNoResults ? (
944
- <NoResultsFound searchTerm={debouncedSearchTerm} />
945
- ) : (
946
- <>
947
- {/* Bounded Context Map (Visualiser only) */}
948
- {filteredData['context-map'] && filteredData.domains && filteredData.domains.length > 0 && (
949
- <div className="pt-0">
950
- <ul className="space-y-1">
951
- {filteredData['context-map'].map((item: any) => (
952
- <li key={item.href}>
953
- <a
954
- href={item.href}
955
- data-active={decodedCurrentPath === item.href}
956
- className={`flex items-center justify-between px-2 py-0.5 text-xs font-bold rounded-md ${
957
- decodedCurrentPath === item.href ? 'bg-purple-100 text-purple-900' : 'hover:bg-purple-100'
958
- }`}
959
- >
960
- <span className="truncate flex flex-col items-start">
961
- <HighlightedText text={item.label} searchTerm={debouncedSearchTerm} />
962
- <span className="text-[10px] text-gray-500 font-light">Explore integrations between domains</span>
963
- </span>
964
- <span className="text-blue-600 ml-2 text-[10px] font-medium bg-blue-50 px-2 py-0.5 rounded">DOMAINS</span>
965
- </a>
966
- </li>
967
- ))}
968
- </ul>
969
- </div>
970
- )}
971
-
972
- {/* Domains */}
973
- {filteredData['domains'] && (
974
- <div className={`${isVisualizer ? 'pt-4 pb-2' : 'p-0'}`}>
975
- <ul className="space-y-2">
976
- {getParentDomains(filteredData['domains'] || []).map((parentDomain: any) => {
977
- const subdomains = getSubdomainsForParent(parentDomain, filteredData['domains'] || []);
978
-
979
- return (
980
- <li key={parentDomain.href} className="space-y-0" data-active={decodedCurrentPath === parentDomain.href}>
981
- <DomainItem item={parentDomain} isSubdomain={false} />
982
- <DomainContent item={parentDomain} />
983
-
984
- {/* Render nested subdomains */}
985
- {subdomains.length > 0 && !collapsedGroups[parentDomain.href] && (
986
- <div className="space-y-0.5 border-gray-200/80 border-l pl-4 ml-[9px] mt-2">
987
- <CollapsibleGroup
988
- isCollapsed={collapsedGroups[`${parentDomain.href}-subdomains`]}
989
- onToggle={() => toggleGroupCollapse(`${parentDomain.href}-subdomains`)}
990
- title={
991
- <button
992
- onClick={(e) => {
993
- e.stopPropagation();
994
- toggleGroupCollapse(`${parentDomain.href}-subdomains`);
995
- }}
996
- className="truncate underline ml-2 text-xs mb-1 py-1"
997
- >
998
- Subdomains ({subdomains.length})
999
- </button>
1000
- }
1001
- >
1002
- <div className="space-y-2">
1003
- {subdomains.map((subdomain: any) => (
1004
- <div
1005
- key={subdomain.href}
1006
- className="space-y-0"
1007
- data-active={decodedCurrentPath === subdomain.href}
1008
- >
1009
- <DomainItem item={subdomain} isSubdomain={true} nestingLevel={1} />
1010
- <DomainContent item={subdomain} nestingLevel={3} className="ml-6" isSubdomain={true} />
1011
- </div>
1012
- ))}
1013
- </div>
1014
- </CollapsibleGroup>
1015
- </div>
1016
- )}
1017
- </li>
1018
- );
1019
- })}
1020
- </ul>
1021
- </div>
1022
- )}
1023
-
1024
- {/* All Services Group */}
1025
- {filteredData['services'] && filteredData['services'].length > 0 && (
1026
- <div className="pt-4 pb-2">
1027
- <CollapsibleGroup
1028
- isCollapsed={collapsedGroups['all-services-group']}
1029
- onToggle={() => toggleGroupCollapse('all-services-group')}
1030
- title={
1031
- <button
1032
- onClick={(e) => {
1033
- e.stopPropagation();
1034
- toggleGroupCollapse('all-services-group');
1035
- }}
1036
- className="flex justify-between items-center pl-2 w-full text-xs"
1037
- >
1038
- <span className="truncate text-xs font-bold">All Services ({filteredData['services'].length})</span>
1039
- <span className="ml-2 rounded bg-purple-50 px-2 py-0.5 text-[10px] font-medium text-purple-600">
1040
- SERVICES
1041
- </span>
1042
- </button>
1043
- }
1044
- >
1045
- <div className="space-y-4 border-gray-200/80 border-l pl-3 ml-[9px] mt-3">
1046
- {filteredData['services'].map((item: any) => {
1047
- // Ensure service is collapsed by default if not in collapsedGroups
1048
- if (collapsedGroups[item.href] === undefined) {
1049
- collapsedGroups[item.href] = true;
1050
- }
1051
-
1052
- return (
1053
- <ServiceItem
1054
- key={item.href}
1055
- item={item}
1056
- decodedCurrentPath={decodedCurrentPath}
1057
- collapsedGroups={collapsedGroups}
1058
- toggleGroupCollapse={toggleGroupCollapse}
1059
- isVisualizer={isVisualizer}
1060
- searchTerm={debouncedSearchTerm}
1061
- />
1062
- );
1063
- })}
1064
- </div>
1065
- </CollapsibleGroup>
1066
- </div>
1067
- )}
1068
-
1069
- {/* Flows Group */}
1070
- {filteredData['flows'] && filteredData['flows'].length > 0 && (
1071
- <div className="pt-4 pb-2">
1072
- <CollapsibleGroup
1073
- isCollapsed={collapsedGroups['flows-group']}
1074
- onToggle={() => toggleGroupCollapse('flows-group')}
1075
- title={
1076
- <button
1077
- onClick={(e) => {
1078
- e.stopPropagation();
1079
- toggleGroupCollapse('flows-group');
1080
- }}
1081
- className="flex justify-between items-center pl-2 w-full text-xs"
1082
- >
1083
- <span className="truncate text-xs font-bold">All Flows ({filteredData['flows'].length})</span>
1084
- <span className="ml-2 rounded bg-teal-50 px-2 py-0.5 text-[10px] font-medium text-teal-600">FLOWS</span>
1085
- </button>
1086
- }
1087
- >
1088
- <div className="space-y-2 border-gray-200/80 border-l pl-3 ml-[9px] mt-3">
1089
- {filteredData['flows'].map((item: any) => (
1090
- <div key={item.href} data-active={decodedCurrentPath === item.href}>
1091
- <a
1092
- href={item.href}
1093
- data-active={decodedCurrentPath === item.href}
1094
- className={`flex items-center justify-between px-2 py-0.5 text-xs font-thin rounded-md ${
1095
- decodedCurrentPath === item.href ? 'bg-purple-100 text-purple-900' : 'hover:bg-purple-100'
1096
- }`}
1097
- title={item.label}
1098
- >
1099
- <span className="truncate">
1100
- <HighlightedText text={item.label} searchTerm={debouncedSearchTerm} />
1101
- </span>
1102
- <span className={`ml-2 text-[10px] font-medium px-2 uppercase py-0.5 rounded bg-teal-50 text-teal-600`}>
1103
- FLOW
1104
- </span>
1105
- </a>
1106
- </div>
1107
- ))}
1108
- </div>
1109
- </CollapsibleGroup>
1110
- </div>
1111
- )}
1112
-
1113
- {/* Data Group */}
1114
- {filteredData['containers'] && filteredData['containers'].length > 0 && (
1115
- <div className="pt-4 pb-2">
1116
- <CollapsibleGroup
1117
- isCollapsed={collapsedGroups['data-group']}
1118
- onToggle={() => toggleGroupCollapse('data-group')}
1119
- title={
1120
- <button
1121
- onClick={(e) => {
1122
- e.stopPropagation();
1123
- toggleGroupCollapse('data-group');
1124
- }}
1125
- className="flex justify-between items-center pl-2 w-full text-xs"
1126
- >
1127
- <span className="truncate text-xs font-bold">All Data Stores ({filteredData['containers'].length})</span>
1128
- <span className="ml-2 rounded bg-blue-50 px-2 py-0.5 text-[10px] font-medium text-blue-600">DATA</span>
1129
- </button>
1130
- }
1131
- >
1132
- <div className="space-y-2 border-gray-200/80 border-l pl-3 ml-[9px] mt-3">
1133
- {filteredData['containers'].map((item: any) => (
1134
- <div key={item.href} data-active={decodedCurrentPath === item.href}>
1135
- <a
1136
- href={item.href}
1137
- data-active={decodedCurrentPath === item.href}
1138
- className={`flex items-center justify-between px-2 py-0.5 text-xs font-thin rounded-md ${
1139
- decodedCurrentPath === item.href ? 'bg-purple-100 text-purple-900' : 'hover:bg-purple-100'
1140
- }`}
1141
- title={item.label}
1142
- >
1143
- <span className="truncate">
1144
- <HighlightedText text={item.label} searchTerm={debouncedSearchTerm} />
1145
- </span>
1146
- <span className={`ml-2 text-[10px] font-medium px-2 uppercase py-0.5 rounded bg-blue-50 text-blue-600`}>
1147
- DATA
1148
- </span>
1149
- </a>
1150
- </div>
1151
- ))}
1152
- </div>
1153
- </CollapsibleGroup>
1154
- </div>
1155
- )}
1156
-
1157
- {filteredData['designs'] && filteredData['designs'].length > 0 && (
1158
- <div className="pt-4 pb-2">
1159
- <CollapsibleGroup
1160
- isCollapsed={collapsedGroups['designs-group']}
1161
- onToggle={() => toggleGroupCollapse('designs-group')}
1162
- title={
1163
- <button
1164
- onClick={(e) => {
1165
- e.stopPropagation();
1166
- toggleGroupCollapse('designs-group');
1167
- }}
1168
- className="flex justify-between items-center pl-2 w-full text-xs"
1169
- >
1170
- <span className="truncate text-xs font-bold">All Designs ({filteredData['designs'].length})</span>
1171
- <span className="ml-2 rounded bg-teal-50 px-2 py-0.5 text-[10px] font-medium text-teal-600">DESIGNS</span>
1172
- </button>
1173
- }
1174
- >
1175
- <div className="space-y-2 border-gray-200/80 border-l pl-3 ml-[9px] mt-3">
1176
- {filteredData['designs'].map((item: any) => (
1177
- <div key={item.href} data-active={decodedCurrentPath === item.href}>
1178
- <a
1179
- href={item.href}
1180
- data-active={decodedCurrentPath === item.href}
1181
- className={`flex items-center justify-between px-2 py-0.5 text-xs font-thin rounded-md ${
1182
- decodedCurrentPath === item.href ? 'bg-purple-100 text-purple-900' : 'hover:bg-purple-100'
1183
- }`}
1184
- title={item.label}
1185
- >
1186
- <span className="truncate">
1187
- <HighlightedText text={item.label} searchTerm={debouncedSearchTerm} />
1188
- </span>
1189
- <span className={`ml-2 text-[10px] font-medium px-2 uppercase py-0.5 rounded bg-teal-50 text-teal-600`}>
1190
- DESIGN
1191
- </span>
1192
- </a>
1193
- </div>
1194
- ))}
1195
- </div>
1196
- </CollapsibleGroup>
1197
- </div>
1198
- )}
1199
-
1200
- {filteredData['messagesNotInService'] && filteredData['messagesNotInService'].length > 0 && showOrphanedMessages && (
1201
- <div className="pt-4 pb-2">
1202
- <CollapsibleGroup
1203
- isCollapsed={collapsedGroups['messagesNotInService-group']}
1204
- onToggle={() => toggleGroupCollapse('messagesNotInService-group')}
1205
- title={
1206
- <button
1207
- onClick={(e) => {
1208
- e.stopPropagation();
1209
- toggleGroupCollapse('messagesNotInService-group');
1210
- }}
1211
- className="flex justify-between items-center pl-2 w-full text-xs"
1212
- >
1213
- <span className="truncate text-xs font-bold">Orphaned Messages</span>
1214
- </button>
1215
- }
1216
- >
1217
- <div className="space-y-2 border-gray-200/80 border-l pl-3 ml-[9px] mt-3">
1218
- {filteredData['messagesNotInService'].map((item: any) => (
1219
- <div key={item.href} data-active={decodedCurrentPath === item.href}>
1220
- <a
1221
- href={item.href}
1222
- data-active={decodedCurrentPath === item.href}
1223
- className={`flex items-center justify-between px-2 py-0.5 text-xs font-thin rounded-md ${
1224
- decodedCurrentPath === item.href ? 'bg-purple-100 text-purple-900' : 'hover:bg-purple-100'
1225
- }`}
1226
- title={item.label}
1227
- >
1228
- <span className="truncate">
1229
- <HighlightedText text={item.label} searchTerm={debouncedSearchTerm} />
1230
- </span>
1231
- <span
1232
- className={`ml-2 text-[10px] font-medium px-2 uppercase py-0.5 rounded ${getMessageColorByCollection(item.collection)}`}
1233
- >
1234
- {getMessageCollectionName(item.collection, item)}
1235
- </span>
1236
- </a>
1237
- </div>
1238
- ))}
1239
- </div>
1240
- </CollapsibleGroup>
1241
- </div>
1242
- )}
1243
- </>
1244
- )}
1245
- </div>
1246
- </nav>
1247
- );
1248
- };
1249
-
1250
- export default React.memo(ListViewSideBar);