@principal-ai/principal-view-react 0.14.30 → 0.14.31
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/components/SequenceDiagramRenderer.d.ts +2 -0
- package/dist/components/SequenceDiagramRenderer.d.ts.map +1 -1
- package/dist/components/SequenceDiagramRenderer.js +85 -39
- package/dist/components/SequenceDiagramRenderer.js.map +1 -1
- package/dist/components/WorkflowSequenceDiagram.d.ts +3 -1
- package/dist/components/WorkflowSequenceDiagram.d.ts.map +1 -1
- package/dist/components/WorkflowSequenceDiagram.js +3 -2
- package/dist/components/WorkflowSequenceDiagram.js.map +1 -1
- package/package.json +1 -1
- package/src/components/SequenceDiagramRenderer.tsx +114 -43
- package/src/components/WorkflowSequenceDiagram.tsx +5 -0
- package/src/stories/WorkflowSequenceDiagram.stories.tsx +777 -0
|
@@ -0,0 +1,777 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
import { WorkflowSequenceDiagram } from '../components/WorkflowSequenceDiagram';
|
|
4
|
+
import type { WorkflowScenario, Canvas } from '@principal-ai/principal-view-core';
|
|
5
|
+
import { ThemeProvider, defaultEditorTheme } from '@principal-ade/industry-theme';
|
|
6
|
+
|
|
7
|
+
const meta = {
|
|
8
|
+
title: 'Components/WorkflowSequenceDiagram',
|
|
9
|
+
component: WorkflowSequenceDiagram,
|
|
10
|
+
parameters: {
|
|
11
|
+
layout: 'fullscreen',
|
|
12
|
+
docs: {
|
|
13
|
+
description: {
|
|
14
|
+
component: `
|
|
15
|
+
# WorkflowSequenceDiagram Component
|
|
16
|
+
|
|
17
|
+
A wrapper around \`SequenceDiagramRenderer\` that simplifies working with workflow scenarios.
|
|
18
|
+
Automatically converts workflow scenarios to sequence diagram format and optionally extracts
|
|
19
|
+
participant metadata from a canvas.
|
|
20
|
+
|
|
21
|
+
## Key Features
|
|
22
|
+
|
|
23
|
+
- **Simplified API**: Just pass a workflow scenario instead of manually creating events/edges
|
|
24
|
+
- **Canvas Integration**: Optionally provide a canvas to extract scope and participant metadata
|
|
25
|
+
- **Move vs Transform Events**: Automatically distinguishes between cross-participant (move) and internal (transform) events
|
|
26
|
+
- **Clickable Labels**: Edge labels are fully clickable and highlight when selected (no dots needed!)
|
|
27
|
+
- **Event Selection**: Click any label to select it - the label changes color to show selection
|
|
28
|
+
- **Empty State Handling**: Gracefully handles scenarios with no events
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
\`\`\`tsx
|
|
33
|
+
<WorkflowSequenceDiagram
|
|
34
|
+
scenario={workflowScenario}
|
|
35
|
+
canvas={otelCanvas}
|
|
36
|
+
height={600}
|
|
37
|
+
onEventIndexChange={(index) => console.log('Selected event:', index)}
|
|
38
|
+
/>
|
|
39
|
+
\`\`\`
|
|
40
|
+
`,
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
tags: ['autodocs'],
|
|
45
|
+
decorators: [
|
|
46
|
+
(Story) => (
|
|
47
|
+
<ThemeProvider theme={defaultEditorTheme}>
|
|
48
|
+
<div style={{ padding: 20, height: '100vh', boxSizing: 'border-box' }}>
|
|
49
|
+
<Story />
|
|
50
|
+
</div>
|
|
51
|
+
</ThemeProvider>
|
|
52
|
+
),
|
|
53
|
+
],
|
|
54
|
+
} satisfies Meta<typeof WorkflowSequenceDiagram>;
|
|
55
|
+
|
|
56
|
+
export default meta;
|
|
57
|
+
type Story = StoryObj<typeof meta>;
|
|
58
|
+
|
|
59
|
+
// ============================================================================
|
|
60
|
+
// Sample Scenarios
|
|
61
|
+
// ============================================================================
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Simple authentication workflow
|
|
65
|
+
*/
|
|
66
|
+
const simpleAuthScenario: WorkflowScenario = {
|
|
67
|
+
id: 'simple-auth',
|
|
68
|
+
name: 'Simple Authentication',
|
|
69
|
+
description: 'Basic authentication flow with validation',
|
|
70
|
+
template: {
|
|
71
|
+
events: {
|
|
72
|
+
'auth.request.received': {},
|
|
73
|
+
'auth.validation.started': {},
|
|
74
|
+
'database.user.lookup': {},
|
|
75
|
+
'auth.validation.completed': {},
|
|
76
|
+
'auth.token.generated': {},
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* E-commerce order processing workflow
|
|
83
|
+
*/
|
|
84
|
+
const orderProcessingScenario: WorkflowScenario = {
|
|
85
|
+
id: 'order-processing',
|
|
86
|
+
name: 'Order Processing',
|
|
87
|
+
description: 'Complete order processing with payment and inventory',
|
|
88
|
+
template: {
|
|
89
|
+
events: {
|
|
90
|
+
'api.order.received': {},
|
|
91
|
+
'auth.session.validate': {},
|
|
92
|
+
'order.validation.started': {},
|
|
93
|
+
'inventory.check.availability': {},
|
|
94
|
+
'inventory.reserve.items': {},
|
|
95
|
+
'order.validation.completed': {},
|
|
96
|
+
'payment.charge.initiated': {},
|
|
97
|
+
'payment.processor.authorize': {},
|
|
98
|
+
'payment.charge.completed': {},
|
|
99
|
+
'order.fulfillment.started': {},
|
|
100
|
+
'notification.email.sent': {},
|
|
101
|
+
'api.response.sent': {},
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Microservices workflow with multiple participants
|
|
108
|
+
*/
|
|
109
|
+
const microservicesScenario: WorkflowScenario = {
|
|
110
|
+
id: 'microservices-flow',
|
|
111
|
+
name: 'Microservices Communication',
|
|
112
|
+
description: 'Inter-service communication pattern',
|
|
113
|
+
template: {
|
|
114
|
+
events: {
|
|
115
|
+
'gateway.request.received': {},
|
|
116
|
+
'gateway.route.determined': {},
|
|
117
|
+
'userservice.user.fetch': {},
|
|
118
|
+
'userservice.database.query': {},
|
|
119
|
+
'userservice.response.prepared': {},
|
|
120
|
+
'gateway.response.aggregated': {},
|
|
121
|
+
'cache.data.stored': {},
|
|
122
|
+
'gateway.response.sent': {},
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Error handling workflow
|
|
129
|
+
*/
|
|
130
|
+
const errorHandlingScenario: WorkflowScenario = {
|
|
131
|
+
id: 'error-handling',
|
|
132
|
+
name: 'Error Handling Flow',
|
|
133
|
+
description: 'Workflow demonstrating error handling and recovery',
|
|
134
|
+
template: {
|
|
135
|
+
events: {
|
|
136
|
+
'api.request.received': {},
|
|
137
|
+
'service.processing.started': {},
|
|
138
|
+
'service.validation.failed': {},
|
|
139
|
+
'service.error.logged': {},
|
|
140
|
+
'notification.alert.sent': {},
|
|
141
|
+
'api.error.response.sent': {},
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Empty scenario for testing empty state
|
|
148
|
+
*/
|
|
149
|
+
const emptyScenario: WorkflowScenario = {
|
|
150
|
+
id: 'empty',
|
|
151
|
+
name: 'Empty Workflow',
|
|
152
|
+
description: 'Scenario with no events',
|
|
153
|
+
template: {
|
|
154
|
+
events: {},
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Sample canvas with OTEL event nodes for metadata extraction
|
|
160
|
+
*/
|
|
161
|
+
const sampleCanvas: Canvas = {
|
|
162
|
+
nodes: [
|
|
163
|
+
{
|
|
164
|
+
id: 'auth-validation-started',
|
|
165
|
+
type: 'otel-event',
|
|
166
|
+
x: 0,
|
|
167
|
+
y: 0,
|
|
168
|
+
width: 200,
|
|
169
|
+
height: 60,
|
|
170
|
+
event: { name: 'auth.validation.started' },
|
|
171
|
+
otel: { scope: 'auth' },
|
|
172
|
+
label: 'Start Validation',
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
id: 'database-lookup',
|
|
176
|
+
type: 'otel-event',
|
|
177
|
+
x: 0,
|
|
178
|
+
y: 100,
|
|
179
|
+
width: 200,
|
|
180
|
+
height: 60,
|
|
181
|
+
event: { name: 'database.user.lookup' },
|
|
182
|
+
otel: { scope: 'database' },
|
|
183
|
+
label: 'Lookup User',
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
id: 'auth-validation-completed',
|
|
187
|
+
type: 'otel-event',
|
|
188
|
+
x: 0,
|
|
189
|
+
y: 200,
|
|
190
|
+
width: 200,
|
|
191
|
+
height: 60,
|
|
192
|
+
event: { name: 'auth.validation.completed' },
|
|
193
|
+
otel: { scope: 'auth' },
|
|
194
|
+
label: 'Validation Complete',
|
|
195
|
+
},
|
|
196
|
+
],
|
|
197
|
+
edges: [],
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
// ============================================================================
|
|
201
|
+
// Stories
|
|
202
|
+
// ============================================================================
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Diagnostic story to test click functionality
|
|
206
|
+
* Shows click feedback with colored labels (no dots!)
|
|
207
|
+
* Verifies the label text matches the selected event (no off-by-one errors)
|
|
208
|
+
*/
|
|
209
|
+
export const ClickDiagnostic: Story = {
|
|
210
|
+
render: () => {
|
|
211
|
+
const [selectedIndex, setSelectedIndex] = useState<number | undefined>(undefined);
|
|
212
|
+
const [clickCount, setClickCount] = useState(0);
|
|
213
|
+
const [lastClickTime, setLastClickTime] = useState<string>('Never');
|
|
214
|
+
|
|
215
|
+
const handleClick = (index: number) => {
|
|
216
|
+
console.log('Click detected! Event index:', index);
|
|
217
|
+
setSelectedIndex(index);
|
|
218
|
+
setClickCount(prev => prev + 1);
|
|
219
|
+
setLastClickTime(new Date().toLocaleTimeString());
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const eventNames = Object.keys(simpleAuthScenario.template.events);
|
|
223
|
+
|
|
224
|
+
return (
|
|
225
|
+
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
|
226
|
+
<div
|
|
227
|
+
style={{
|
|
228
|
+
padding: '16px',
|
|
229
|
+
background: '#1a1a1a',
|
|
230
|
+
color: '#00ff00',
|
|
231
|
+
fontFamily: 'monospace',
|
|
232
|
+
borderBottom: '2px solid #00ff00',
|
|
233
|
+
marginBottom: 16,
|
|
234
|
+
}}
|
|
235
|
+
>
|
|
236
|
+
<h3 style={{ margin: '0 0 12px 0', color: '#00ff00' }}>🔍 CLICK DIAGNOSTIC MODE</h3>
|
|
237
|
+
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8, fontSize: 13 }}>
|
|
238
|
+
<div>Total Clicks: <strong>{clickCount}</strong></div>
|
|
239
|
+
<div>Last Click: <strong>{lastClickTime}</strong></div>
|
|
240
|
+
<div>Selected Index: <strong>{selectedIndex !== undefined ? selectedIndex : 'None'}</strong></div>
|
|
241
|
+
<div>Selected Event: <strong>{selectedIndex !== undefined ? eventNames[selectedIndex] : 'None'}</strong></div>
|
|
242
|
+
</div>
|
|
243
|
+
<div style={{ marginTop: 12, padding: '8px', background: '#2a2a2a', borderRadius: 4 }}>
|
|
244
|
+
💡 <strong>Click any label</strong> - it will highlight with colored background!
|
|
245
|
+
Watch the label change color and verify it matches the event shown above.
|
|
246
|
+
</div>
|
|
247
|
+
{selectedIndex !== undefined && (
|
|
248
|
+
<div style={{ marginTop: 8, padding: '8px', background: '#1e5a1e', borderRadius: 4, fontSize: 14 }}>
|
|
249
|
+
✅ <strong>Selected:</strong> "{eventNames[selectedIndex]?.split('.').pop()}" (the highlighted label should match!)
|
|
250
|
+
</div>
|
|
251
|
+
)}
|
|
252
|
+
</div>
|
|
253
|
+
<div style={{ flex: 1 }}>
|
|
254
|
+
<WorkflowSequenceDiagram
|
|
255
|
+
scenario={simpleAuthScenario}
|
|
256
|
+
height="100%"
|
|
257
|
+
selectedEventIndex={selectedIndex}
|
|
258
|
+
onEventIndexChange={handleClick}
|
|
259
|
+
layoutOptions={{
|
|
260
|
+
namespaceStrategy: 'first',
|
|
261
|
+
eventSpacing: 100,
|
|
262
|
+
}}
|
|
263
|
+
/>
|
|
264
|
+
</div>
|
|
265
|
+
</div>
|
|
266
|
+
);
|
|
267
|
+
},
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Basic workflow scenario without canvas
|
|
272
|
+
*/
|
|
273
|
+
export const BasicScenario: Story = {
|
|
274
|
+
args: {
|
|
275
|
+
scenario: simpleAuthScenario,
|
|
276
|
+
height: 500,
|
|
277
|
+
layoutOptions: {
|
|
278
|
+
namespaceStrategy: 'first',
|
|
279
|
+
eventSpacing: 80,
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Workflow with canvas metadata for enhanced labels
|
|
286
|
+
*/
|
|
287
|
+
export const WithCanvasMetadata: Story = {
|
|
288
|
+
args: {
|
|
289
|
+
scenario: simpleAuthScenario,
|
|
290
|
+
canvas: sampleCanvas,
|
|
291
|
+
height: 500,
|
|
292
|
+
layoutOptions: {
|
|
293
|
+
namespaceStrategy: 'first',
|
|
294
|
+
eventSpacing: 80,
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Complex workflow with many events and participants
|
|
301
|
+
*/
|
|
302
|
+
export const ComplexWorkflow: Story = {
|
|
303
|
+
args: {
|
|
304
|
+
scenario: orderProcessingScenario,
|
|
305
|
+
height: 700,
|
|
306
|
+
layoutOptions: {
|
|
307
|
+
namespaceStrategy: 'first',
|
|
308
|
+
laneWidth: 200,
|
|
309
|
+
eventSpacing: 70,
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Microservices communication pattern
|
|
316
|
+
*/
|
|
317
|
+
export const MicroservicesPattern: Story = {
|
|
318
|
+
args: {
|
|
319
|
+
scenario: microservicesScenario,
|
|
320
|
+
height: 600,
|
|
321
|
+
layoutOptions: {
|
|
322
|
+
namespaceStrategy: 'first',
|
|
323
|
+
laneWidth: 220,
|
|
324
|
+
eventSpacing: 75,
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Error handling workflow
|
|
331
|
+
*/
|
|
332
|
+
export const ErrorHandling: Story = {
|
|
333
|
+
args: {
|
|
334
|
+
scenario: errorHandlingScenario,
|
|
335
|
+
height: 500,
|
|
336
|
+
layoutOptions: {
|
|
337
|
+
namespaceStrategy: 'first',
|
|
338
|
+
eventSpacing: 80,
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Empty scenario showing graceful handling of no events
|
|
345
|
+
*/
|
|
346
|
+
export const EmptyState: Story = {
|
|
347
|
+
args: {
|
|
348
|
+
scenario: emptyScenario,
|
|
349
|
+
height: 300,
|
|
350
|
+
},
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Interactive example with event selection
|
|
355
|
+
* Click on any event in the sequence diagram to select it
|
|
356
|
+
* NOTE: Event click areas are small (14x14px by default) - use larger nodes for better UX
|
|
357
|
+
*/
|
|
358
|
+
export const WithEventSelection: Story = {
|
|
359
|
+
render: () => {
|
|
360
|
+
const [selectedIndex, setSelectedIndex] = useState<number | undefined>(undefined);
|
|
361
|
+
|
|
362
|
+
// Get event names from the scenario
|
|
363
|
+
const eventNames = Object.keys(simpleAuthScenario.template.events);
|
|
364
|
+
const selectedEventName = selectedIndex !== undefined ? eventNames[selectedIndex] : null;
|
|
365
|
+
|
|
366
|
+
return (
|
|
367
|
+
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
|
368
|
+
<div
|
|
369
|
+
style={{
|
|
370
|
+
padding: '16px',
|
|
371
|
+
background: '#2c3e50',
|
|
372
|
+
color: '#ecf0f1',
|
|
373
|
+
borderBottom: '3px solid #3498db',
|
|
374
|
+
marginBottom: 16,
|
|
375
|
+
}}
|
|
376
|
+
>
|
|
377
|
+
<div style={{ fontSize: 16, marginBottom: 8 }}>
|
|
378
|
+
<strong>💡 Click on any event dot in the diagram below</strong>
|
|
379
|
+
</div>
|
|
380
|
+
<div style={{ fontSize: 14, opacity: 0.9 }}>
|
|
381
|
+
<strong>Selected Event:</strong>{' '}
|
|
382
|
+
{selectedIndex !== undefined ? (
|
|
383
|
+
<span style={{ color: '#3498db', fontFamily: 'monospace' }}>
|
|
384
|
+
[{selectedIndex}] {selectedEventName}
|
|
385
|
+
</span>
|
|
386
|
+
) : (
|
|
387
|
+
<span style={{ opacity: 0.6 }}>None - Click an event to select</span>
|
|
388
|
+
)}
|
|
389
|
+
</div>
|
|
390
|
+
{selectedIndex !== undefined && (
|
|
391
|
+
<button
|
|
392
|
+
onClick={() => setSelectedIndex(undefined)}
|
|
393
|
+
style={{
|
|
394
|
+
marginTop: 8,
|
|
395
|
+
padding: '6px 16px',
|
|
396
|
+
cursor: 'pointer',
|
|
397
|
+
background: '#e74c3c',
|
|
398
|
+
color: 'white',
|
|
399
|
+
border: 'none',
|
|
400
|
+
borderRadius: 4,
|
|
401
|
+
fontSize: 13,
|
|
402
|
+
}}
|
|
403
|
+
>
|
|
404
|
+
Clear Selection
|
|
405
|
+
</button>
|
|
406
|
+
)}
|
|
407
|
+
</div>
|
|
408
|
+
<div style={{ flex: 1 }}>
|
|
409
|
+
<WorkflowSequenceDiagram
|
|
410
|
+
scenario={simpleAuthScenario}
|
|
411
|
+
height="100%"
|
|
412
|
+
selectedEventIndex={selectedIndex}
|
|
413
|
+
onEventIndexChange={setSelectedIndex}
|
|
414
|
+
layoutOptions={{
|
|
415
|
+
namespaceStrategy: 'first',
|
|
416
|
+
eventSpacing: 80,
|
|
417
|
+
nodeWidth: 14,
|
|
418
|
+
nodeHeight: 14,
|
|
419
|
+
}}
|
|
420
|
+
/>
|
|
421
|
+
</div>
|
|
422
|
+
</div>
|
|
423
|
+
);
|
|
424
|
+
},
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Interactive selection example
|
|
429
|
+
* Click labels to see them highlight with colored background
|
|
430
|
+
*/
|
|
431
|
+
export const InteractiveSelection: Story = {
|
|
432
|
+
render: () => {
|
|
433
|
+
const [selectedIndex, setSelectedIndex] = useState<number | undefined>(0);
|
|
434
|
+
|
|
435
|
+
// Get event names from the scenario
|
|
436
|
+
const eventNames = Object.keys(orderProcessingScenario.template.events);
|
|
437
|
+
const selectedEventName = selectedIndex !== undefined ? eventNames[selectedIndex] : null;
|
|
438
|
+
|
|
439
|
+
return (
|
|
440
|
+
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
|
441
|
+
<div
|
|
442
|
+
style={{
|
|
443
|
+
padding: '16px',
|
|
444
|
+
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
|
445
|
+
color: 'white',
|
|
446
|
+
borderBottom: '4px solid #5a67d8',
|
|
447
|
+
marginBottom: 16,
|
|
448
|
+
}}
|
|
449
|
+
>
|
|
450
|
+
<div style={{ fontSize: 16, marginBottom: 8 }}>
|
|
451
|
+
<strong>✨ Clickable Labels Demo</strong>
|
|
452
|
+
</div>
|
|
453
|
+
<div style={{ fontSize: 14, marginBottom: 8 }}>
|
|
454
|
+
Labels highlight with colored background when selected - clean and intuitive!
|
|
455
|
+
</div>
|
|
456
|
+
<div style={{ fontSize: 14, opacity: 0.9 }}>
|
|
457
|
+
<strong>Selected:</strong>{' '}
|
|
458
|
+
{selectedIndex !== undefined ? (
|
|
459
|
+
<span style={{ color: '#ffd700', fontFamily: 'monospace', fontWeight: 'bold' }}>
|
|
460
|
+
Event {selectedIndex + 1} - {selectedEventName}
|
|
461
|
+
</span>
|
|
462
|
+
) : (
|
|
463
|
+
<span style={{ opacity: 0.7 }}>Click any label</span>
|
|
464
|
+
)}
|
|
465
|
+
</div>
|
|
466
|
+
</div>
|
|
467
|
+
<div style={{ flex: 1 }}>
|
|
468
|
+
<WorkflowSequenceDiagram
|
|
469
|
+
scenario={orderProcessingScenario}
|
|
470
|
+
height="100%"
|
|
471
|
+
selectedEventIndex={selectedIndex}
|
|
472
|
+
onEventIndexChange={setSelectedIndex}
|
|
473
|
+
layoutOptions={{
|
|
474
|
+
namespaceStrategy: 'first',
|
|
475
|
+
eventSpacing: 80,
|
|
476
|
+
}}
|
|
477
|
+
/>
|
|
478
|
+
</div>
|
|
479
|
+
</div>
|
|
480
|
+
);
|
|
481
|
+
},
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Comparison: Edge Labels (Default) vs Node Labels (Optional)
|
|
486
|
+
* Both are clickable! This shows different visualization styles.
|
|
487
|
+
*/
|
|
488
|
+
export const EdgeLabelsVsNodeLabels: Story = {
|
|
489
|
+
render: () => {
|
|
490
|
+
const [selectedLeft, setSelectedLeft] = useState<number | undefined>(undefined);
|
|
491
|
+
const [selectedRight, setSelectedRight] = useState<number | undefined>(undefined);
|
|
492
|
+
|
|
493
|
+
return (
|
|
494
|
+
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', height: '100%', gap: 16 }}>
|
|
495
|
+
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
|
496
|
+
<div style={{
|
|
497
|
+
padding: '12px',
|
|
498
|
+
background: '#3498db',
|
|
499
|
+
color: 'white',
|
|
500
|
+
borderBottom: '3px solid #2980b9',
|
|
501
|
+
marginBottom: 12,
|
|
502
|
+
}}>
|
|
503
|
+
<h3 style={{ margin: '0 0 8px 0', fontSize: 15 }}>📊 Edge Labels (Default)</h3>
|
|
504
|
+
<div style={{ fontSize: 13 }}>
|
|
505
|
+
Selected: {selectedLeft !== undefined ? `Event ${selectedLeft + 1}` : 'None'}
|
|
506
|
+
</div>
|
|
507
|
+
<div style={{ fontSize: 12, marginTop: 4, opacity: 0.9 }}>
|
|
508
|
+
Traditional UML style - labels on arrows
|
|
509
|
+
</div>
|
|
510
|
+
</div>
|
|
511
|
+
<div style={{ flex: 1, border: '1px solid #ddd', borderRadius: 4 }}>
|
|
512
|
+
<WorkflowSequenceDiagram
|
|
513
|
+
scenario={simpleAuthScenario}
|
|
514
|
+
height="100%"
|
|
515
|
+
selectedEventIndex={selectedLeft}
|
|
516
|
+
onEventIndexChange={setSelectedLeft}
|
|
517
|
+
showEventLabels={false}
|
|
518
|
+
layoutOptions={{
|
|
519
|
+
namespaceStrategy: 'first',
|
|
520
|
+
eventSpacing: 100,
|
|
521
|
+
}}
|
|
522
|
+
/>
|
|
523
|
+
</div>
|
|
524
|
+
</div>
|
|
525
|
+
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
|
526
|
+
<div style={{
|
|
527
|
+
padding: '12px',
|
|
528
|
+
background: '#9b59b6',
|
|
529
|
+
color: 'white',
|
|
530
|
+
borderBottom: '3px solid #8e44ad',
|
|
531
|
+
marginBottom: 12,
|
|
532
|
+
}}>
|
|
533
|
+
<h3 style={{ margin: '0 0 8px 0', fontSize: 15 }}>🏷️ Node Labels (Alternative)</h3>
|
|
534
|
+
<div style={{ fontSize: 13 }}>
|
|
535
|
+
Selected: {selectedRight !== undefined ? `Event ${selectedRight + 1}` : 'None'}
|
|
536
|
+
</div>
|
|
537
|
+
<div style={{ fontSize: 12, marginTop: 4, opacity: 0.9 }}>
|
|
538
|
+
Labels directly on events - alternative style
|
|
539
|
+
</div>
|
|
540
|
+
</div>
|
|
541
|
+
<div style={{ flex: 1, border: '1px solid #ddd', borderRadius: 4 }}>
|
|
542
|
+
<WorkflowSequenceDiagram
|
|
543
|
+
scenario={simpleAuthScenario}
|
|
544
|
+
height="100%"
|
|
545
|
+
selectedEventIndex={selectedRight}
|
|
546
|
+
onEventIndexChange={setSelectedRight}
|
|
547
|
+
showEventLabels={true}
|
|
548
|
+
layoutOptions={{
|
|
549
|
+
namespaceStrategy: 'first',
|
|
550
|
+
eventSpacing: 100,
|
|
551
|
+
nodeWidth: 80,
|
|
552
|
+
nodeHeight: 50,
|
|
553
|
+
}}
|
|
554
|
+
/>
|
|
555
|
+
</div>
|
|
556
|
+
</div>
|
|
557
|
+
</div>
|
|
558
|
+
);
|
|
559
|
+
},
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Comparison of different namespace strategies
|
|
564
|
+
*/
|
|
565
|
+
export const NamespaceStrategies: Story = {
|
|
566
|
+
render: () => {
|
|
567
|
+
return (
|
|
568
|
+
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', height: '100%', gap: 16 }}>
|
|
569
|
+
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
|
570
|
+
<h3 style={{ margin: '0 0 12px 0' }}>First Segment (Participants)</h3>
|
|
571
|
+
<div style={{ flex: 1, border: '1px solid #ddd', borderRadius: 4 }}>
|
|
572
|
+
<WorkflowSequenceDiagram
|
|
573
|
+
scenario={orderProcessingScenario}
|
|
574
|
+
height="100%"
|
|
575
|
+
layoutOptions={{
|
|
576
|
+
namespaceStrategy: 'first',
|
|
577
|
+
laneWidth: 180,
|
|
578
|
+
eventSpacing: 60,
|
|
579
|
+
}}
|
|
580
|
+
/>
|
|
581
|
+
</div>
|
|
582
|
+
</div>
|
|
583
|
+
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
|
584
|
+
<h3 style={{ margin: '0 0 12px 0' }}>All But Last (Detailed)</h3>
|
|
585
|
+
<div style={{ flex: 1, border: '1px solid #ddd', borderRadius: 4 }}>
|
|
586
|
+
<WorkflowSequenceDiagram
|
|
587
|
+
scenario={orderProcessingScenario}
|
|
588
|
+
height="100%"
|
|
589
|
+
layoutOptions={{
|
|
590
|
+
namespaceStrategy: 'all-but-last',
|
|
591
|
+
laneWidth: 180,
|
|
592
|
+
eventSpacing: 60,
|
|
593
|
+
}}
|
|
594
|
+
/>
|
|
595
|
+
</div>
|
|
596
|
+
</div>
|
|
597
|
+
</div>
|
|
598
|
+
);
|
|
599
|
+
},
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Custom layout options - compact view
|
|
604
|
+
*/
|
|
605
|
+
export const CompactLayout: Story = {
|
|
606
|
+
args: {
|
|
607
|
+
scenario: simpleAuthScenario,
|
|
608
|
+
height: 400,
|
|
609
|
+
layoutOptions: {
|
|
610
|
+
namespaceStrategy: 'first',
|
|
611
|
+
laneWidth: 150,
|
|
612
|
+
laneGap: 30,
|
|
613
|
+
eventSpacing: 50,
|
|
614
|
+
},
|
|
615
|
+
},
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* Custom layout options - spacious view
|
|
620
|
+
*/
|
|
621
|
+
export const SpaciousLayout: Story = {
|
|
622
|
+
args: {
|
|
623
|
+
scenario: simpleAuthScenario,
|
|
624
|
+
height: 600,
|
|
625
|
+
layoutOptions: {
|
|
626
|
+
namespaceStrategy: 'first',
|
|
627
|
+
laneWidth: 250,
|
|
628
|
+
laneGap: 100,
|
|
629
|
+
eventSpacing: 120,
|
|
630
|
+
},
|
|
631
|
+
},
|
|
632
|
+
};
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* Without controls for embedded use
|
|
636
|
+
*/
|
|
637
|
+
export const WithoutControls: Story = {
|
|
638
|
+
args: {
|
|
639
|
+
scenario: simpleAuthScenario,
|
|
640
|
+
height: 500,
|
|
641
|
+
showControls: false,
|
|
642
|
+
layoutOptions: {
|
|
643
|
+
namespaceStrategy: 'first',
|
|
644
|
+
eventSpacing: 80,
|
|
645
|
+
},
|
|
646
|
+
},
|
|
647
|
+
};
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* With background grid
|
|
651
|
+
*/
|
|
652
|
+
export const WithBackground: Story = {
|
|
653
|
+
args: {
|
|
654
|
+
scenario: simpleAuthScenario,
|
|
655
|
+
height: 500,
|
|
656
|
+
showBackground: true,
|
|
657
|
+
layoutOptions: {
|
|
658
|
+
namespaceStrategy: 'first',
|
|
659
|
+
eventSpacing: 80,
|
|
660
|
+
},
|
|
661
|
+
},
|
|
662
|
+
};
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* Full-featured example with all options
|
|
666
|
+
* Demonstrates event navigation, selection, and visual customization
|
|
667
|
+
*/
|
|
668
|
+
export const FullFeatured: Story = {
|
|
669
|
+
render: () => {
|
|
670
|
+
const [selectedIndex, setSelectedIndex] = useState<number>(0);
|
|
671
|
+
const [showControls, setShowControls] = useState(true);
|
|
672
|
+
const [showBackground, setShowBackground] = useState(false);
|
|
673
|
+
|
|
674
|
+
const eventNames = Object.keys(orderProcessingScenario.template.events);
|
|
675
|
+
const totalEvents = eventNames.length;
|
|
676
|
+
|
|
677
|
+
return (
|
|
678
|
+
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
|
679
|
+
<div
|
|
680
|
+
style={{
|
|
681
|
+
padding: '16px',
|
|
682
|
+
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
|
683
|
+
color: 'white',
|
|
684
|
+
borderBottom: '4px solid #5a67d8',
|
|
685
|
+
marginBottom: 16,
|
|
686
|
+
}}
|
|
687
|
+
>
|
|
688
|
+
<div style={{ marginBottom: 12, fontSize: 15 }}>
|
|
689
|
+
<strong>📍 Current Event:</strong>{' '}
|
|
690
|
+
<span style={{ fontFamily: 'monospace', background: 'rgba(255,255,255,0.2)', padding: '4px 8px', borderRadius: 3 }}>
|
|
691
|
+
{selectedIndex + 1} / {totalEvents}
|
|
692
|
+
</span>
|
|
693
|
+
{' - '}
|
|
694
|
+
<span style={{ fontFamily: 'monospace' }}>{eventNames[selectedIndex]}</span>
|
|
695
|
+
</div>
|
|
696
|
+
|
|
697
|
+
<div style={{ display: 'flex', gap: 16, alignItems: 'center', flexWrap: 'wrap' }}>
|
|
698
|
+
<div style={{ display: 'flex', gap: 8 }}>
|
|
699
|
+
<button
|
|
700
|
+
onClick={() => setSelectedIndex(Math.max(0, selectedIndex - 1))}
|
|
701
|
+
disabled={selectedIndex === 0}
|
|
702
|
+
style={{
|
|
703
|
+
padding: '8px 16px',
|
|
704
|
+
cursor: selectedIndex === 0 ? 'not-allowed' : 'pointer',
|
|
705
|
+
background: selectedIndex === 0 ? '#ccc' : '#3498db',
|
|
706
|
+
color: 'white',
|
|
707
|
+
border: 'none',
|
|
708
|
+
borderRadius: 4,
|
|
709
|
+
fontSize: 13,
|
|
710
|
+
fontWeight: 'bold',
|
|
711
|
+
}}
|
|
712
|
+
>
|
|
713
|
+
← Previous
|
|
714
|
+
</button>
|
|
715
|
+
<button
|
|
716
|
+
onClick={() => setSelectedIndex(Math.min(totalEvents - 1, selectedIndex + 1))}
|
|
717
|
+
disabled={selectedIndex === totalEvents - 1}
|
|
718
|
+
style={{
|
|
719
|
+
padding: '8px 16px',
|
|
720
|
+
cursor: selectedIndex === totalEvents - 1 ? 'not-allowed' : 'pointer',
|
|
721
|
+
background: selectedIndex === totalEvents - 1 ? '#ccc' : '#3498db',
|
|
722
|
+
color: 'white',
|
|
723
|
+
border: 'none',
|
|
724
|
+
borderRadius: 4,
|
|
725
|
+
fontSize: 13,
|
|
726
|
+
fontWeight: 'bold',
|
|
727
|
+
}}
|
|
728
|
+
>
|
|
729
|
+
Next →
|
|
730
|
+
</button>
|
|
731
|
+
</div>
|
|
732
|
+
|
|
733
|
+
<div style={{ borderLeft: '2px solid rgba(255,255,255,0.3)', paddingLeft: 16, display: 'flex', gap: 12 }}>
|
|
734
|
+
<label style={{ cursor: 'pointer', fontSize: 13 }}>
|
|
735
|
+
<input
|
|
736
|
+
type="checkbox"
|
|
737
|
+
checked={showControls}
|
|
738
|
+
onChange={(e) => setShowControls(e.target.checked)}
|
|
739
|
+
style={{ marginRight: 6 }}
|
|
740
|
+
/>
|
|
741
|
+
Show Controls
|
|
742
|
+
</label>
|
|
743
|
+
<label style={{ cursor: 'pointer', fontSize: 13 }}>
|
|
744
|
+
<input
|
|
745
|
+
type="checkbox"
|
|
746
|
+
checked={showBackground}
|
|
747
|
+
onChange={(e) => setShowBackground(e.target.checked)}
|
|
748
|
+
style={{ marginRight: 6 }}
|
|
749
|
+
/>
|
|
750
|
+
Show Background
|
|
751
|
+
</label>
|
|
752
|
+
</div>
|
|
753
|
+
</div>
|
|
754
|
+
|
|
755
|
+
<div style={{ marginTop: 12, fontSize: 12, opacity: 0.9, background: 'rgba(0,0,0,0.2)', padding: '8px', borderRadius: 4 }}>
|
|
756
|
+
💡 <strong>Tip:</strong> Click directly on any event dot in the diagram to jump to it!
|
|
757
|
+
</div>
|
|
758
|
+
</div>
|
|
759
|
+
<div style={{ flex: 1 }}>
|
|
760
|
+
<WorkflowSequenceDiagram
|
|
761
|
+
scenario={orderProcessingScenario}
|
|
762
|
+
height="100%"
|
|
763
|
+
selectedEventIndex={selectedIndex}
|
|
764
|
+
onEventIndexChange={setSelectedIndex}
|
|
765
|
+
showControls={showControls}
|
|
766
|
+
showBackground={showBackground}
|
|
767
|
+
layoutOptions={{
|
|
768
|
+
namespaceStrategy: 'first',
|
|
769
|
+
laneWidth: 200,
|
|
770
|
+
eventSpacing: 70,
|
|
771
|
+
}}
|
|
772
|
+
/>
|
|
773
|
+
</div>
|
|
774
|
+
</div>
|
|
775
|
+
);
|
|
776
|
+
},
|
|
777
|
+
};
|