@principal-ai/principal-view-react 0.7.39 → 0.7.41
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/EdgeInfoPanel.d.ts.map +1 -1
- package/dist/components/EdgeInfoPanel.js +18 -12
- package/dist/components/EdgeInfoPanel.js.map +1 -1
- package/dist/components/NodeInfoPanel.d.ts.map +1 -1
- package/dist/components/NodeInfoPanel.js +46 -30
- package/dist/components/NodeInfoPanel.js.map +1 -1
- package/dist/components/NodeTooltip.d.ts.map +1 -1
- package/dist/components/NodeTooltip.js +11 -6
- package/dist/components/NodeTooltip.js.map +1 -1
- package/dist/components/SelectionSidebar.d.ts.map +1 -1
- package/dist/components/SelectionSidebar.js +24 -13
- package/dist/components/SelectionSidebar.js.map +1 -1
- package/dist/edges/CustomEdge.d.ts.map +1 -1
- package/dist/edges/CustomEdge.js +8 -4
- package/dist/edges/CustomEdge.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/nodes/CustomNode.d.ts.map +1 -1
- package/dist/nodes/CustomNode.js +32 -19
- package/dist/nodes/CustomNode.js.map +1 -1
- package/dist/utils/orientationUtils.d.ts +19 -0
- package/dist/utils/orientationUtils.d.ts.map +1 -0
- package/dist/utils/orientationUtils.js +62 -0
- package/dist/utils/orientationUtils.js.map +1 -0
- package/package.json +1 -2
- package/src/components/EdgeInfoPanel.tsx +20 -14
- package/src/components/NodeInfoPanel.tsx +48 -32
- package/src/components/NodeTooltip.tsx +11 -6
- package/src/components/SelectionSidebar.tsx +24 -13
- package/src/edges/CustomEdge.tsx +8 -4
- package/src/index.ts +5 -0
- package/src/nodes/CustomNode.tsx +32 -19
- package/src/stories/GraphOrientation.stories.tsx +500 -0
- package/src/utils/orientationUtils.ts +73 -0
- package/dist/components/NarrativeRenderer.d.ts +0 -19
- package/dist/components/NarrativeRenderer.d.ts.map +0 -1
- package/dist/components/NarrativeRenderer.js +0 -103
- package/dist/components/NarrativeRenderer.js.map +0 -1
- package/dist/components/TestEventPanel.d.ts +0 -44
- package/dist/components/TestEventPanel.d.ts.map +0 -1
- package/dist/components/TestEventPanel.js +0 -277
- package/dist/components/TestEventPanel.js.map +0 -1
- package/dist/utils/narrative-converter.d.ts +0 -45
- package/dist/utils/narrative-converter.d.ts.map +0 -1
- package/dist/utils/narrative-converter.js +0 -121
- package/dist/utils/narrative-converter.js.map +0 -1
- package/dist/utils/narrative-loader.d.ts +0 -53
- package/dist/utils/narrative-loader.d.ts.map +0 -1
- package/dist/utils/narrative-loader.js +0 -163
- package/dist/utils/narrative-loader.js.map +0 -1
- package/src/stories/RealTestExecution.stories.tsx +0 -404
- package/src/stories/ValidatedExecution.stories.tsx +0 -158
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
import React, { useState, useMemo } from 'react';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
import { GraphRenderer } from '../components/GraphRenderer';
|
|
4
|
+
import type { ExtendedCanvas } from '@principal-ai/principal-view-core/browser';
|
|
5
|
+
import { ThemeProvider, defaultEditorTheme } from '@principal-ade/industry-theme';
|
|
6
|
+
import { swapGraphOrientation } from '../utils/orientationUtils';
|
|
7
|
+
import { CanvasConverter } from '@principal-ai/principal-view-core/browser';
|
|
8
|
+
|
|
9
|
+
const meta = {
|
|
10
|
+
title: 'Features/Graph Orientation',
|
|
11
|
+
component: GraphRenderer,
|
|
12
|
+
parameters: {
|
|
13
|
+
layout: 'centered',
|
|
14
|
+
},
|
|
15
|
+
tags: ['autodocs'],
|
|
16
|
+
decorators: [
|
|
17
|
+
(Story) => (
|
|
18
|
+
<ThemeProvider theme={defaultEditorTheme}>
|
|
19
|
+
<Story />
|
|
20
|
+
</ThemeProvider>
|
|
21
|
+
),
|
|
22
|
+
],
|
|
23
|
+
} satisfies Meta<typeof GraphRenderer>;
|
|
24
|
+
|
|
25
|
+
export default meta;
|
|
26
|
+
type Story = StoryObj<typeof meta>;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Canvas with horizontal flow (left to right)
|
|
30
|
+
*/
|
|
31
|
+
const horizontalCanvas: ExtendedCanvas = {
|
|
32
|
+
nodes: [
|
|
33
|
+
{
|
|
34
|
+
id: 'start',
|
|
35
|
+
type: 'text',
|
|
36
|
+
x: 100,
|
|
37
|
+
y: 200,
|
|
38
|
+
width: 120,
|
|
39
|
+
height: 80,
|
|
40
|
+
text: 'Start',
|
|
41
|
+
color: 4,
|
|
42
|
+
pv: {
|
|
43
|
+
nodeType: 'process',
|
|
44
|
+
shape: 'circle',
|
|
45
|
+
icon: 'Play',
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: 'process-1',
|
|
50
|
+
type: 'text',
|
|
51
|
+
x: 300,
|
|
52
|
+
y: 200,
|
|
53
|
+
width: 140,
|
|
54
|
+
height: 80,
|
|
55
|
+
text: 'Process 1',
|
|
56
|
+
color: 6,
|
|
57
|
+
pv: {
|
|
58
|
+
nodeType: 'process',
|
|
59
|
+
shape: 'rectangle',
|
|
60
|
+
icon: 'Box',
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: 'decision',
|
|
65
|
+
type: 'text',
|
|
66
|
+
x: 520,
|
|
67
|
+
y: 200,
|
|
68
|
+
width: 100,
|
|
69
|
+
height: 100,
|
|
70
|
+
text: 'Decision',
|
|
71
|
+
color: 2,
|
|
72
|
+
pv: {
|
|
73
|
+
nodeType: 'decision',
|
|
74
|
+
shape: 'diamond',
|
|
75
|
+
icon: 'GitBranch',
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: 'process-2a',
|
|
80
|
+
type: 'text',
|
|
81
|
+
x: 700,
|
|
82
|
+
y: 120,
|
|
83
|
+
width: 140,
|
|
84
|
+
height: 80,
|
|
85
|
+
text: 'Process 2A',
|
|
86
|
+
color: 6,
|
|
87
|
+
pv: {
|
|
88
|
+
nodeType: 'process',
|
|
89
|
+
shape: 'rectangle',
|
|
90
|
+
icon: 'Box',
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: 'process-2b',
|
|
95
|
+
type: 'text',
|
|
96
|
+
x: 700,
|
|
97
|
+
y: 280,
|
|
98
|
+
width: 140,
|
|
99
|
+
height: 80,
|
|
100
|
+
text: 'Process 2B',
|
|
101
|
+
color: 6,
|
|
102
|
+
pv: {
|
|
103
|
+
nodeType: 'process',
|
|
104
|
+
shape: 'rectangle',
|
|
105
|
+
icon: 'Box',
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
id: 'end',
|
|
110
|
+
type: 'text',
|
|
111
|
+
x: 920,
|
|
112
|
+
y: 200,
|
|
113
|
+
width: 120,
|
|
114
|
+
height: 80,
|
|
115
|
+
text: 'End',
|
|
116
|
+
color: 5,
|
|
117
|
+
pv: {
|
|
118
|
+
nodeType: 'process',
|
|
119
|
+
shape: 'circle',
|
|
120
|
+
icon: 'CheckCircle',
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
edges: [
|
|
125
|
+
{
|
|
126
|
+
id: 'e1',
|
|
127
|
+
fromNode: 'start',
|
|
128
|
+
toNode: 'process-1',
|
|
129
|
+
fromSide: 'right',
|
|
130
|
+
toSide: 'left',
|
|
131
|
+
pv: {
|
|
132
|
+
edgeType: 'flow',
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
id: 'e2',
|
|
137
|
+
fromNode: 'process-1',
|
|
138
|
+
toNode: 'decision',
|
|
139
|
+
fromSide: 'right',
|
|
140
|
+
toSide: 'left',
|
|
141
|
+
pv: {
|
|
142
|
+
edgeType: 'flow',
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: 'e3',
|
|
147
|
+
fromNode: 'decision',
|
|
148
|
+
toNode: 'process-2a',
|
|
149
|
+
fromSide: 'top',
|
|
150
|
+
toSide: 'left',
|
|
151
|
+
pv: {
|
|
152
|
+
edgeType: 'flow',
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
id: 'e4',
|
|
157
|
+
fromNode: 'decision',
|
|
158
|
+
toNode: 'process-2b',
|
|
159
|
+
fromSide: 'bottom',
|
|
160
|
+
toSide: 'left',
|
|
161
|
+
pv: {
|
|
162
|
+
edgeType: 'flow',
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
id: 'e5',
|
|
167
|
+
fromNode: 'process-2a',
|
|
168
|
+
toNode: 'end',
|
|
169
|
+
fromSide: 'right',
|
|
170
|
+
toSide: 'top',
|
|
171
|
+
pv: {
|
|
172
|
+
edgeType: 'flow',
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
id: 'e6',
|
|
177
|
+
fromNode: 'process-2b',
|
|
178
|
+
toNode: 'end',
|
|
179
|
+
fromSide: 'right',
|
|
180
|
+
toSide: 'bottom',
|
|
181
|
+
pv: {
|
|
182
|
+
edgeType: 'flow',
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
],
|
|
186
|
+
pv: {
|
|
187
|
+
version: '1.0.0',
|
|
188
|
+
name: 'Horizontal Flow',
|
|
189
|
+
description: 'Graph with horizontal (left-to-right) orientation',
|
|
190
|
+
edgeTypes: {
|
|
191
|
+
flow: {
|
|
192
|
+
description: 'Flow connection',
|
|
193
|
+
style: 'solid',
|
|
194
|
+
color: '#888',
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Interactive story demonstrating orientation swap
|
|
202
|
+
*/
|
|
203
|
+
const OrientationSwapTemplate = () => {
|
|
204
|
+
const [isVertical, setIsVertical] = useState(false);
|
|
205
|
+
|
|
206
|
+
const canvas = useMemo(() => {
|
|
207
|
+
if (!isVertical) {
|
|
208
|
+
return horizontalCanvas;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Convert canvas to internal format
|
|
212
|
+
const { nodes: nodeStates, edges: edgeStates } =
|
|
213
|
+
CanvasConverter.canvasToGraph(horizontalCanvas);
|
|
214
|
+
|
|
215
|
+
// Swap orientation
|
|
216
|
+
const { nodes: swappedNodes, edges: swappedEdges } = swapGraphOrientation(
|
|
217
|
+
nodeStates,
|
|
218
|
+
edgeStates
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
// Convert back to canvas format for rendering
|
|
222
|
+
return {
|
|
223
|
+
...horizontalCanvas,
|
|
224
|
+
nodes: swappedNodes.map((node, idx) => ({
|
|
225
|
+
...horizontalCanvas.nodes[idx],
|
|
226
|
+
x: node.position?.x ?? 0,
|
|
227
|
+
y: node.position?.y ?? 0,
|
|
228
|
+
})),
|
|
229
|
+
edges: swappedEdges.map((edge, idx) => {
|
|
230
|
+
const data = edge.data as Record<string, unknown> | undefined;
|
|
231
|
+
return {
|
|
232
|
+
...horizontalCanvas.edges[idx],
|
|
233
|
+
fromSide: (data?.fromSide as 'top' | 'right' | 'bottom' | 'left') ?? 'bottom',
|
|
234
|
+
toSide: (data?.toSide as 'top' | 'right' | 'bottom' | 'left') ?? 'top',
|
|
235
|
+
};
|
|
236
|
+
}),
|
|
237
|
+
};
|
|
238
|
+
}, [isVertical]);
|
|
239
|
+
|
|
240
|
+
return (
|
|
241
|
+
<div style={{ padding: 20 }}>
|
|
242
|
+
<div style={{ marginBottom: 20, display: 'flex', alignItems: 'center', gap: 20 }}>
|
|
243
|
+
<h2 style={{ margin: 0, fontFamily: 'system-ui' }}>Graph Orientation</h2>
|
|
244
|
+
<button
|
|
245
|
+
onClick={() => setIsVertical(!isVertical)}
|
|
246
|
+
style={{
|
|
247
|
+
padding: '8px 16px',
|
|
248
|
+
fontSize: 14,
|
|
249
|
+
fontFamily: 'system-ui',
|
|
250
|
+
backgroundColor: '#3b82f6',
|
|
251
|
+
color: 'white',
|
|
252
|
+
border: 'none',
|
|
253
|
+
borderRadius: 6,
|
|
254
|
+
cursor: 'pointer',
|
|
255
|
+
}}
|
|
256
|
+
>
|
|
257
|
+
Switch to {isVertical ? 'Horizontal' : 'Vertical'}
|
|
258
|
+
</button>
|
|
259
|
+
<div style={{ fontSize: 14, color: '#666', fontFamily: 'system-ui' }}>
|
|
260
|
+
Current: <strong>{isVertical ? 'Vertical (Top-to-Bottom)' : 'Horizontal (Left-to-Right)'}</strong>
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
263
|
+
|
|
264
|
+
<GraphRenderer
|
|
265
|
+
key={isVertical ? 'vertical' : 'horizontal'}
|
|
266
|
+
canvas={canvas}
|
|
267
|
+
width={isVertical ? 500 : 1100}
|
|
268
|
+
height={isVertical ? 1100 : 500}
|
|
269
|
+
/>
|
|
270
|
+
|
|
271
|
+
<div style={{ marginTop: 30, padding: 16, backgroundColor: '#f5f5f5', borderRadius: 8 }}>
|
|
272
|
+
<h4 style={{ marginBottom: 10, fontFamily: 'system-ui' }}>How It Works</h4>
|
|
273
|
+
<ul style={{ fontSize: 13, lineHeight: 1.8, margin: 0, paddingLeft: 20 }}>
|
|
274
|
+
<li>
|
|
275
|
+
<strong>Position Swap</strong>: Swaps x and y coordinates of all nodes
|
|
276
|
+
</li>
|
|
277
|
+
<li>
|
|
278
|
+
<strong>Edge Side Rotation</strong>: Both sides rotate <strong>clockwise</strong>
|
|
279
|
+
<ul style={{ marginTop: 5 }}>
|
|
280
|
+
<li><code>top → right → bottom → left → top</code></li>
|
|
281
|
+
<li>Example: <code>right → left</code> becomes <code>bottom → top</code></li>
|
|
282
|
+
</ul>
|
|
283
|
+
</li>
|
|
284
|
+
<li>
|
|
285
|
+
<strong>Usage</strong>: <code>swapGraphOrientation(nodes, edges)</code>
|
|
286
|
+
</li>
|
|
287
|
+
</ul>
|
|
288
|
+
</div>
|
|
289
|
+
</div>
|
|
290
|
+
);
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
export const InteractiveOrientationSwap: Story = {
|
|
294
|
+
render: () => <OrientationSwapTemplate />,
|
|
295
|
+
parameters: {
|
|
296
|
+
docs: {
|
|
297
|
+
description: {
|
|
298
|
+
story: `
|
|
299
|
+
**Interactive Orientation Swap**
|
|
300
|
+
|
|
301
|
+
Click the button to toggle between horizontal (left-to-right) and vertical (top-to-bottom) orientations.
|
|
302
|
+
|
|
303
|
+
The \`swapGraphOrientation\` utility:
|
|
304
|
+
1. Swaps x/y coordinates for all node positions
|
|
305
|
+
2. Rotates edge connection sides clockwise (both fromSide and toSide)
|
|
306
|
+
- Rotation: top → right → bottom → left → top
|
|
307
|
+
- Example: \`right → left\` becomes \`bottom → top\`
|
|
308
|
+
3. Preserves the graph structure while rotating it 90 degrees
|
|
309
|
+
`,
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Horizontal flow example
|
|
317
|
+
*/
|
|
318
|
+
export const HorizontalFlow: Story = {
|
|
319
|
+
args: {
|
|
320
|
+
canvas: horizontalCanvas,
|
|
321
|
+
width: 1100,
|
|
322
|
+
height: 450,
|
|
323
|
+
},
|
|
324
|
+
parameters: {
|
|
325
|
+
docs: {
|
|
326
|
+
description: {
|
|
327
|
+
story: `
|
|
328
|
+
**Horizontal Flow (Left-to-Right)**
|
|
329
|
+
|
|
330
|
+
Traditional left-to-right flowchart layout:
|
|
331
|
+
- Primary flow uses \`right → left\` connections
|
|
332
|
+
- Branches use \`top\` and \`bottom\` connections for splitting
|
|
333
|
+
- Typical for flowcharts and process diagrams
|
|
334
|
+
`,
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Vertical flow example (using swapped canvas)
|
|
342
|
+
*/
|
|
343
|
+
const VerticalFlowTemplate = () => {
|
|
344
|
+
const verticalCanvas = useMemo(() => {
|
|
345
|
+
const { nodes: nodeStates, edges: edgeStates } =
|
|
346
|
+
CanvasConverter.canvasToGraph(horizontalCanvas);
|
|
347
|
+
|
|
348
|
+
const { nodes: swappedNodes, edges: swappedEdges } = swapGraphOrientation(
|
|
349
|
+
nodeStates,
|
|
350
|
+
edgeStates
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
return {
|
|
354
|
+
...horizontalCanvas,
|
|
355
|
+
pv: {
|
|
356
|
+
...horizontalCanvas.pv,
|
|
357
|
+
name: 'Vertical Flow',
|
|
358
|
+
description: 'Graph with vertical (top-to-bottom) orientation',
|
|
359
|
+
},
|
|
360
|
+
nodes: swappedNodes.map((node, idx) => ({
|
|
361
|
+
...horizontalCanvas.nodes[idx],
|
|
362
|
+
x: node.position?.x ?? 0,
|
|
363
|
+
y: node.position?.y ?? 0,
|
|
364
|
+
})),
|
|
365
|
+
edges: swappedEdges.map((edge, idx) => {
|
|
366
|
+
const data = edge.data as Record<string, unknown> | undefined;
|
|
367
|
+
return {
|
|
368
|
+
...horizontalCanvas.edges[idx],
|
|
369
|
+
fromSide: (data?.fromSide as 'top' | 'right' | 'bottom' | 'left') ?? 'bottom',
|
|
370
|
+
toSide: (data?.toSide as 'top' | 'right' | 'bottom' | 'left') ?? 'top',
|
|
371
|
+
};
|
|
372
|
+
}),
|
|
373
|
+
};
|
|
374
|
+
}, []);
|
|
375
|
+
|
|
376
|
+
return <GraphRenderer canvas={verticalCanvas} width={500} height={1100} />;
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
export const VerticalFlow: Story = {
|
|
380
|
+
render: () => <VerticalFlowTemplate />,
|
|
381
|
+
parameters: {
|
|
382
|
+
docs: {
|
|
383
|
+
description: {
|
|
384
|
+
story: `
|
|
385
|
+
**Vertical Flow (Top-to-Bottom)**
|
|
386
|
+
|
|
387
|
+
Top-to-bottom layout using \`swapGraphOrientation\`:
|
|
388
|
+
- Primary flow uses \`bottom → top\` connections
|
|
389
|
+
- Branches use \`left\` and \`right\` connections for splitting
|
|
390
|
+
- Common for hierarchical diagrams and org charts
|
|
391
|
+
`,
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
},
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Side-by-side comparison
|
|
399
|
+
*/
|
|
400
|
+
const SideBySideTemplate = () => {
|
|
401
|
+
const verticalCanvas = useMemo(() => {
|
|
402
|
+
const { nodes: nodeStates, edges: edgeStates } =
|
|
403
|
+
CanvasConverter.canvasToGraph(horizontalCanvas);
|
|
404
|
+
|
|
405
|
+
const { nodes: swappedNodes, edges: swappedEdges } = swapGraphOrientation(
|
|
406
|
+
nodeStates,
|
|
407
|
+
edgeStates
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
return {
|
|
411
|
+
...horizontalCanvas,
|
|
412
|
+
nodes: swappedNodes.map((node, idx) => ({
|
|
413
|
+
...horizontalCanvas.nodes[idx],
|
|
414
|
+
x: node.position?.x ?? 0,
|
|
415
|
+
y: node.position?.y ?? 0,
|
|
416
|
+
})),
|
|
417
|
+
edges: swappedEdges.map((edge, idx) => {
|
|
418
|
+
const data = edge.data as Record<string, unknown> | undefined;
|
|
419
|
+
return {
|
|
420
|
+
...horizontalCanvas.edges[idx],
|
|
421
|
+
fromSide: (data?.fromSide as 'top' | 'right' | 'bottom' | 'left') ?? 'bottom',
|
|
422
|
+
toSide: (data?.toSide as 'top' | 'right' | 'bottom' | 'left') ?? 'top',
|
|
423
|
+
};
|
|
424
|
+
}),
|
|
425
|
+
};
|
|
426
|
+
}, []);
|
|
427
|
+
|
|
428
|
+
return (
|
|
429
|
+
<div style={{ padding: 20 }}>
|
|
430
|
+
<h2 style={{ marginBottom: 20, fontFamily: 'system-ui' }}>Orientation Comparison</h2>
|
|
431
|
+
|
|
432
|
+
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 40 }}>
|
|
433
|
+
<div>
|
|
434
|
+
<h3 style={{ fontFamily: 'system-ui', marginBottom: 10 }}>
|
|
435
|
+
Horizontal (Left-to-Right)
|
|
436
|
+
</h3>
|
|
437
|
+
<div style={{ border: '1px solid #ddd', borderRadius: 8, overflow: 'hidden' }}>
|
|
438
|
+
<GraphRenderer canvas={horizontalCanvas} width={550} height={400} />
|
|
439
|
+
</div>
|
|
440
|
+
<div style={{ marginTop: 10, fontSize: 13, color: '#666' }}>
|
|
441
|
+
Original orientation with right→left primary flow
|
|
442
|
+
</div>
|
|
443
|
+
</div>
|
|
444
|
+
|
|
445
|
+
<div>
|
|
446
|
+
<h3 style={{ fontFamily: 'system-ui', marginBottom: 10 }}>
|
|
447
|
+
Vertical (Top-to-Bottom)
|
|
448
|
+
</h3>
|
|
449
|
+
<div style={{ border: '1px solid #ddd', borderRadius: 8, overflow: 'hidden' }}>
|
|
450
|
+
<GraphRenderer canvas={verticalCanvas} width={550} height={400} />
|
|
451
|
+
</div>
|
|
452
|
+
<div style={{ marginTop: 10, fontSize: 13, color: '#666' }}>
|
|
453
|
+
Swapped orientation with bottom→top primary flow
|
|
454
|
+
</div>
|
|
455
|
+
</div>
|
|
456
|
+
</div>
|
|
457
|
+
|
|
458
|
+
<div style={{ marginTop: 30, padding: 16, backgroundColor: '#f5f5f5', borderRadius: 8 }}>
|
|
459
|
+
<h4 style={{ marginBottom: 10, fontFamily: 'system-ui' }}>
|
|
460
|
+
Edge Side Rotation (Both Clockwise)
|
|
461
|
+
</h4>
|
|
462
|
+
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 20 }}>
|
|
463
|
+
<div>
|
|
464
|
+
<strong>Clockwise Rotation:</strong>
|
|
465
|
+
<ul style={{ fontSize: 13, lineHeight: 1.8, marginTop: 8, paddingLeft: 20 }}>
|
|
466
|
+
<li><code>top</code> → <code>right</code></li>
|
|
467
|
+
<li><code>right</code> → <code>bottom</code></li>
|
|
468
|
+
<li><code>bottom</code> → <code>left</code></li>
|
|
469
|
+
<li><code>left</code> → <code>top</code></li>
|
|
470
|
+
</ul>
|
|
471
|
+
</div>
|
|
472
|
+
<div>
|
|
473
|
+
<strong>Examples:</strong>
|
|
474
|
+
<ul style={{ fontSize: 13, lineHeight: 1.8, marginTop: 8, paddingLeft: 20 }}>
|
|
475
|
+
<li><code>right → left</code> becomes <code>bottom → top</code></li>
|
|
476
|
+
<li><code>top → left</code> becomes <code>right → top</code></li>
|
|
477
|
+
<li><code>bottom → left</code> becomes <code>left → top</code></li>
|
|
478
|
+
</ul>
|
|
479
|
+
</div>
|
|
480
|
+
</div>
|
|
481
|
+
</div>
|
|
482
|
+
</div>
|
|
483
|
+
);
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
export const SideBySideComparison: Story = {
|
|
487
|
+
render: () => <SideBySideTemplate />,
|
|
488
|
+
parameters: {
|
|
489
|
+
docs: {
|
|
490
|
+
description: {
|
|
491
|
+
story: `
|
|
492
|
+
**Side-by-Side Comparison**
|
|
493
|
+
|
|
494
|
+
Visual comparison of the same graph in both orientations, showing how \`swapGraphOrientation\`
|
|
495
|
+
transforms the layout while preserving the graph structure and flow logic.
|
|
496
|
+
`,
|
|
497
|
+
},
|
|
498
|
+
},
|
|
499
|
+
},
|
|
500
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { NodeState, EdgeState, CanvasSide } from '@principal-ai/principal-view-core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Swaps the orientation of a graph from horizontal to vertical or vice versa.
|
|
5
|
+
* This swaps x/y coordinates and adjusts edge connection sides accordingly.
|
|
6
|
+
*/
|
|
7
|
+
export function swapGraphOrientation(
|
|
8
|
+
nodes: NodeState[],
|
|
9
|
+
edges: EdgeState[]
|
|
10
|
+
): { nodes: NodeState[]; edges: EdgeState[] } {
|
|
11
|
+
return {
|
|
12
|
+
nodes: swapNodePositions(nodes),
|
|
13
|
+
edges: swapEdgeSides(edges),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Swaps x and y positions for all nodes
|
|
19
|
+
*/
|
|
20
|
+
export function swapNodePositions(nodes: NodeState[]): NodeState[] {
|
|
21
|
+
return nodes.map(node => ({
|
|
22
|
+
...node,
|
|
23
|
+
position: node.position ? {
|
|
24
|
+
x: node.position.y,
|
|
25
|
+
y: node.position.x,
|
|
26
|
+
} : undefined,
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Swaps edge connection sides to match the new orientation
|
|
32
|
+
* Both sides rotate clockwise: top → right → bottom → left → top
|
|
33
|
+
*/
|
|
34
|
+
export function swapEdgeSides(edges: EdgeState[]): EdgeState[] {
|
|
35
|
+
return edges.map(edge => {
|
|
36
|
+
const data = edge.data as Record<string, unknown> | undefined;
|
|
37
|
+
const fromSide = data?.fromSide as CanvasSide | undefined;
|
|
38
|
+
const toSide = data?.toSide as CanvasSide | undefined;
|
|
39
|
+
|
|
40
|
+
const rotatedFromSide = rotateClockwise(fromSide);
|
|
41
|
+
const rotatedToSide = rotateClockwise(toSide);
|
|
42
|
+
|
|
43
|
+
// Only include fromSide/toSide if they have defined values (not undefined)
|
|
44
|
+
const newData = { ...edge.data } as Record<string, unknown>;
|
|
45
|
+
if (rotatedFromSide !== undefined) {
|
|
46
|
+
newData.fromSide = rotatedFromSide;
|
|
47
|
+
}
|
|
48
|
+
if (rotatedToSide !== undefined) {
|
|
49
|
+
newData.toSide = rotatedToSide;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
...edge,
|
|
54
|
+
data: newData as EdgeState['data'],
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Rotates a side clockwise: top → right → bottom → left → top
|
|
61
|
+
*/
|
|
62
|
+
function rotateClockwise(side?: CanvasSide): CanvasSide | undefined {
|
|
63
|
+
if (!side) return undefined;
|
|
64
|
+
|
|
65
|
+
const clockwiseMap: Record<CanvasSide, CanvasSide> = {
|
|
66
|
+
top: 'right',
|
|
67
|
+
right: 'bottom',
|
|
68
|
+
bottom: 'left',
|
|
69
|
+
left: 'top',
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
return clockwiseMap[side];
|
|
73
|
+
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import type { NarrativeTemplate, OtelEvent } from '@principal-ai/principal-view-core/browser';
|
|
3
|
-
export interface NarrativeRendererProps {
|
|
4
|
-
/** Narrative template to use for rendering */
|
|
5
|
-
template: NarrativeTemplate;
|
|
6
|
-
/** OTEL events to render */
|
|
7
|
-
events: OtelEvent[];
|
|
8
|
-
/** Optional CSS class name */
|
|
9
|
-
className?: string;
|
|
10
|
-
/** Optional custom style */
|
|
11
|
-
style?: React.CSSProperties;
|
|
12
|
-
/** Show metadata panel */
|
|
13
|
-
showMetadata?: boolean;
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Renders OTEL events as a human-readable narrative using a template
|
|
17
|
-
*/
|
|
18
|
-
export declare const NarrativeRenderer: React.FC<NarrativeRendererProps>;
|
|
19
|
-
//# sourceMappingURL=NarrativeRenderer.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"NarrativeRenderer.d.ts","sourceRoot":"","sources":["../../src/components/NarrativeRenderer.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkB,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,2CAA2C,CAAC;AAG9F,MAAM,WAAW,sBAAsB;IACrC,8CAA8C;IAC9C,QAAQ,EAAE,iBAAiB,CAAC;IAE5B,4BAA4B;IAC5B,MAAM,EAAE,SAAS,EAAE,CAAC;IAEpB,8BAA8B;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,4BAA4B;IAC5B,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAE5B,0BAA0B;IAC1B,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;GAEG;AACH,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,EAAE,CAAC,sBAAsB,CAiJ9D,CAAC"}
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useMemo } from 'react';
|
|
3
|
-
import { renderNarrative } from '@principal-ai/principal-view-core/browser';
|
|
4
|
-
import { useTheme } from '@principal-ade/industry-theme';
|
|
5
|
-
/**
|
|
6
|
-
* Renders OTEL events as a human-readable narrative using a template
|
|
7
|
-
*/
|
|
8
|
-
export const NarrativeRenderer = ({ template, events, className, style, showMetadata = false, }) => {
|
|
9
|
-
const { theme } = useTheme();
|
|
10
|
-
// Render the narrative
|
|
11
|
-
const result = useMemo(() => {
|
|
12
|
-
try {
|
|
13
|
-
return renderNarrative(template, events);
|
|
14
|
-
}
|
|
15
|
-
catch (error) {
|
|
16
|
-
return {
|
|
17
|
-
text: `Error rendering narrative: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
18
|
-
scenarioId: 'error',
|
|
19
|
-
metadata: {
|
|
20
|
-
eventCount: events.length,
|
|
21
|
-
spanCount: 0,
|
|
22
|
-
logCount: 0,
|
|
23
|
-
},
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
}, [template, events]);
|
|
27
|
-
// Parse narrative text to add syntax highlighting
|
|
28
|
-
const renderHighlightedText = (text) => {
|
|
29
|
-
const lines = text.split('\n');
|
|
30
|
-
return lines.map((line, idx) => {
|
|
31
|
-
// Determine line style based on content
|
|
32
|
-
let lineStyle = {};
|
|
33
|
-
let content = line;
|
|
34
|
-
// Status indicators (✅ ❌ ⚠️ 📋)
|
|
35
|
-
if (/^[✅❌⚠️📋]/.test(line)) {
|
|
36
|
-
lineStyle = {
|
|
37
|
-
fontWeight: 'bold',
|
|
38
|
-
fontSize: '16px',
|
|
39
|
-
marginTop: idx > 0 ? '8px' : '0',
|
|
40
|
-
marginBottom: '4px',
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
// Separators (━━━━)
|
|
44
|
-
else if (/^━+/.test(line)) {
|
|
45
|
-
lineStyle = {
|
|
46
|
-
color: theme.colors.border,
|
|
47
|
-
opacity: 0.6,
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
// Arrow items (→)
|
|
51
|
-
else if (/^(\s*)→/.test(line)) {
|
|
52
|
-
const indent = line.match(/^(\s*)/)?.[1] || '';
|
|
53
|
-
lineStyle = {
|
|
54
|
-
color: theme.colors.text,
|
|
55
|
-
fontWeight: indent.length === 0 ? 'bold' : 'normal',
|
|
56
|
-
marginTop: indent.length === 0 ? '12px' : '4px',
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
// Bullet items (•)
|
|
60
|
-
else if (/^\s+•/.test(line)) {
|
|
61
|
-
lineStyle = {
|
|
62
|
-
color: theme.colors.textMuted,
|
|
63
|
-
paddingLeft: '8px',
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
// Section headers (UPPERCASE at start)
|
|
67
|
-
else if (/^[A-Z\s]+:/.test(line)) {
|
|
68
|
-
lineStyle = {
|
|
69
|
-
fontWeight: 'bold',
|
|
70
|
-
marginTop: '8px',
|
|
71
|
-
color: theme.colors.text,
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
return (_jsx("div", { style: lineStyle, children: content }, idx));
|
|
75
|
-
});
|
|
76
|
-
};
|
|
77
|
-
return (_jsxs("div", { className: className, style: {
|
|
78
|
-
width: '100%',
|
|
79
|
-
height: '100%',
|
|
80
|
-
display: 'flex',
|
|
81
|
-
flexDirection: 'column',
|
|
82
|
-
...style,
|
|
83
|
-
}, children: [_jsx("div", { style: {
|
|
84
|
-
flex: 1,
|
|
85
|
-
overflow: 'auto',
|
|
86
|
-
padding: '20px',
|
|
87
|
-
fontFamily: theme.fonts.monospace,
|
|
88
|
-
fontSize: '14px',
|
|
89
|
-
lineHeight: '1.6',
|
|
90
|
-
color: theme.colors.text,
|
|
91
|
-
backgroundColor: theme.colors.background,
|
|
92
|
-
whiteSpace: 'pre-wrap',
|
|
93
|
-
wordWrap: 'break-word',
|
|
94
|
-
}, children: renderHighlightedText(result.text) }), showMetadata && (_jsxs("div", { style: {
|
|
95
|
-
borderTop: `1px solid ${theme.colors.border}`,
|
|
96
|
-
padding: '12px 20px',
|
|
97
|
-
backgroundColor: theme.colors.surface,
|
|
98
|
-
fontSize: '12px',
|
|
99
|
-
color: theme.colors.textMuted,
|
|
100
|
-
fontFamily: theme.fonts.monospace,
|
|
101
|
-
}, children: [_jsxs("div", { style: { marginBottom: '4px' }, children: [_jsx("strong", { style: { color: theme.colors.text }, children: "Template:" }), " ", template.name] }), _jsxs("div", { style: { marginBottom: '4px' }, children: [_jsx("strong", { style: { color: theme.colors.text }, children: "Scenario:" }), " ", result.scenarioId] }), _jsxs("div", { children: [_jsx("strong", { style: { color: theme.colors.text }, children: "Events:" }), " ", result.metadata.eventCount, " total (", result.metadata.spanCount, " spans, ", result.metadata.logCount, " logs)"] }), result.metadata.timeRange && (_jsxs("div", { style: { marginTop: '4px' }, children: [_jsx("strong", { style: { color: theme.colors.text }, children: "Duration:" }), ' ', Number(result.metadata.timeRange.end) - Number(result.metadata.timeRange.start), "ms"] }))] }))] }));
|
|
102
|
-
};
|
|
103
|
-
//# sourceMappingURL=NarrativeRenderer.js.map
|