@eventcatalog/core 3.12.7 → 3.12.9-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.
- 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-RZOSO7BH.js → chunk-2UUS3AQ2.js} +1 -1
- package/dist/{chunk-UFF5Q7GJ.js → chunk-EDX2LGRV.js} +1 -1
- package/dist/{chunk-2EFYBMLH.js → chunk-RJOB6XEC.js} +1 -1
- package/dist/{chunk-YO6IQXB3.js → chunk-V7YMKA4P.js} +1 -1
- package/dist/{chunk-PLQAZDJI.js → chunk-WTCJKTEF.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +1 -1
- package/dist/eventcatalog.js +5 -5
- package/dist/generate.cjs +1 -1
- package/dist/generate.js +3 -3
- package/dist/utils/cli-logger.cjs +1 -1
- package/dist/utils/cli-logger.js +2 -2
- package/eventcatalog/src/components/MDX/Design/Design.astro +2 -2
- package/eventcatalog/src/components/MDX/EntityMap/EntityMap.astro +2 -2
- package/eventcatalog/src/components/MDX/Flow/Flow.astro +2 -2
- package/eventcatalog/src/components/MDX/NodeGraph/AstroNodeGraph.tsx +104 -0
- package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.astro +2 -2
- package/eventcatalog/src/components/MDX/NodeGraph/README.md +85 -0
- package/eventcatalog/src/pages/visualiser/designs/[id]/index.astro +2 -2
- package/eventcatalog/src/utils/node-graphs/container-node-graph.ts +66 -17
- package/eventcatalog/src/utils/node-graphs/data-products-node-graph.ts +14 -5
- package/eventcatalog/src/utils/node-graphs/domains-node-graph.ts +1 -1
- package/eventcatalog/src/utils/node-graphs/message-node-graph.ts +133 -18
- package/eventcatalog/src/utils/node-graphs/services-node-graph.ts +36 -14
- package/eventcatalog/src/utils/node-graphs/utils/utils.ts +115 -4
- package/package.json +4 -4
- package/eventcatalog/src/components/MDX/NodeGraph/DownloadButton.tsx +0 -62
- package/eventcatalog/src/components/MDX/NodeGraph/Edges/AnimatedMessageEdge.tsx +0 -110
- package/eventcatalog/src/components/MDX/NodeGraph/Edges/FlowEdge.tsx +0 -96
- package/eventcatalog/src/components/MDX/NodeGraph/Edges/MultilineEdgeLabel.tsx +0 -52
- package/eventcatalog/src/components/MDX/NodeGraph/FocusMode/FocusModeContent.tsx +0 -294
- package/eventcatalog/src/components/MDX/NodeGraph/FocusMode/FocusModeNodeActions.tsx +0 -92
- package/eventcatalog/src/components/MDX/NodeGraph/FocusMode/FocusModePlaceholder.tsx +0 -26
- package/eventcatalog/src/components/MDX/NodeGraph/FocusMode/utils.ts +0 -163
- package/eventcatalog/src/components/MDX/NodeGraph/FocusModeModal.tsx +0 -99
- package/eventcatalog/src/components/MDX/NodeGraph/MermaidView.tsx +0 -242
- package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.tsx +0 -1181
- package/eventcatalog/src/components/MDX/NodeGraph/Nodes/Actor.tsx +0 -46
- package/eventcatalog/src/components/MDX/NodeGraph/Nodes/Channel.tsx +0 -55
- package/eventcatalog/src/components/MDX/NodeGraph/Nodes/Command.tsx +0 -27
- package/eventcatalog/src/components/MDX/NodeGraph/Nodes/Custom.tsx +0 -159
- package/eventcatalog/src/components/MDX/NodeGraph/Nodes/Data.tsx +0 -63
- package/eventcatalog/src/components/MDX/NodeGraph/Nodes/DataProduct.tsx +0 -132
- package/eventcatalog/src/components/MDX/NodeGraph/Nodes/Domain.tsx +0 -155
- package/eventcatalog/src/components/MDX/NodeGraph/Nodes/Entity.tsx +0 -154
- package/eventcatalog/src/components/MDX/NodeGraph/Nodes/Event.tsx +0 -29
- package/eventcatalog/src/components/MDX/NodeGraph/Nodes/ExternalSystem.tsx +0 -79
- package/eventcatalog/src/components/MDX/NodeGraph/Nodes/ExternalSystem2.tsx +0 -24
- package/eventcatalog/src/components/MDX/NodeGraph/Nodes/Flow.tsx +0 -107
- package/eventcatalog/src/components/MDX/NodeGraph/Nodes/MessageContextMenu.tsx +0 -63
- package/eventcatalog/src/components/MDX/NodeGraph/Nodes/Query.tsx +0 -28
- package/eventcatalog/src/components/MDX/NodeGraph/Nodes/Service.tsx +0 -127
- package/eventcatalog/src/components/MDX/NodeGraph/Nodes/Step.tsx +0 -64
- package/eventcatalog/src/components/MDX/NodeGraph/Nodes/User.tsx +0 -76
- package/eventcatalog/src/components/MDX/NodeGraph/Nodes/View.tsx +0 -24
- package/eventcatalog/src/components/MDX/NodeGraph/StepWalkthrough.tsx +0 -296
- package/eventcatalog/src/components/MDX/NodeGraph/StudioModal.tsx +0 -129
- package/eventcatalog/src/components/MDX/NodeGraph/VisualiserSearch.tsx +0 -258
- package/eventcatalog/src/components/MDX/NodeGraph/VisualizerDropdownContent.tsx +0 -313
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
import { useState, useCallback, useRef, useEffect, forwardRef, useImperativeHandle } from 'react';
|
|
2
|
-
import type { Node } from '@xyflow/react';
|
|
3
|
-
|
|
4
|
-
// Define interfaces for different node data structures
|
|
5
|
-
interface MessageData {
|
|
6
|
-
name: string;
|
|
7
|
-
version?: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
interface ServiceData {
|
|
11
|
-
name: string;
|
|
12
|
-
version?: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
interface DomainData {
|
|
16
|
-
name: string;
|
|
17
|
-
version?: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
interface EntityData {
|
|
21
|
-
name: string;
|
|
22
|
-
version?: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
interface NodeDataContent extends Record<string, unknown> {
|
|
26
|
-
message?: {
|
|
27
|
-
data: MessageData;
|
|
28
|
-
};
|
|
29
|
-
service?: {
|
|
30
|
-
data: ServiceData;
|
|
31
|
-
};
|
|
32
|
-
domain?: {
|
|
33
|
-
data: DomainData;
|
|
34
|
-
};
|
|
35
|
-
entity?: {
|
|
36
|
-
data: EntityData;
|
|
37
|
-
};
|
|
38
|
-
name?: string;
|
|
39
|
-
version?: string;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Extend the Node type with our custom data structure
|
|
43
|
-
type CustomNode = Node<NodeDataContent>;
|
|
44
|
-
|
|
45
|
-
interface VisualiserSearchProps {
|
|
46
|
-
nodes: CustomNode[];
|
|
47
|
-
onNodeSelect: (node: CustomNode) => void;
|
|
48
|
-
onClear: () => void;
|
|
49
|
-
onPaneClick?: () => void;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export interface VisualiserSearchRef {
|
|
53
|
-
hideSuggestions: () => void;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const VisualiserSearch = forwardRef<VisualiserSearchRef, VisualiserSearchProps>(
|
|
57
|
-
({ nodes, onNodeSelect, onClear, onPaneClick }, ref) => {
|
|
58
|
-
const [searchQuery, setSearchQuery] = useState('');
|
|
59
|
-
const [filteredSuggestions, setFilteredSuggestions] = useState<CustomNode[]>([]);
|
|
60
|
-
const [showSuggestions, setShowSuggestions] = useState(false);
|
|
61
|
-
const [selectedSuggestionIndex, setSelectedSuggestionIndex] = useState(-1);
|
|
62
|
-
const searchInputRef = useRef<HTMLInputElement>(null);
|
|
63
|
-
const containerRef = useRef<HTMLDivElement>(null);
|
|
64
|
-
|
|
65
|
-
const hideSuggestions = useCallback(() => {
|
|
66
|
-
setShowSuggestions(false);
|
|
67
|
-
setSelectedSuggestionIndex(-1);
|
|
68
|
-
}, []);
|
|
69
|
-
|
|
70
|
-
useImperativeHandle(
|
|
71
|
-
ref,
|
|
72
|
-
() => ({
|
|
73
|
-
hideSuggestions,
|
|
74
|
-
}),
|
|
75
|
-
[hideSuggestions]
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
const getNodeDisplayName = useCallback((node: CustomNode) => {
|
|
79
|
-
const name =
|
|
80
|
-
node.data?.message?.data?.name ||
|
|
81
|
-
node.data?.service?.data?.name ||
|
|
82
|
-
node.data?.domain?.data?.name ||
|
|
83
|
-
node.data?.entity?.data?.name ||
|
|
84
|
-
node.data?.name ||
|
|
85
|
-
node.id;
|
|
86
|
-
const version =
|
|
87
|
-
node.data?.message?.data?.version ||
|
|
88
|
-
node.data?.service?.data?.version ||
|
|
89
|
-
node.data?.domain?.data?.version ||
|
|
90
|
-
node.data?.entity?.data?.version ||
|
|
91
|
-
node.data?.version;
|
|
92
|
-
return version ? `${name} (v${version})` : name;
|
|
93
|
-
}, []);
|
|
94
|
-
|
|
95
|
-
const getNodeTypeColorClass = useCallback((nodeType: string) => {
|
|
96
|
-
const colorClasses: { [key: string]: string } = {
|
|
97
|
-
events: 'bg-orange-600 text-white',
|
|
98
|
-
services: 'bg-pink-600 text-white',
|
|
99
|
-
flows: 'bg-teal-600 text-white',
|
|
100
|
-
commands: 'bg-blue-600 text-white',
|
|
101
|
-
queries: 'bg-green-600 text-white',
|
|
102
|
-
channels: 'bg-gray-600 text-white',
|
|
103
|
-
domains: 'bg-yellow-500 text-white',
|
|
104
|
-
externalSystem: 'bg-pink-600 text-white',
|
|
105
|
-
actor: 'bg-yellow-500 text-white',
|
|
106
|
-
step: 'bg-gray-700 text-white',
|
|
107
|
-
user: 'bg-yellow-500 text-white',
|
|
108
|
-
custom: 'bg-gray-500 text-white',
|
|
109
|
-
};
|
|
110
|
-
return colorClasses[nodeType] || 'bg-gray-100 text-gray-700';
|
|
111
|
-
}, []);
|
|
112
|
-
|
|
113
|
-
const handleSearchChange = useCallback(
|
|
114
|
-
(event: React.ChangeEvent<HTMLInputElement>) => {
|
|
115
|
-
const query = event.target.value;
|
|
116
|
-
setSearchQuery(query);
|
|
117
|
-
|
|
118
|
-
if (query.length > 0) {
|
|
119
|
-
const filtered = nodes.filter((node) => {
|
|
120
|
-
const nodeName = getNodeDisplayName(node);
|
|
121
|
-
return nodeName.toLowerCase().includes(query.toLowerCase());
|
|
122
|
-
});
|
|
123
|
-
setFilteredSuggestions(filtered);
|
|
124
|
-
setShowSuggestions(true);
|
|
125
|
-
setSelectedSuggestionIndex(-1);
|
|
126
|
-
} else {
|
|
127
|
-
setFilteredSuggestions(nodes);
|
|
128
|
-
setShowSuggestions(true);
|
|
129
|
-
setSelectedSuggestionIndex(-1);
|
|
130
|
-
}
|
|
131
|
-
},
|
|
132
|
-
[nodes, getNodeDisplayName]
|
|
133
|
-
);
|
|
134
|
-
|
|
135
|
-
const handleSearchFocus = useCallback(() => {
|
|
136
|
-
if (searchQuery.length === 0) {
|
|
137
|
-
setFilteredSuggestions(nodes);
|
|
138
|
-
}
|
|
139
|
-
setShowSuggestions(true);
|
|
140
|
-
setSelectedSuggestionIndex(-1);
|
|
141
|
-
}, [nodes, searchQuery]);
|
|
142
|
-
|
|
143
|
-
const handleSuggestionClick = useCallback(
|
|
144
|
-
(node: CustomNode) => {
|
|
145
|
-
setSearchQuery(getNodeDisplayName(node));
|
|
146
|
-
setShowSuggestions(false);
|
|
147
|
-
onNodeSelect(node);
|
|
148
|
-
},
|
|
149
|
-
[onNodeSelect, getNodeDisplayName]
|
|
150
|
-
);
|
|
151
|
-
|
|
152
|
-
const handleSearchKeyDown = useCallback(
|
|
153
|
-
(event: React.KeyboardEvent<HTMLInputElement>) => {
|
|
154
|
-
if (!showSuggestions || filteredSuggestions.length === 0) return;
|
|
155
|
-
|
|
156
|
-
switch (event.key) {
|
|
157
|
-
case 'ArrowDown':
|
|
158
|
-
event.preventDefault();
|
|
159
|
-
setSelectedSuggestionIndex((prev) => (prev < filteredSuggestions.length - 1 ? prev + 1 : 0));
|
|
160
|
-
break;
|
|
161
|
-
case 'ArrowUp':
|
|
162
|
-
event.preventDefault();
|
|
163
|
-
setSelectedSuggestionIndex((prev) => (prev > 0 ? prev - 1 : filteredSuggestions.length - 1));
|
|
164
|
-
break;
|
|
165
|
-
case 'Enter':
|
|
166
|
-
event.preventDefault();
|
|
167
|
-
if (selectedSuggestionIndex >= 0) {
|
|
168
|
-
handleSuggestionClick(filteredSuggestions[selectedSuggestionIndex]);
|
|
169
|
-
}
|
|
170
|
-
break;
|
|
171
|
-
case 'Escape':
|
|
172
|
-
setShowSuggestions(false);
|
|
173
|
-
setSelectedSuggestionIndex(-1);
|
|
174
|
-
break;
|
|
175
|
-
}
|
|
176
|
-
},
|
|
177
|
-
[showSuggestions, filteredSuggestions, selectedSuggestionIndex, handleSuggestionClick]
|
|
178
|
-
);
|
|
179
|
-
|
|
180
|
-
const clearSearch = useCallback(() => {
|
|
181
|
-
setSearchQuery('');
|
|
182
|
-
setShowSuggestions(false);
|
|
183
|
-
setFilteredSuggestions([]);
|
|
184
|
-
setSelectedSuggestionIndex(-1);
|
|
185
|
-
onClear();
|
|
186
|
-
if (searchInputRef.current) {
|
|
187
|
-
searchInputRef.current.focus();
|
|
188
|
-
}
|
|
189
|
-
}, [onClear]);
|
|
190
|
-
|
|
191
|
-
// Close suggestions when clicking outside
|
|
192
|
-
useEffect(() => {
|
|
193
|
-
const handleClickOutside = (event: MouseEvent) => {
|
|
194
|
-
if (containerRef.current && !containerRef.current.contains(event.target as any)) {
|
|
195
|
-
setShowSuggestions(false);
|
|
196
|
-
setSelectedSuggestionIndex(-1);
|
|
197
|
-
}
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
document.addEventListener('mousedown', handleClickOutside);
|
|
201
|
-
return () => {
|
|
202
|
-
document.removeEventListener('mousedown', handleClickOutside);
|
|
203
|
-
};
|
|
204
|
-
}, []);
|
|
205
|
-
|
|
206
|
-
return (
|
|
207
|
-
<div ref={containerRef} className="w-full max-w-md mx-auto relative">
|
|
208
|
-
<div className="relative">
|
|
209
|
-
<input
|
|
210
|
-
ref={searchInputRef}
|
|
211
|
-
type="text"
|
|
212
|
-
placeholder="Search nodes..."
|
|
213
|
-
value={searchQuery}
|
|
214
|
-
onChange={handleSearchChange}
|
|
215
|
-
onKeyDown={handleSearchKeyDown}
|
|
216
|
-
onFocus={handleSearchFocus}
|
|
217
|
-
className="w-full px-4 py-2 pr-10 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-[rgb(var(--ec-accent))] focus:border-transparent"
|
|
218
|
-
/>
|
|
219
|
-
{searchQuery && (
|
|
220
|
-
<button
|
|
221
|
-
onClick={clearSearch}
|
|
222
|
-
className="absolute right-2 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
|
223
|
-
aria-label="Clear search"
|
|
224
|
-
>
|
|
225
|
-
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
226
|
-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
|
227
|
-
</svg>
|
|
228
|
-
</button>
|
|
229
|
-
)}
|
|
230
|
-
</div>
|
|
231
|
-
{showSuggestions && filteredSuggestions.length > 0 && (
|
|
232
|
-
<div className="absolute top-full left-0 right-0 mt-1 bg-white border border-gray-300 rounded-md shadow-lg z-50 max-h-60 overflow-y-auto">
|
|
233
|
-
{filteredSuggestions.map((node, index) => {
|
|
234
|
-
const nodeName = getNodeDisplayName(node);
|
|
235
|
-
const nodeType = node.type || 'unknown';
|
|
236
|
-
return (
|
|
237
|
-
<div
|
|
238
|
-
key={node.id}
|
|
239
|
-
onClick={() => handleSuggestionClick(node)}
|
|
240
|
-
className={`px-4 py-2 cursor-pointer flex items-center justify-between hover:bg-gray-100 ${
|
|
241
|
-
index === selectedSuggestionIndex ? 'bg-[rgb(var(--ec-accent-subtle))]' : ''
|
|
242
|
-
}`}
|
|
243
|
-
>
|
|
244
|
-
<span className="text-sm font-medium text-gray-900">{nodeName}</span>
|
|
245
|
-
<span className={`text-xs capitalize px-2 py-1 rounded ${getNodeTypeColorClass(nodeType)}`}>{nodeType}</span>
|
|
246
|
-
</div>
|
|
247
|
-
);
|
|
248
|
-
})}
|
|
249
|
-
</div>
|
|
250
|
-
)}
|
|
251
|
-
</div>
|
|
252
|
-
);
|
|
253
|
-
}
|
|
254
|
-
);
|
|
255
|
-
|
|
256
|
-
VisualiserSearch.displayName = 'VisualiserSearch';
|
|
257
|
-
|
|
258
|
-
export default VisualiserSearch;
|
|
@@ -1,313 +0,0 @@
|
|
|
1
|
-
import React, { type RefObject, useState } from 'react';
|
|
2
|
-
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
|
|
3
|
-
import {
|
|
4
|
-
Code,
|
|
5
|
-
Share2,
|
|
6
|
-
Search,
|
|
7
|
-
Grid3x3,
|
|
8
|
-
Maximize2,
|
|
9
|
-
Map,
|
|
10
|
-
Sparkles,
|
|
11
|
-
Zap,
|
|
12
|
-
EyeOff,
|
|
13
|
-
ExternalLink,
|
|
14
|
-
Save,
|
|
15
|
-
RotateCcw,
|
|
16
|
-
Loader2,
|
|
17
|
-
} from 'lucide-react';
|
|
18
|
-
import { DocumentArrowDownIcon, PresentationChartLineIcon } from '@heroicons/react/24/outline';
|
|
19
|
-
import type { VisualiserSearchRef } from './VisualiserSearch';
|
|
20
|
-
|
|
21
|
-
interface VisualizerDropdownContentProps {
|
|
22
|
-
isMermaidView: boolean;
|
|
23
|
-
setIsMermaidView: (value: boolean) => void;
|
|
24
|
-
animateMessages: boolean;
|
|
25
|
-
toggleAnimateMessages: () => void;
|
|
26
|
-
hideChannels: boolean;
|
|
27
|
-
toggleChannelsVisibility: () => void;
|
|
28
|
-
hasChannels: boolean;
|
|
29
|
-
showMinimap: boolean;
|
|
30
|
-
setShowMinimap: (value: boolean) => void;
|
|
31
|
-
handleFitView: () => void;
|
|
32
|
-
searchRef: RefObject<VisualiserSearchRef | null>;
|
|
33
|
-
isChatEnabled: boolean;
|
|
34
|
-
openChat: () => void;
|
|
35
|
-
handleCopyArchitectureCode: () => void;
|
|
36
|
-
handleExportVisual: () => void;
|
|
37
|
-
setIsShareModalOpen: (value: boolean) => void;
|
|
38
|
-
toggleFullScreen: () => void;
|
|
39
|
-
openStudioModal: () => void;
|
|
40
|
-
isDevMode?: boolean;
|
|
41
|
-
onSaveLayout?: () => Promise<boolean>;
|
|
42
|
-
onResetLayout?: () => Promise<boolean>;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const VisualizerDropdownContent: React.FC<VisualizerDropdownContentProps> = ({
|
|
46
|
-
isMermaidView,
|
|
47
|
-
setIsMermaidView,
|
|
48
|
-
animateMessages,
|
|
49
|
-
toggleAnimateMessages,
|
|
50
|
-
hideChannels,
|
|
51
|
-
toggleChannelsVisibility,
|
|
52
|
-
hasChannels,
|
|
53
|
-
showMinimap,
|
|
54
|
-
setShowMinimap,
|
|
55
|
-
handleFitView,
|
|
56
|
-
searchRef,
|
|
57
|
-
isChatEnabled,
|
|
58
|
-
openChat,
|
|
59
|
-
handleCopyArchitectureCode,
|
|
60
|
-
handleExportVisual,
|
|
61
|
-
setIsShareModalOpen,
|
|
62
|
-
toggleFullScreen,
|
|
63
|
-
openStudioModal,
|
|
64
|
-
isDevMode = false,
|
|
65
|
-
onSaveLayout,
|
|
66
|
-
onResetLayout,
|
|
67
|
-
}) => {
|
|
68
|
-
const [layoutStatus, setLayoutStatus] = useState<'idle' | 'saving' | 'resetting'>('idle');
|
|
69
|
-
|
|
70
|
-
const handleSaveLayout = async () => {
|
|
71
|
-
if (!onSaveLayout) return;
|
|
72
|
-
setLayoutStatus('saving');
|
|
73
|
-
await onSaveLayout();
|
|
74
|
-
setLayoutStatus('idle');
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
const handleResetLayout = async () => {
|
|
78
|
-
if (!onResetLayout) return;
|
|
79
|
-
if (!window.confirm('Reset layout to auto-positioning? This will delete your saved layout.')) {
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
setLayoutStatus('resetting');
|
|
83
|
-
const success = await onResetLayout();
|
|
84
|
-
if (success) {
|
|
85
|
-
window.location.reload();
|
|
86
|
-
} else {
|
|
87
|
-
setLayoutStatus('idle');
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
return (
|
|
92
|
-
<>
|
|
93
|
-
{/* Canvas Settings Submenu */}
|
|
94
|
-
<DropdownMenu.Sub>
|
|
95
|
-
<DropdownMenu.SubTrigger className="flex items-center px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer transition-colors gap-2 outline-none">
|
|
96
|
-
<Grid3x3 className="w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" />
|
|
97
|
-
<span className="flex-1 font-normal">Canvas</span>
|
|
98
|
-
<svg className="w-3 h-3 text-[rgb(var(--ec-page-text-muted))]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
99
|
-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
|
100
|
-
</svg>
|
|
101
|
-
</DropdownMenu.SubTrigger>
|
|
102
|
-
<DropdownMenu.Portal>
|
|
103
|
-
<DropdownMenu.SubContent
|
|
104
|
-
className="min-w-[200px] bg-[rgb(var(--ec-card-bg))] rounded-lg shadow-xl border border-[rgb(var(--ec-page-border))] py-1.5 z-[60]"
|
|
105
|
-
sideOffset={8}
|
|
106
|
-
alignOffset={-8}
|
|
107
|
-
>
|
|
108
|
-
<DropdownMenu.CheckboxItem
|
|
109
|
-
checked={isMermaidView}
|
|
110
|
-
onCheckedChange={setIsMermaidView}
|
|
111
|
-
className="flex items-center px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer transition-colors gap-2"
|
|
112
|
-
>
|
|
113
|
-
<Code className="w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" />
|
|
114
|
-
<span className="flex-1 font-normal">Render as mermaid</span>
|
|
115
|
-
<div
|
|
116
|
-
className={`w-7 h-4 rounded-full transition-all duration-200 flex-shrink-0 relative ${isMermaidView ? 'bg-[rgb(var(--ec-accent))]' : 'bg-[rgb(var(--ec-page-border))]'}`}
|
|
117
|
-
>
|
|
118
|
-
<div
|
|
119
|
-
className={`absolute top-0.5 w-3 h-3 rounded-full bg-white shadow-sm transition-all duration-200 ${isMermaidView ? 'left-3.5' : 'left-0.5'}`}
|
|
120
|
-
/>
|
|
121
|
-
</div>
|
|
122
|
-
</DropdownMenu.CheckboxItem>
|
|
123
|
-
|
|
124
|
-
<DropdownMenu.Separator className="my-1 h-px bg-[rgb(var(--ec-page-border))]" />
|
|
125
|
-
|
|
126
|
-
<DropdownMenu.CheckboxItem
|
|
127
|
-
checked={animateMessages}
|
|
128
|
-
onCheckedChange={toggleAnimateMessages}
|
|
129
|
-
className="flex items-center px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer transition-colors gap-2"
|
|
130
|
-
>
|
|
131
|
-
<Zap className="w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" />
|
|
132
|
-
<span className="flex-1 font-normal">Simulate Messages</span>
|
|
133
|
-
<div
|
|
134
|
-
className={`w-7 h-4 rounded-full transition-all duration-200 flex-shrink-0 relative ${animateMessages ? 'bg-[rgb(var(--ec-accent))]' : 'bg-[rgb(var(--ec-page-border))]'}`}
|
|
135
|
-
>
|
|
136
|
-
<div
|
|
137
|
-
className={`absolute top-0.5 w-3 h-3 rounded-full bg-white shadow-sm transition-all duration-200 ${animateMessages ? 'left-3.5' : 'left-0.5'}`}
|
|
138
|
-
/>
|
|
139
|
-
</div>
|
|
140
|
-
</DropdownMenu.CheckboxItem>
|
|
141
|
-
|
|
142
|
-
{hasChannels && (
|
|
143
|
-
<DropdownMenu.CheckboxItem
|
|
144
|
-
checked={hideChannels}
|
|
145
|
-
onCheckedChange={toggleChannelsVisibility}
|
|
146
|
-
className="flex items-center px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer transition-colors gap-2"
|
|
147
|
-
>
|
|
148
|
-
<EyeOff className="w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" />
|
|
149
|
-
<span className="flex-1 font-normal">Hide channels</span>
|
|
150
|
-
<div
|
|
151
|
-
className={`w-7 h-4 rounded-full transition-all duration-200 flex-shrink-0 relative ${hideChannels ? 'bg-[rgb(var(--ec-accent))]' : 'bg-[rgb(var(--ec-page-border))]'}`}
|
|
152
|
-
>
|
|
153
|
-
<div
|
|
154
|
-
className={`absolute top-0.5 w-3 h-3 rounded-full bg-white shadow-sm transition-all duration-200 ${hideChannels ? 'left-3.5' : 'left-0.5'}`}
|
|
155
|
-
/>
|
|
156
|
-
</div>
|
|
157
|
-
</DropdownMenu.CheckboxItem>
|
|
158
|
-
)}
|
|
159
|
-
|
|
160
|
-
<DropdownMenu.CheckboxItem
|
|
161
|
-
checked={showMinimap}
|
|
162
|
-
onCheckedChange={setShowMinimap}
|
|
163
|
-
className="flex items-center px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer transition-colors gap-2"
|
|
164
|
-
>
|
|
165
|
-
<Map className="w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" />
|
|
166
|
-
<span className="flex-1 font-normal">Show minimap</span>
|
|
167
|
-
<div
|
|
168
|
-
className={`w-7 h-4 rounded-full transition-all duration-200 flex-shrink-0 relative ${showMinimap ? 'bg-[rgb(var(--ec-accent))]' : 'bg-[rgb(var(--ec-page-border))]'}`}
|
|
169
|
-
>
|
|
170
|
-
<div
|
|
171
|
-
className={`absolute top-0.5 w-3 h-3 rounded-full bg-white shadow-sm transition-all duration-200 ${showMinimap ? 'left-3.5' : 'left-0.5'}`}
|
|
172
|
-
/>
|
|
173
|
-
</div>
|
|
174
|
-
</DropdownMenu.CheckboxItem>
|
|
175
|
-
|
|
176
|
-
<DropdownMenu.Separator className="my-1 h-px bg-[rgb(var(--ec-page-border))]" />
|
|
177
|
-
|
|
178
|
-
<DropdownMenu.Item
|
|
179
|
-
onClick={handleFitView}
|
|
180
|
-
className="px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer flex items-center gap-2 transition-colors"
|
|
181
|
-
>
|
|
182
|
-
<Maximize2 className="w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" />
|
|
183
|
-
<span className="flex-1 font-normal">Fit to view</span>
|
|
184
|
-
</DropdownMenu.Item>
|
|
185
|
-
|
|
186
|
-
<DropdownMenu.Item
|
|
187
|
-
onClick={() => {
|
|
188
|
-
searchRef.current?.hideSuggestions();
|
|
189
|
-
setTimeout(() => {
|
|
190
|
-
const searchInput = document.querySelector('input[placeholder="Search nodes..."]') as HTMLInputElement;
|
|
191
|
-
searchInput?.focus();
|
|
192
|
-
}, 50);
|
|
193
|
-
}}
|
|
194
|
-
className="px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer flex items-center gap-2 transition-colors"
|
|
195
|
-
>
|
|
196
|
-
<Search className="w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" />
|
|
197
|
-
<span className="flex-1 font-normal">Find on canvas</span>
|
|
198
|
-
</DropdownMenu.Item>
|
|
199
|
-
</DropdownMenu.SubContent>
|
|
200
|
-
</DropdownMenu.Portal>
|
|
201
|
-
</DropdownMenu.Sub>
|
|
202
|
-
|
|
203
|
-
{/* Dev Mode: Layout Submenu */}
|
|
204
|
-
{isDevMode && onSaveLayout && (
|
|
205
|
-
<DropdownMenu.Sub>
|
|
206
|
-
<DropdownMenu.SubTrigger className="flex items-center px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer transition-colors gap-2 outline-none">
|
|
207
|
-
<Save className="w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" />
|
|
208
|
-
<span className="flex-1 font-normal">Layout</span>
|
|
209
|
-
<span className="text-[10px] text-amber-600 font-medium">DEV</span>
|
|
210
|
-
<svg className="w-3 h-3 text-[rgb(var(--ec-page-text-muted))]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
211
|
-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
|
212
|
-
</svg>
|
|
213
|
-
</DropdownMenu.SubTrigger>
|
|
214
|
-
<DropdownMenu.Portal>
|
|
215
|
-
<DropdownMenu.SubContent
|
|
216
|
-
className="min-w-[180px] bg-[rgb(var(--ec-card-bg))] rounded-lg shadow-xl border border-[rgb(var(--ec-page-border))] py-1.5 z-[60]"
|
|
217
|
-
sideOffset={8}
|
|
218
|
-
alignOffset={-8}
|
|
219
|
-
>
|
|
220
|
-
<DropdownMenu.Item
|
|
221
|
-
onClick={handleSaveLayout}
|
|
222
|
-
disabled={layoutStatus !== 'idle'}
|
|
223
|
-
className="px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer flex items-center gap-2 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
|
224
|
-
>
|
|
225
|
-
{layoutStatus === 'saving' ? (
|
|
226
|
-
<Loader2 className="w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0 animate-spin" />
|
|
227
|
-
) : (
|
|
228
|
-
<Save className="w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" />
|
|
229
|
-
)}
|
|
230
|
-
<span className="flex-1 font-normal">{layoutStatus === 'saving' ? 'Saving...' : 'Save Layout'}</span>
|
|
231
|
-
</DropdownMenu.Item>
|
|
232
|
-
<DropdownMenu.Item
|
|
233
|
-
onClick={handleResetLayout}
|
|
234
|
-
disabled={layoutStatus !== 'idle'}
|
|
235
|
-
className="px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer flex items-center gap-2 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
|
236
|
-
>
|
|
237
|
-
{layoutStatus === 'resetting' ? (
|
|
238
|
-
<Loader2 className="w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0 animate-spin" />
|
|
239
|
-
) : (
|
|
240
|
-
<RotateCcw className="w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" />
|
|
241
|
-
)}
|
|
242
|
-
<span className="flex-1 font-normal">{layoutStatus === 'resetting' ? 'Resetting...' : 'Reset Layout'}</span>
|
|
243
|
-
</DropdownMenu.Item>
|
|
244
|
-
</DropdownMenu.SubContent>
|
|
245
|
-
</DropdownMenu.Portal>
|
|
246
|
-
</DropdownMenu.Sub>
|
|
247
|
-
)}
|
|
248
|
-
|
|
249
|
-
{/* Ask AI */}
|
|
250
|
-
{isChatEnabled && (
|
|
251
|
-
<>
|
|
252
|
-
<DropdownMenu.Separator className="my-1 h-px bg-[rgb(var(--ec-page-border))]" />
|
|
253
|
-
<DropdownMenu.Item
|
|
254
|
-
onClick={openChat}
|
|
255
|
-
className="px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer flex items-center gap-2 transition-colors"
|
|
256
|
-
>
|
|
257
|
-
<Sparkles className="w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" />
|
|
258
|
-
<span className="flex-1 font-normal">Ask a question</span>
|
|
259
|
-
</DropdownMenu.Item>
|
|
260
|
-
</>
|
|
261
|
-
)}
|
|
262
|
-
|
|
263
|
-
{/* Export Items */}
|
|
264
|
-
<DropdownMenu.Separator className="my-1 h-px bg-[rgb(var(--ec-page-border))]" />
|
|
265
|
-
<DropdownMenu.Item
|
|
266
|
-
onClick={handleCopyArchitectureCode}
|
|
267
|
-
className="px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer flex items-center gap-2 transition-colors"
|
|
268
|
-
>
|
|
269
|
-
<Code className="w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" />
|
|
270
|
-
<span className="flex-1 font-normal">Copy as mermaid</span>
|
|
271
|
-
</DropdownMenu.Item>
|
|
272
|
-
|
|
273
|
-
<DropdownMenu.Item
|
|
274
|
-
onClick={handleExportVisual}
|
|
275
|
-
className="px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer flex items-center gap-2 transition-colors"
|
|
276
|
-
>
|
|
277
|
-
<DocumentArrowDownIcon className="w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" />
|
|
278
|
-
<span className="flex-1 font-normal">Export image</span>
|
|
279
|
-
</DropdownMenu.Item>
|
|
280
|
-
|
|
281
|
-
{/* Share Link */}
|
|
282
|
-
<DropdownMenu.Item
|
|
283
|
-
onClick={() => setIsShareModalOpen(true)}
|
|
284
|
-
className="px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer flex items-center gap-2 transition-colors"
|
|
285
|
-
>
|
|
286
|
-
<Share2 className="w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" />
|
|
287
|
-
<span className="flex-1 font-normal">Share Link</span>
|
|
288
|
-
</DropdownMenu.Item>
|
|
289
|
-
|
|
290
|
-
{/* Start Presentation */}
|
|
291
|
-
<DropdownMenu.Separator className="my-1 h-px bg-[rgb(var(--ec-page-border))]" />
|
|
292
|
-
<DropdownMenu.Item
|
|
293
|
-
onClick={toggleFullScreen}
|
|
294
|
-
className="px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer flex items-center gap-2 transition-colors"
|
|
295
|
-
>
|
|
296
|
-
<PresentationChartLineIcon className="w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" />
|
|
297
|
-
<span className="flex-1 font-normal">Start Presentation</span>
|
|
298
|
-
</DropdownMenu.Item>
|
|
299
|
-
|
|
300
|
-
{/* Open in EventCatalog Studio */}
|
|
301
|
-
<DropdownMenu.Separator className="my-1 h-px bg-[rgb(var(--ec-page-border))]" />
|
|
302
|
-
<DropdownMenu.Item
|
|
303
|
-
onClick={openStudioModal}
|
|
304
|
-
className="px-3 py-2 text-xs text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-accent-subtle)/0.3)] cursor-pointer flex items-center gap-2 transition-colors"
|
|
305
|
-
>
|
|
306
|
-
<ExternalLink className="w-3.5 h-3.5 text-[rgb(var(--ec-page-text-muted))] flex-shrink-0" />
|
|
307
|
-
<span className="flex-1 font-normal">Open in EventCatalog Studio</span>
|
|
308
|
-
</DropdownMenu.Item>
|
|
309
|
-
</>
|
|
310
|
-
);
|
|
311
|
-
};
|
|
312
|
-
|
|
313
|
-
export default VisualizerDropdownContent;
|