@eventcatalog/core 2.26.1 → 2.28.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.
- package/dist/analytics/analytics.cjs +1 -1
- package/dist/analytics/analytics.js +2 -2
- package/dist/analytics/log-build.cjs +1 -1
- package/dist/analytics/log-build.js +3 -3
- package/dist/{chunk-TOTPAQ4C.js → chunk-KGWJTMWU.js} +1 -1
- package/dist/{chunk-M7ERKXSB.js → chunk-KYGD25IE.js} +1 -1
- package/dist/{chunk-JCGLXXSE.js → chunk-RCPEAVRY.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +1 -1
- package/dist/eventcatalog.config.d.cts +1 -4
- package/dist/eventcatalog.config.d.ts +1 -4
- package/dist/eventcatalog.js +3 -3
- package/eventcatalog/astro.config.mjs +0 -3
- package/eventcatalog/public/logo.svg +14 -0
- package/eventcatalog/src/components/Grids/DomainGrid.tsx +233 -0
- package/eventcatalog/src/components/Grids/MessageGrid.tsx +457 -0
- package/eventcatalog/src/components/Grids/ServiceGrid.tsx +362 -0
- package/eventcatalog/src/components/Grids/components.tsx +170 -0
- package/eventcatalog/src/components/Grids/utils.tsx +38 -0
- package/eventcatalog/src/components/SideNav/ListViewSideBar/components/CollapsibleGroup.tsx +46 -0
- package/eventcatalog/src/components/SideNav/ListViewSideBar/components/MessageList.tsx +31 -0
- package/eventcatalog/src/components/SideNav/ListViewSideBar/index.tsx +390 -0
- package/eventcatalog/src/components/SideNav/ListViewSideBar/types.ts +48 -0
- package/eventcatalog/src/components/SideNav/ListViewSideBar/utils.ts +103 -0
- package/eventcatalog/src/components/SideNav/SideNav.astro +8 -7
- package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +34 -22
- package/eventcatalog/src/pages/architecture/[type]/index.astro +14 -0
- package/eventcatalog/src/pages/architecture/architecture.astro +81 -0
- package/eventcatalog/src/pages/architecture/docs/[type]/index.astro +14 -0
- package/eventcatalog/src/pages/index.astro +237 -72
- package/eventcatalog/src/utils/url-builder.ts +20 -0
- package/eventcatalog/tailwind.config.mjs +11 -0
- package/package.json +2 -1
- package/eventcatalog/src/components/SideNav/CatalogResourcesSideBar/getCatalogResources.ts +0 -65
- package/eventcatalog/src/components/SideNav/CatalogResourcesSideBar/index.tsx +0 -138
- package/eventcatalog/src/components/SideNav/CatalogResourcesSideBar/styles.css +0 -8
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import React, { useState, useEffect, useRef, useMemo, useCallback } from 'react';
|
|
2
|
+
import { ChevronDownIcon } 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 type { MessageItem, ServiceItem, ListViewSideBarProps } from './types';
|
|
7
|
+
const STORAGE_KEY = 'EventCatalog:catalogSidebarCollapsedGroups';
|
|
8
|
+
const DEBOUNCE_DELAY = 300; // 300ms debounce delay
|
|
9
|
+
|
|
10
|
+
export const getMessageColorByCollection = (collection: string) => {
|
|
11
|
+
if (collection === 'commands') return 'bg-blue-50 text-blue-600';
|
|
12
|
+
if (collection === 'queries') return 'bg-green-50 text-green-600';
|
|
13
|
+
if (collection === 'events') return 'bg-orange-50 text-orange-600';
|
|
14
|
+
return 'text-gray-600';
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const getMessageCollectionName = (collection: string) => {
|
|
18
|
+
if (collection === 'commands') return 'Command';
|
|
19
|
+
if (collection === 'queries') return 'Query';
|
|
20
|
+
if (collection === 'events') return 'Event';
|
|
21
|
+
return collection.slice(0, collection.length - 1).toUpperCase();
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const NoResultsFound = React.memo(({ searchTerm }: { searchTerm: string }) => (
|
|
25
|
+
<div className="px-4 py-6 text-center">
|
|
26
|
+
<div className="text-gray-400 text-sm mb-2">No results found for "{searchTerm}"</div>
|
|
27
|
+
<div className="text-gray-400 text-xs">
|
|
28
|
+
Try:
|
|
29
|
+
<ul className="mt-2 space-y-1 text-left list-disc pl-4">
|
|
30
|
+
<li>Checking for typos</li>
|
|
31
|
+
<li>Using fewer keywords</li>
|
|
32
|
+
<li>Using more general terms</li>
|
|
33
|
+
</ul>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
));
|
|
37
|
+
|
|
38
|
+
const ServiceItem = React.memo(
|
|
39
|
+
({
|
|
40
|
+
item,
|
|
41
|
+
decodedCurrentPath,
|
|
42
|
+
collapsedGroups,
|
|
43
|
+
toggleGroupCollapse,
|
|
44
|
+
}: {
|
|
45
|
+
item: ServiceItem;
|
|
46
|
+
decodedCurrentPath: string;
|
|
47
|
+
collapsedGroups: { [key: string]: boolean };
|
|
48
|
+
toggleGroupCollapse: (group: string) => void;
|
|
49
|
+
}) => (
|
|
50
|
+
<CollapsibleGroup
|
|
51
|
+
isCollapsed={collapsedGroups[item.href]}
|
|
52
|
+
onToggle={() => toggleGroupCollapse(item.href)}
|
|
53
|
+
title={
|
|
54
|
+
<button
|
|
55
|
+
onClick={(e) => {
|
|
56
|
+
e.stopPropagation();
|
|
57
|
+
toggleGroupCollapse(item.href);
|
|
58
|
+
}}
|
|
59
|
+
className="flex justify-between items-center pl-2 w-full text-xs"
|
|
60
|
+
>
|
|
61
|
+
<span className="truncate text-xs font-bold">{item.label}</span>
|
|
62
|
+
<span className="text-purple-600 ml-2 text-[10px] font-medium bg-purple-50 px-2 py-0.5 rounded">SERVICE</span>
|
|
63
|
+
</button>
|
|
64
|
+
}
|
|
65
|
+
>
|
|
66
|
+
<div className="space-y-0 border-gray-200/80 border-l pl-3 ml-[9px] mt-1">
|
|
67
|
+
<a
|
|
68
|
+
href={`${item.href}`}
|
|
69
|
+
className={`flex items-center px-2 py-1.5 text-xs text-gray-600 hover:bg-purple-100 rounded-md ${
|
|
70
|
+
decodedCurrentPath === item.href ? 'bg-purple-100' : 'hover:bg-purple-100'
|
|
71
|
+
}`}
|
|
72
|
+
>
|
|
73
|
+
<span className="truncate">Overview</span>
|
|
74
|
+
</a>
|
|
75
|
+
<a
|
|
76
|
+
href={buildUrlWithParams('/architecture/docs/messages', {
|
|
77
|
+
serviceName: item.name,
|
|
78
|
+
serviceId: item.id,
|
|
79
|
+
})}
|
|
80
|
+
className={`flex items-center px-2 py-1.5 text-xs text-gray-600 hover:bg-purple-100 rounded-md ${
|
|
81
|
+
window.location.href.includes(`serviceId=${item.id}`) ? 'bg-purple-100' : 'hover:bg-purple-100'
|
|
82
|
+
}`}
|
|
83
|
+
>
|
|
84
|
+
<span className="truncate">Architecture</span>
|
|
85
|
+
</a>
|
|
86
|
+
|
|
87
|
+
<CollapsibleGroup
|
|
88
|
+
isCollapsed={collapsedGroups[`${item.href}-receives`]}
|
|
89
|
+
onToggle={() => toggleGroupCollapse(`${item.href}-receives`)}
|
|
90
|
+
title={
|
|
91
|
+
<button
|
|
92
|
+
onClick={(e) => {
|
|
93
|
+
e.stopPropagation();
|
|
94
|
+
toggleGroupCollapse(`${item.href}-receives`);
|
|
95
|
+
}}
|
|
96
|
+
className="truncate underline ml-2 text-xs mb-1 py-1"
|
|
97
|
+
>
|
|
98
|
+
Receives messages ({item.receives.length})
|
|
99
|
+
</button>
|
|
100
|
+
}
|
|
101
|
+
>
|
|
102
|
+
<MessageList messages={item.receives} decodedCurrentPath={decodedCurrentPath} />
|
|
103
|
+
</CollapsibleGroup>
|
|
104
|
+
|
|
105
|
+
<CollapsibleGroup
|
|
106
|
+
isCollapsed={collapsedGroups[`${item.href}-sends`]}
|
|
107
|
+
onToggle={() => toggleGroupCollapse(`${item.href}-sends`)}
|
|
108
|
+
title={
|
|
109
|
+
<button
|
|
110
|
+
onClick={(e) => {
|
|
111
|
+
e.stopPropagation();
|
|
112
|
+
toggleGroupCollapse(`${item.href}-sends`);
|
|
113
|
+
}}
|
|
114
|
+
className="truncate underline ml-2 text-xs mb-1 py-1"
|
|
115
|
+
>
|
|
116
|
+
Sends messages ({item.sends.length})
|
|
117
|
+
</button>
|
|
118
|
+
}
|
|
119
|
+
>
|
|
120
|
+
<MessageList messages={item.sends} decodedCurrentPath={decodedCurrentPath} />
|
|
121
|
+
</CollapsibleGroup>
|
|
122
|
+
</div>
|
|
123
|
+
</CollapsibleGroup>
|
|
124
|
+
)
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const ListViewSideBar: React.FC<ListViewSideBarProps> = ({ resources, currentPath }) => {
|
|
128
|
+
const navRef = useRef<HTMLElement>(null);
|
|
129
|
+
const [data] = useState(resources);
|
|
130
|
+
const [searchTerm, setSearchTerm] = useState('');
|
|
131
|
+
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState('');
|
|
132
|
+
const [isInitialized, setIsInitialized] = useState(false);
|
|
133
|
+
const [collapsedGroups, setCollapsedGroups] = useState<{ [key: string]: boolean }>(() => {
|
|
134
|
+
if (typeof window !== 'undefined') {
|
|
135
|
+
const saved = window.localStorage.getItem(STORAGE_KEY);
|
|
136
|
+
setIsInitialized(true);
|
|
137
|
+
return saved ? JSON.parse(saved) : {};
|
|
138
|
+
}
|
|
139
|
+
return {};
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const decodedCurrentPath = decodeURIComponent(currentPath);
|
|
143
|
+
|
|
144
|
+
// Debounce search term updates
|
|
145
|
+
useEffect(() => {
|
|
146
|
+
const timer = setTimeout(() => {
|
|
147
|
+
setDebouncedSearchTerm(searchTerm.toLowerCase());
|
|
148
|
+
}, DEBOUNCE_DELAY);
|
|
149
|
+
|
|
150
|
+
return () => clearTimeout(timer);
|
|
151
|
+
}, [searchTerm]);
|
|
152
|
+
|
|
153
|
+
// Filter data based on search term
|
|
154
|
+
const filteredData = useMemo(() => {
|
|
155
|
+
if (!debouncedSearchTerm) return data;
|
|
156
|
+
|
|
157
|
+
const filterItem = (item: { label: string }) => {
|
|
158
|
+
return item.label.toLowerCase().includes(debouncedSearchTerm);
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const filterMessages = (messages: MessageItem[]) => {
|
|
162
|
+
return messages.filter((message) => message.data.name.toLowerCase().includes(debouncedSearchTerm));
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
domains: data.domains?.filter(filterItem) || [],
|
|
167
|
+
services:
|
|
168
|
+
data.services
|
|
169
|
+
?.map((service: ServiceItem) => ({
|
|
170
|
+
...service,
|
|
171
|
+
sends: filterMessages(service.sends),
|
|
172
|
+
receives: filterMessages(service.receives),
|
|
173
|
+
isVisible:
|
|
174
|
+
filterItem(service) ||
|
|
175
|
+
service.sends.some((msg: MessageItem) => msg.data.name.toLowerCase().includes(debouncedSearchTerm)) ||
|
|
176
|
+
service.receives.some((msg: MessageItem) => msg.data.name.toLowerCase().includes(debouncedSearchTerm)),
|
|
177
|
+
}))
|
|
178
|
+
.filter((service: ServiceItem & { isVisible: boolean }) => service.isVisible) || [],
|
|
179
|
+
flows: data.flows?.filter(filterItem) || [],
|
|
180
|
+
messagesNotInService:
|
|
181
|
+
data.messagesNotInService?.filter((msg: MessageItem) => msg.label.toLowerCase().includes(debouncedSearchTerm)) || [],
|
|
182
|
+
};
|
|
183
|
+
}, [data, debouncedSearchTerm]);
|
|
184
|
+
|
|
185
|
+
// Auto-expand groups when searching
|
|
186
|
+
useEffect(() => {
|
|
187
|
+
if (debouncedSearchTerm) {
|
|
188
|
+
// Expand all groups when searching
|
|
189
|
+
const newCollapsedState = { ...collapsedGroups };
|
|
190
|
+
Object.keys(newCollapsedState).forEach((key) => {
|
|
191
|
+
newCollapsedState[key] = false;
|
|
192
|
+
});
|
|
193
|
+
setCollapsedGroups(newCollapsedState);
|
|
194
|
+
}
|
|
195
|
+
}, [debouncedSearchTerm]);
|
|
196
|
+
|
|
197
|
+
// Store collapsed groups in local storage
|
|
198
|
+
useEffect(() => {
|
|
199
|
+
if (typeof window !== 'undefined') {
|
|
200
|
+
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(collapsedGroups));
|
|
201
|
+
}
|
|
202
|
+
}, [collapsedGroups]);
|
|
203
|
+
|
|
204
|
+
// If we find a data-active element, scroll to it on mount
|
|
205
|
+
useEffect(() => {
|
|
206
|
+
const activeElement = document.querySelector('[data-active="true"]');
|
|
207
|
+
if (activeElement) {
|
|
208
|
+
// Add y offset to the scroll position
|
|
209
|
+
activeElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
210
|
+
}
|
|
211
|
+
}, []);
|
|
212
|
+
|
|
213
|
+
const toggleGroupCollapse = useCallback((group: string) => {
|
|
214
|
+
setCollapsedGroups((prev) => ({
|
|
215
|
+
...prev,
|
|
216
|
+
[group]: !prev[group],
|
|
217
|
+
}));
|
|
218
|
+
}, []);
|
|
219
|
+
|
|
220
|
+
const handleSearchChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
|
221
|
+
setSearchTerm(e.target.value);
|
|
222
|
+
}, []);
|
|
223
|
+
|
|
224
|
+
if (!isInitialized) return null;
|
|
225
|
+
|
|
226
|
+
const hasNoResults =
|
|
227
|
+
debouncedSearchTerm &&
|
|
228
|
+
!filteredData.domains?.length &&
|
|
229
|
+
!filteredData.services?.length &&
|
|
230
|
+
!filteredData.flows?.length &&
|
|
231
|
+
!filteredData.messagesNotInService?.length;
|
|
232
|
+
|
|
233
|
+
return (
|
|
234
|
+
<nav ref={navRef} className="space-y-4 text-gray-800 px-3 py-4">
|
|
235
|
+
<input
|
|
236
|
+
type="text"
|
|
237
|
+
value={searchTerm}
|
|
238
|
+
onChange={handleSearchChange}
|
|
239
|
+
placeholder="Quick search..."
|
|
240
|
+
className="w-full p-2 text-sm rounded-md border border-gray-200 h-[30px]"
|
|
241
|
+
/>
|
|
242
|
+
<div className="space-y-2 divide-y divide-gray-200/80">
|
|
243
|
+
{hasNoResults ? (
|
|
244
|
+
<NoResultsFound searchTerm={debouncedSearchTerm} />
|
|
245
|
+
) : (
|
|
246
|
+
<>
|
|
247
|
+
{/* Domains */}
|
|
248
|
+
{filteredData['domains'] && (
|
|
249
|
+
<div>
|
|
250
|
+
<ul className="space-y-2">
|
|
251
|
+
{filteredData['domains'].map((item: any) => (
|
|
252
|
+
<li key={item.href} className="space-y-0" data-active={decodedCurrentPath === item.href}>
|
|
253
|
+
<div className="flex items-center">
|
|
254
|
+
<button
|
|
255
|
+
onClick={(e) => {
|
|
256
|
+
e.stopPropagation();
|
|
257
|
+
toggleGroupCollapse(item.href);
|
|
258
|
+
}}
|
|
259
|
+
className="p-1 hover:bg-gray-100 rounded-md"
|
|
260
|
+
>
|
|
261
|
+
<div className={`transition-transform duration-150 ${collapsedGroups[item.href] ? '' : 'rotate-180'}`}>
|
|
262
|
+
<ChevronDownIcon className="h-3 w-3 text-gray-500" />
|
|
263
|
+
</div>
|
|
264
|
+
</button>
|
|
265
|
+
<button
|
|
266
|
+
onClick={(e) => {
|
|
267
|
+
e.stopPropagation();
|
|
268
|
+
toggleGroupCollapse(item.href);
|
|
269
|
+
}}
|
|
270
|
+
className={`flex-grow flex items-center justify-between px-2 py-0.5 text-xs font-bold rounded-md ${
|
|
271
|
+
decodedCurrentPath === item.href
|
|
272
|
+
}`}
|
|
273
|
+
>
|
|
274
|
+
<span className="truncate">{item.label}</span>
|
|
275
|
+
<span className="text-yellow-600 ml-2 text-[10px] font-medium bg-yellow-50 px-2 py-0.5 rounded">
|
|
276
|
+
DOMAIN
|
|
277
|
+
</span>
|
|
278
|
+
</button>
|
|
279
|
+
</div>
|
|
280
|
+
<div
|
|
281
|
+
className={`overflow-hidden transition-[height] duration-150 ease-out ${
|
|
282
|
+
collapsedGroups[item.href] ? 'h-0' : 'h-auto'
|
|
283
|
+
}`}
|
|
284
|
+
>
|
|
285
|
+
<div className="space-y-0 border-gray-200/80 border-l pl-4 ml-[9px] mt-1">
|
|
286
|
+
<a
|
|
287
|
+
href={`${item.href}`}
|
|
288
|
+
className={`flex items-center px-2 py-1.5 text-xs text-gray-600 hover:bg-purple-100 rounded-md ${
|
|
289
|
+
decodedCurrentPath === item.href ? 'bg-purple-100 ' : 'hover:bg-purple-100'
|
|
290
|
+
}`}
|
|
291
|
+
>
|
|
292
|
+
<span className="truncate">Overview</span>
|
|
293
|
+
</a>
|
|
294
|
+
<a
|
|
295
|
+
href={buildUrlWithParams('/architecture/docs/services', {
|
|
296
|
+
serviceIds: item.services.map((service: any) => service.data.id).join(','),
|
|
297
|
+
domainId: item.id,
|
|
298
|
+
domainName: item.name,
|
|
299
|
+
})}
|
|
300
|
+
className={`flex items-center px-2 py-1.5 text-xs text-gray-600 hover:bg-purple-100 rounded-md ${
|
|
301
|
+
window.location.href.includes(`domainId=${item.id}`) ? 'bg-purple-100 ' : 'hover:bg-purple-100'
|
|
302
|
+
}`}
|
|
303
|
+
>
|
|
304
|
+
<span className="truncate">Architecture</span>
|
|
305
|
+
</a>
|
|
306
|
+
<a
|
|
307
|
+
href={buildUrl(`/docs/domains/${item.id}/language`)}
|
|
308
|
+
className={`flex items-center px-2 py-1.5 text-xs text-gray-600 hover:bg-purple-100 rounded-md ${
|
|
309
|
+
decodedCurrentPath.includes(`/docs/domains/${item.id}/language`)
|
|
310
|
+
? 'bg-purple-100 '
|
|
311
|
+
: 'hover:bg-purple-100'
|
|
312
|
+
}`}
|
|
313
|
+
>
|
|
314
|
+
<span className="truncate">Ubiquitous Language</span>
|
|
315
|
+
</a>
|
|
316
|
+
</div>
|
|
317
|
+
</div>
|
|
318
|
+
</li>
|
|
319
|
+
))}
|
|
320
|
+
</ul>
|
|
321
|
+
</div>
|
|
322
|
+
)}
|
|
323
|
+
|
|
324
|
+
{filteredData['services'] && (
|
|
325
|
+
<div className="pt-4 pb-2">
|
|
326
|
+
<ul className="space-y-4">
|
|
327
|
+
{filteredData['services'].map((item: any) => (
|
|
328
|
+
<ServiceItem
|
|
329
|
+
key={item.href}
|
|
330
|
+
item={item}
|
|
331
|
+
decodedCurrentPath={decodedCurrentPath}
|
|
332
|
+
collapsedGroups={collapsedGroups}
|
|
333
|
+
toggleGroupCollapse={toggleGroupCollapse}
|
|
334
|
+
/>
|
|
335
|
+
))}
|
|
336
|
+
</ul>
|
|
337
|
+
</div>
|
|
338
|
+
)}
|
|
339
|
+
|
|
340
|
+
{filteredData['messagesNotInService'] && (
|
|
341
|
+
<div className="pt-4 pb-2">
|
|
342
|
+
<ul className="space-y-4">
|
|
343
|
+
{filteredData['messagesNotInService'].map((item: any) => (
|
|
344
|
+
<li key={item.href} className="space-y-0" data-active={decodedCurrentPath === item.href}>
|
|
345
|
+
<a
|
|
346
|
+
href={item.href}
|
|
347
|
+
className={`flex items-center justify-between px-2 py-0.5 text-xs font-bold rounded-md ${
|
|
348
|
+
decodedCurrentPath === item.href ? 'bg-purple-100 text-purple-900' : 'hover:bg-purple-100'
|
|
349
|
+
}`}
|
|
350
|
+
>
|
|
351
|
+
<span className="truncate">{item.label}</span>
|
|
352
|
+
<span
|
|
353
|
+
className={`ml-2 text-[10px] font-medium px-2 uppercase py-0.5 rounded ${getMessageColorByCollection(item.collection)}`}
|
|
354
|
+
>
|
|
355
|
+
{getMessageCollectionName(item.collection)}
|
|
356
|
+
</span>
|
|
357
|
+
</a>
|
|
358
|
+
</li>
|
|
359
|
+
))}
|
|
360
|
+
</ul>
|
|
361
|
+
</div>
|
|
362
|
+
)}
|
|
363
|
+
|
|
364
|
+
{filteredData['flows'] && (
|
|
365
|
+
<div className="pt-4 pb-2">
|
|
366
|
+
<ul className="space-y-4">
|
|
367
|
+
{filteredData['flows'].map((item: any) => (
|
|
368
|
+
<li key={item.href} className="space-y-0" data-active={decodedCurrentPath === item.href}>
|
|
369
|
+
<a
|
|
370
|
+
href={item.href}
|
|
371
|
+
className={`flex items-center justify-between px-2 py-0.5 text-xs font-bold rounded-md ${
|
|
372
|
+
decodedCurrentPath === item.href ? 'bg-cyan-100 text-cyan-900' : 'hover:bg-purple-100'
|
|
373
|
+
}`}
|
|
374
|
+
>
|
|
375
|
+
<span className="truncate">{item.label}</span>
|
|
376
|
+
<span className="text-cyan-600 ml-2 text-[10px] font-medium bg-cyan-50 px-2 py-0.5 rounded">FLOW</span>
|
|
377
|
+
</a>
|
|
378
|
+
</li>
|
|
379
|
+
))}
|
|
380
|
+
</ul>
|
|
381
|
+
</div>
|
|
382
|
+
)}
|
|
383
|
+
</>
|
|
384
|
+
)}
|
|
385
|
+
</div>
|
|
386
|
+
</nav>
|
|
387
|
+
);
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
export default React.memo(ListViewSideBar);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export interface MessageItem {
|
|
2
|
+
href: string;
|
|
3
|
+
label: string;
|
|
4
|
+
service: string;
|
|
5
|
+
id: string;
|
|
6
|
+
direction: 'sends' | 'receives';
|
|
7
|
+
type: 'command' | 'query' | 'event';
|
|
8
|
+
data: {
|
|
9
|
+
name: string;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ServiceItem {
|
|
14
|
+
href: string;
|
|
15
|
+
label: string;
|
|
16
|
+
name: string;
|
|
17
|
+
id: string;
|
|
18
|
+
sends: MessageItem[];
|
|
19
|
+
receives: MessageItem[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface DomainItem {
|
|
23
|
+
href: string;
|
|
24
|
+
label: string;
|
|
25
|
+
id: string;
|
|
26
|
+
name: string;
|
|
27
|
+
services: any[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface FlowItem {
|
|
31
|
+
href: string;
|
|
32
|
+
label: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface Resources {
|
|
36
|
+
domains?: DomainItem[];
|
|
37
|
+
services?: ServiceItem[];
|
|
38
|
+
flows?: FlowItem[];
|
|
39
|
+
messagesNotInService?: MessageItem[];
|
|
40
|
+
commands?: MessageItem[];
|
|
41
|
+
queries?: MessageItem[];
|
|
42
|
+
events?: MessageItem[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface ListViewSideBarProps {
|
|
46
|
+
resources: Resources;
|
|
47
|
+
currentPath: string;
|
|
48
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { isCollectionVisibleInCatalog } from '@eventcatalog';
|
|
2
|
+
import { buildUrl } from '@utils/url-builder';
|
|
3
|
+
import { getChannels } from '@utils/channels';
|
|
4
|
+
import { getDomains } from '@utils/collections/domains';
|
|
5
|
+
import { getFlows } from '@utils/collections/flows';
|
|
6
|
+
import { getServices } from '@utils/collections/services';
|
|
7
|
+
import { getCommands } from '@utils/commands';
|
|
8
|
+
import { getEvents } from '@utils/events';
|
|
9
|
+
import { getQueries } from '@utils/queries';
|
|
10
|
+
|
|
11
|
+
export async function getResourcesForNavigation({ currentPath }: { currentPath: string }) {
|
|
12
|
+
const events = await getEvents({ getAllVersions: false });
|
|
13
|
+
const commands = await getCommands({ getAllVersions: false });
|
|
14
|
+
const queries = await getQueries({ getAllVersions: false });
|
|
15
|
+
const services = await getServices({ getAllVersions: false });
|
|
16
|
+
const domains = await getDomains({ getAllVersions: false });
|
|
17
|
+
const channels = await getChannels({ getAllVersions: false });
|
|
18
|
+
const flows = await getFlows({ getAllVersions: false });
|
|
19
|
+
|
|
20
|
+
const messages = [...events, ...commands, ...queries];
|
|
21
|
+
|
|
22
|
+
// messages that are not in a service (sends or receives)
|
|
23
|
+
const messagesNotInService = messages.filter(
|
|
24
|
+
(message) =>
|
|
25
|
+
!services.some(
|
|
26
|
+
(service) =>
|
|
27
|
+
service.data?.sends?.some((send: any) => send.data.id === message.data.id) ||
|
|
28
|
+
service.data?.receives?.some((receive: any) => receive.data.id === message.data.id)
|
|
29
|
+
)
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const route = currentPath.includes('visualiser') ? 'visualiser' : 'docs';
|
|
33
|
+
|
|
34
|
+
// Just the domains for now.
|
|
35
|
+
const allDataAsSideNav = [...domains, ...services, ...flows, ...channels].reduce((acc, item) => {
|
|
36
|
+
const title = item.collection;
|
|
37
|
+
const group = acc[title] || [];
|
|
38
|
+
|
|
39
|
+
const servicesCount = item.collection === 'domains' ? item.data.services?.length || 0 : 0;
|
|
40
|
+
const sends = item.collection === 'services' ? item.data.sends || null : null;
|
|
41
|
+
const receives = item.collection === 'services' ? item.data.receives || null : null;
|
|
42
|
+
|
|
43
|
+
// Add href to the sends and receives
|
|
44
|
+
const sendsWithHref = sends?.map((send: any) => ({
|
|
45
|
+
...send,
|
|
46
|
+
href: buildUrl(`/${route}/${send.collection}/${send.data.id}/${send.data.version}`),
|
|
47
|
+
}));
|
|
48
|
+
const receivesWithHref = receives?.map((receive: any) => ({
|
|
49
|
+
...receive,
|
|
50
|
+
href: buildUrl(`/${route}/${receive.collection}/${receive.data.id}/${receive.data.version}`),
|
|
51
|
+
}));
|
|
52
|
+
|
|
53
|
+
const navigationItem = {
|
|
54
|
+
label: item.data.name,
|
|
55
|
+
version: item.data.version,
|
|
56
|
+
// items: item.collection === 'users' ? [] : item.headings,
|
|
57
|
+
visible: isCollectionVisibleInCatalog(item.collection),
|
|
58
|
+
// @ts-ignore
|
|
59
|
+
href: item.data.version
|
|
60
|
+
? // @ts-ignore
|
|
61
|
+
buildUrl(`/${route}/${item.collection}/${item.data.id}/${item.data.version}`)
|
|
62
|
+
: buildUrl(`/${route}/${item.collection}/${item.data.id}`),
|
|
63
|
+
collection: item.collection,
|
|
64
|
+
servicesCount,
|
|
65
|
+
id: item.data.id,
|
|
66
|
+
name: item.data.name,
|
|
67
|
+
services: item.collection === 'domains' ? item.data.services : null,
|
|
68
|
+
sends: sendsWithHref,
|
|
69
|
+
receives: receivesWithHref,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
group.push(navigationItem);
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
...acc,
|
|
76
|
+
[title]: group,
|
|
77
|
+
};
|
|
78
|
+
}, {} as any);
|
|
79
|
+
|
|
80
|
+
// Add messagesNotInService
|
|
81
|
+
const messagesNotInServiceAsSideNav = messagesNotInService.map((item) => ({
|
|
82
|
+
label: item.data.name,
|
|
83
|
+
version: item.data.version,
|
|
84
|
+
id: item.data.id,
|
|
85
|
+
name: item.data.name,
|
|
86
|
+
href: buildUrl(`/${route}/${item.collection}/${item.data.id}/${item.data.version}`),
|
|
87
|
+
collection: item.collection,
|
|
88
|
+
}));
|
|
89
|
+
|
|
90
|
+
const sideNav = {
|
|
91
|
+
...(currentPath.includes('visualiser')
|
|
92
|
+
? {
|
|
93
|
+
'bounded context map': [
|
|
94
|
+
{ label: 'Domain map', href: buildUrl('/visualiser/context-map'), collection: 'bounded-context-map' },
|
|
95
|
+
],
|
|
96
|
+
}
|
|
97
|
+
: {}),
|
|
98
|
+
...allDataAsSideNav,
|
|
99
|
+
messagesNotInService: messagesNotInServiceAsSideNav,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
return sideNav;
|
|
103
|
+
}
|
|
@@ -3,29 +3,30 @@ import type { HTMLAttributes } from 'astro/types';
|
|
|
3
3
|
import config from '@config';
|
|
4
4
|
|
|
5
5
|
// FlatView
|
|
6
|
-
import
|
|
7
|
-
import { getCatalogResources } from './CatalogResourcesSideBar/getCatalogResources';
|
|
6
|
+
import { getResourcesForNavigation as getListViewResources } from './ListViewSideBar/utils';
|
|
8
7
|
|
|
9
8
|
// TreeView
|
|
10
9
|
import { SideNavTreeView } from './TreeView';
|
|
11
10
|
import { getTreeView } from './TreeView/getTreeView';
|
|
12
11
|
|
|
12
|
+
import ListViewSideBar from './ListViewSideBar';
|
|
13
|
+
|
|
13
14
|
interface Props extends Omit<HTMLAttributes<'div'>, 'children'> {}
|
|
14
15
|
|
|
15
16
|
const currentPath = Astro.url.pathname;
|
|
16
17
|
|
|
17
18
|
let props;
|
|
18
19
|
|
|
19
|
-
const SIDENAV_TYPE = config?.docs?.sidebar?.type ?? '
|
|
20
|
+
const SIDENAV_TYPE = config?.docs?.sidebar?.type ?? 'LIST_VIEW';
|
|
20
21
|
|
|
21
|
-
if (SIDENAV_TYPE === '
|
|
22
|
-
props = await getCatalogResources({ currentPath });
|
|
23
|
-
} else if (SIDENAV_TYPE === 'TREE_VIEW') {
|
|
22
|
+
if (SIDENAV_TYPE === 'TREE_VIEW') {
|
|
24
23
|
props = getTreeView({ projectDir: process.env.PROJECT_DIR!, currentPath });
|
|
24
|
+
} else if (SIDENAV_TYPE === 'LIST_VIEW') {
|
|
25
|
+
props = await getListViewResources({ currentPath });
|
|
25
26
|
}
|
|
26
27
|
---
|
|
27
28
|
|
|
28
29
|
<div {...Astro.props}>
|
|
29
|
-
{SIDENAV_TYPE === '
|
|
30
|
+
{SIDENAV_TYPE === 'LIST_VIEW' && <ListViewSideBar resources={props} currentPath={currentPath} client:only />}
|
|
30
31
|
{SIDENAV_TYPE === 'TREE_VIEW' && <SideNavTreeView client:only transition:persist tree={props} />}
|
|
31
32
|
</div>
|
|
@@ -5,10 +5,11 @@ interface Props {
|
|
|
5
5
|
description?: string;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
import { BookOpenText, Workflow, TableProperties, House, BookUser, MessageSquare, BotMessageSquare } from 'lucide-react';
|
|
8
|
+
import { BookOpenText, Workflow, TableProperties, House, BookUser, MessageSquare, BotMessageSquare, Users } from 'lucide-react';
|
|
9
9
|
import Header from '../components/Header.astro';
|
|
10
10
|
import SEO from '../components/Seo.astro';
|
|
11
11
|
import SideNav from '../components/SideNav/SideNav.astro';
|
|
12
|
+
// import '@fontsource/inter';
|
|
12
13
|
|
|
13
14
|
import { getCommands } from '@utils/commands';
|
|
14
15
|
import { getDomains } from '@utils/collections/domains';
|
|
@@ -65,7 +66,7 @@ const navigationItems = [
|
|
|
65
66
|
label: 'Documentation',
|
|
66
67
|
icon: BookOpenText,
|
|
67
68
|
href: catalogHasDefaultLandingPageForDocs ? buildUrl('/docs') : getDefaultUrl('docs', '/docs'),
|
|
68
|
-
current: currentPath.includes('/docs'),
|
|
69
|
+
current: currentPath.includes('/docs') || currentPath.includes('/architecture/docs/'),
|
|
69
70
|
sidebar: true,
|
|
70
71
|
},
|
|
71
72
|
{
|
|
@@ -86,12 +87,21 @@ const navigationItems = [
|
|
|
86
87
|
},
|
|
87
88
|
{
|
|
88
89
|
id: '/directory',
|
|
89
|
-
label: '
|
|
90
|
-
icon:
|
|
90
|
+
label: 'Users & Teams',
|
|
91
|
+
icon: Users,
|
|
91
92
|
href: buildUrl('/directory/users'),
|
|
92
93
|
current: currentPath.includes('/directory'),
|
|
93
94
|
sidebar: false,
|
|
94
95
|
},
|
|
96
|
+
{
|
|
97
|
+
id: '/architecture',
|
|
98
|
+
label: 'Architecture',
|
|
99
|
+
icon: BookUser,
|
|
100
|
+
href: buildUrl('/architecture/domains'),
|
|
101
|
+
current: currentPath.includes('/architecture/'),
|
|
102
|
+
sidebar: false,
|
|
103
|
+
hidden: true,
|
|
104
|
+
},
|
|
95
105
|
{
|
|
96
106
|
id: '/chat',
|
|
97
107
|
label: 'AI Assistant',
|
|
@@ -139,23 +149,25 @@ const canPageBeEmbedded = process.env.ENABLE_EMBED === 'true';
|
|
|
139
149
|
<nav class="flex flex-col h-[84vh] justify-between">
|
|
140
150
|
<div class="flex flex-col items-center flex-1 space-y-8">
|
|
141
151
|
{
|
|
142
|
-
navigationItems
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
152
|
+
navigationItems
|
|
153
|
+
.filter((item) => !item.hidden)
|
|
154
|
+
.map((item) => {
|
|
155
|
+
return (
|
|
156
|
+
<a
|
|
157
|
+
id={item.id}
|
|
158
|
+
data-role="nav-item"
|
|
159
|
+
href={item.href}
|
|
160
|
+
class={`p-1.5 inline-block transition-colors duration-200 rounded-lg ${item.current ? 'text-white bg-gradient-to-b from-purple-500 to-purple-700' : 'hover:bg-gradient-to-r hover:from-purple-500 hover:to-purple-700 hover:text-white text-gray-700'}`}
|
|
161
|
+
>
|
|
162
|
+
<div class="has-tooltip">
|
|
163
|
+
<span class="tooltip rounded shadow-lg p-1 text-xs bg-gradient-to-l from-purple-500 to-purple-700 text-white ml-10">
|
|
164
|
+
{item.label}
|
|
165
|
+
</span>
|
|
166
|
+
<item.icon className="h-6 w-6 " />
|
|
167
|
+
</div>
|
|
168
|
+
</a>
|
|
169
|
+
);
|
|
170
|
+
})
|
|
159
171
|
}
|
|
160
172
|
</div>
|
|
161
173
|
</nav>
|
|
@@ -163,7 +175,7 @@ const canPageBeEmbedded = process.env.ENABLE_EMBED === 'true';
|
|
|
163
175
|
|
|
164
176
|
<SideNav
|
|
165
177
|
id="sidebar"
|
|
166
|
-
class={`sidebar-transition h-content overflow-y-auto bg-white border-r border-gray-100 w-
|
|
178
|
+
class={`sidebar-transition h-content overflow-y-auto bg-white border-r border-gray-100 w-80 ml-16 ${showSideBarOnLoad ? 'block' : 'hidden'}`}
|
|
167
179
|
/>
|
|
168
180
|
</aside>
|
|
169
181
|
<main
|