@principal-ai/principal-view-react 0.6.6
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/README.md +111 -0
- package/dist/components/ConfigurationSelector.d.ts +37 -0
- package/dist/components/ConfigurationSelector.d.ts.map +1 -0
- package/dist/components/ConfigurationSelector.js +67 -0
- package/dist/components/ConfigurationSelector.js.map +1 -0
- package/dist/components/EdgeInfoPanel.d.ts +16 -0
- package/dist/components/EdgeInfoPanel.d.ts.map +1 -0
- package/dist/components/EdgeInfoPanel.js +85 -0
- package/dist/components/EdgeInfoPanel.js.map +1 -0
- package/dist/components/EventLog.d.ts +20 -0
- package/dist/components/EventLog.d.ts.map +1 -0
- package/dist/components/EventLog.js +13 -0
- package/dist/components/EventLog.js.map +1 -0
- package/dist/components/EventLog.test.d.ts +2 -0
- package/dist/components/EventLog.test.d.ts.map +1 -0
- package/dist/components/EventLog.test.js +73 -0
- package/dist/components/EventLog.test.js.map +1 -0
- package/dist/components/GraphRenderer.d.ts +121 -0
- package/dist/components/GraphRenderer.d.ts.map +1 -0
- package/dist/components/GraphRenderer.js +809 -0
- package/dist/components/GraphRenderer.js.map +1 -0
- package/dist/components/GraphRenderer.test.d.ts +2 -0
- package/dist/components/GraphRenderer.test.d.ts.map +1 -0
- package/dist/components/GraphRenderer.test.js +88 -0
- package/dist/components/GraphRenderer.test.js.map +1 -0
- package/dist/components/MetricsDashboard.d.ts +14 -0
- package/dist/components/MetricsDashboard.d.ts.map +1 -0
- package/dist/components/MetricsDashboard.js +13 -0
- package/dist/components/MetricsDashboard.js.map +1 -0
- package/dist/components/NodeInfoPanel.d.ts +21 -0
- package/dist/components/NodeInfoPanel.d.ts.map +1 -0
- package/dist/components/NodeInfoPanel.js +217 -0
- package/dist/components/NodeInfoPanel.js.map +1 -0
- package/dist/edges/CustomEdge.d.ts +16 -0
- package/dist/edges/CustomEdge.d.ts.map +1 -0
- package/dist/edges/CustomEdge.js +200 -0
- package/dist/edges/CustomEdge.js.map +1 -0
- package/dist/edges/GenericEdge.d.ts +18 -0
- package/dist/edges/GenericEdge.d.ts.map +1 -0
- package/dist/edges/GenericEdge.js +14 -0
- package/dist/edges/GenericEdge.js.map +1 -0
- package/dist/hooks/usePathBasedEvents.d.ts +42 -0
- package/dist/hooks/usePathBasedEvents.d.ts.map +1 -0
- package/dist/hooks/usePathBasedEvents.js +122 -0
- package/dist/hooks/usePathBasedEvents.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -0
- package/dist/nodes/CustomNode.d.ts +18 -0
- package/dist/nodes/CustomNode.d.ts.map +1 -0
- package/dist/nodes/CustomNode.js +298 -0
- package/dist/nodes/CustomNode.js.map +1 -0
- package/dist/nodes/GenericNode.d.ts +20 -0
- package/dist/nodes/GenericNode.d.ts.map +1 -0
- package/dist/nodes/GenericNode.js +24 -0
- package/dist/nodes/GenericNode.js.map +1 -0
- package/dist/utils/animationMapping.d.ts +53 -0
- package/dist/utils/animationMapping.d.ts.map +1 -0
- package/dist/utils/animationMapping.js +133 -0
- package/dist/utils/animationMapping.js.map +1 -0
- package/dist/utils/graphConverter.d.ts +22 -0
- package/dist/utils/graphConverter.d.ts.map +1 -0
- package/dist/utils/graphConverter.js +176 -0
- package/dist/utils/graphConverter.js.map +1 -0
- package/dist/utils/iconResolver.d.ts +29 -0
- package/dist/utils/iconResolver.d.ts.map +1 -0
- package/dist/utils/iconResolver.js +68 -0
- package/dist/utils/iconResolver.js.map +1 -0
- package/package.json +61 -0
- package/src/components/ConfigurationSelector.tsx +147 -0
- package/src/components/EdgeInfoPanel.tsx +198 -0
- package/src/components/EventLog.test.tsx +85 -0
- package/src/components/EventLog.tsx +51 -0
- package/src/components/GraphRenderer.test.tsx +118 -0
- package/src/components/GraphRenderer.tsx +1222 -0
- package/src/components/MetricsDashboard.tsx +40 -0
- package/src/components/NodeInfoPanel.tsx +425 -0
- package/src/edges/CustomEdge.tsx +344 -0
- package/src/edges/GenericEdge.tsx +40 -0
- package/src/hooks/usePathBasedEvents.ts +182 -0
- package/src/index.ts +67 -0
- package/src/nodes/CustomNode.tsx +432 -0
- package/src/nodes/GenericNode.tsx +54 -0
- package/src/stories/AnimationWorkshop.stories.tsx +608 -0
- package/src/stories/EventDrivenAnimations.stories.tsx +499 -0
- package/src/stories/EventLog.stories.tsx +161 -0
- package/src/stories/GraphRenderer.stories.tsx +628 -0
- package/src/stories/Introduction.mdx +51 -0
- package/src/stories/MetricsDashboard.stories.tsx +227 -0
- package/src/stories/MultiConfig.stories.tsx +531 -0
- package/src/stories/MultiDirectionalConnections.stories.tsx +345 -0
- package/src/stories/NodeShapes.stories.tsx +769 -0
- package/src/utils/animationMapping.ts +170 -0
- package/src/utils/graphConverter.ts +218 -0
- package/src/utils/iconResolver.tsx +49 -0
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import React, { useState, useEffect, useRef } from 'react';
|
|
2
|
+
import { BaseEdge, EdgeLabelRenderer, getBezierPath } from '@xyflow/react';
|
|
3
|
+
import type { EdgeProps } from '@xyflow/react';
|
|
4
|
+
import type { EdgeTypeDefinition } from '@principal-ai/principal-view-core';
|
|
5
|
+
|
|
6
|
+
export interface CustomEdgeData extends Record<string, unknown> {
|
|
7
|
+
typeDefinition: EdgeTypeDefinition;
|
|
8
|
+
hasViolations?: boolean;
|
|
9
|
+
data?: Record<string, unknown>;
|
|
10
|
+
// Animation control
|
|
11
|
+
animationType?: 'flow' | 'particle' | 'pulse' | 'glow' | null;
|
|
12
|
+
animationDuration?: number;
|
|
13
|
+
animationDirection?: 'forward' | 'backward' | 'bidirectional';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Custom edge component for xyflow that renders based on EdgeTypeDefinition
|
|
18
|
+
*/
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
20
|
+
export const CustomEdge: React.FC<EdgeProps<any>> = ({
|
|
21
|
+
id,
|
|
22
|
+
sourceX,
|
|
23
|
+
sourceY,
|
|
24
|
+
targetX,
|
|
25
|
+
targetY,
|
|
26
|
+
sourcePosition,
|
|
27
|
+
targetPosition,
|
|
28
|
+
data,
|
|
29
|
+
markerEnd,
|
|
30
|
+
selected,
|
|
31
|
+
}) => {
|
|
32
|
+
const edgeProps = data as CustomEdgeData | undefined;
|
|
33
|
+
const {
|
|
34
|
+
typeDefinition,
|
|
35
|
+
hasViolations,
|
|
36
|
+
data: edgeData,
|
|
37
|
+
animationType,
|
|
38
|
+
animationDuration = 1000,
|
|
39
|
+
animationDirection = 'forward'
|
|
40
|
+
} = edgeProps || ({} as CustomEdgeData);
|
|
41
|
+
|
|
42
|
+
const [particlePosition, setParticlePosition] = useState(0);
|
|
43
|
+
const pathRef = useRef<SVGPathElement>(null);
|
|
44
|
+
|
|
45
|
+
// Particle animation effect
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (animationType !== 'particle') return;
|
|
48
|
+
|
|
49
|
+
const animate = () => {
|
|
50
|
+
setParticlePosition((prev) => {
|
|
51
|
+
const next = prev + (100 / animationDuration) * 16; // ~60fps
|
|
52
|
+
return next >= 100 ? 0 : next;
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const intervalId = setInterval(animate, 16);
|
|
57
|
+
return () => clearInterval(intervalId);
|
|
58
|
+
}, [animationType, animationDuration]);
|
|
59
|
+
|
|
60
|
+
// Early return after hooks
|
|
61
|
+
if (!typeDefinition) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const color = hasViolations ? '#D0021B' : (typeDefinition.color || '#888');
|
|
66
|
+
const width = typeDefinition.width || 2;
|
|
67
|
+
|
|
68
|
+
// Get Bezier path
|
|
69
|
+
const [edgePath, labelX, labelY] = getBezierPath({
|
|
70
|
+
sourceX,
|
|
71
|
+
sourceY,
|
|
72
|
+
sourcePosition,
|
|
73
|
+
targetX,
|
|
74
|
+
targetY,
|
|
75
|
+
targetPosition,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Style based on edge type
|
|
79
|
+
const getStrokeStyle = () => {
|
|
80
|
+
switch (typeDefinition.style) {
|
|
81
|
+
case 'dashed':
|
|
82
|
+
return '5 5';
|
|
83
|
+
case 'dotted':
|
|
84
|
+
return '2 2';
|
|
85
|
+
default:
|
|
86
|
+
return 'none';
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// Label configuration
|
|
91
|
+
const labelConfig = typeDefinition.label;
|
|
92
|
+
const labelField = labelConfig?.field;
|
|
93
|
+
const labelText = labelField && edgeData?.[labelField] ? String(edgeData[labelField]) : '';
|
|
94
|
+
|
|
95
|
+
// Animation-specific rendering helpers
|
|
96
|
+
const getAnimationClass = () => {
|
|
97
|
+
if (!animationType) {
|
|
98
|
+
return typeDefinition.style === 'animated' ? 'edge-flow-forward' : '';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
switch (animationType) {
|
|
102
|
+
case 'flow':
|
|
103
|
+
return animationDirection === 'backward'
|
|
104
|
+
? 'edge-flow-backward'
|
|
105
|
+
: animationDirection === 'bidirectional'
|
|
106
|
+
? 'edge-flow-bidirectional'
|
|
107
|
+
: 'edge-flow-forward';
|
|
108
|
+
case 'pulse':
|
|
109
|
+
return 'edge-pulse';
|
|
110
|
+
case 'glow':
|
|
111
|
+
return 'edge-glow';
|
|
112
|
+
default:
|
|
113
|
+
return '';
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const getAnimationDurationStyle = () => {
|
|
118
|
+
if (!animationType) return {};
|
|
119
|
+
return {
|
|
120
|
+
animationDuration: `${animationDuration}ms`,
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// Calculate particle position along path using SVG path methods
|
|
125
|
+
const getParticleTransform = () => {
|
|
126
|
+
if (!pathRef.current) {
|
|
127
|
+
// Fallback to linear interpolation if path ref not available yet
|
|
128
|
+
const progress = animationDirection === 'backward' ? 1 - particlePosition / 100 : particlePosition / 100;
|
|
129
|
+
const x = sourceX + (targetX - sourceX) * progress;
|
|
130
|
+
const y = sourceY + (targetY - sourceY) * progress;
|
|
131
|
+
return { x, y };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Use actual path to get point along the curve
|
|
135
|
+
const pathLength = pathRef.current.getTotalLength();
|
|
136
|
+
const progress = animationDirection === 'backward' ? 1 - particlePosition / 100 : particlePosition / 100;
|
|
137
|
+
const distance = pathLength * progress;
|
|
138
|
+
const point = pathRef.current.getPointAtLength(distance);
|
|
139
|
+
|
|
140
|
+
return { x: point.x, y: point.y };
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const particlePos = animationType === 'particle' ? getParticleTransform() : null;
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<>
|
|
147
|
+
{/* Hidden path for particle position calculation */}
|
|
148
|
+
<path
|
|
149
|
+
ref={pathRef}
|
|
150
|
+
d={edgePath}
|
|
151
|
+
fill="none"
|
|
152
|
+
stroke="transparent"
|
|
153
|
+
strokeWidth={0}
|
|
154
|
+
style={{ pointerEvents: 'none' }}
|
|
155
|
+
/>
|
|
156
|
+
|
|
157
|
+
{/* Invisible wide path for easier clicking */}
|
|
158
|
+
<path
|
|
159
|
+
d={edgePath}
|
|
160
|
+
fill="none"
|
|
161
|
+
stroke="transparent"
|
|
162
|
+
strokeWidth={Math.max(width + 10, 20)}
|
|
163
|
+
style={{ cursor: 'pointer' }}
|
|
164
|
+
/>
|
|
165
|
+
|
|
166
|
+
<BaseEdge
|
|
167
|
+
id={id}
|
|
168
|
+
path={edgePath}
|
|
169
|
+
markerEnd={markerEnd as string}
|
|
170
|
+
style={{
|
|
171
|
+
stroke: color,
|
|
172
|
+
strokeWidth: selected ? width + 2 : width,
|
|
173
|
+
strokeDasharray: getStrokeStyle(),
|
|
174
|
+
opacity: animationType ? 0.7 : 1,
|
|
175
|
+
cursor: 'pointer',
|
|
176
|
+
}}
|
|
177
|
+
/>
|
|
178
|
+
|
|
179
|
+
{/* Flow Animation - Animated dashed overlay */}
|
|
180
|
+
{animationType === 'flow' && (
|
|
181
|
+
<path
|
|
182
|
+
d={edgePath}
|
|
183
|
+
fill="none"
|
|
184
|
+
stroke={typeDefinition.animation?.color || color}
|
|
185
|
+
strokeWidth={width}
|
|
186
|
+
strokeDasharray="10 5"
|
|
187
|
+
className={getAnimationClass()}
|
|
188
|
+
style={{
|
|
189
|
+
...getAnimationDurationStyle(),
|
|
190
|
+
opacity: 0.8,
|
|
191
|
+
}}
|
|
192
|
+
/>
|
|
193
|
+
)}
|
|
194
|
+
|
|
195
|
+
{/* Pulse Animation - Wave effect */}
|
|
196
|
+
{animationType === 'pulse' && (
|
|
197
|
+
<path
|
|
198
|
+
d={edgePath}
|
|
199
|
+
fill="none"
|
|
200
|
+
stroke={typeDefinition.animation?.color || color}
|
|
201
|
+
strokeWidth={width + 2}
|
|
202
|
+
className={getAnimationClass()}
|
|
203
|
+
style={{
|
|
204
|
+
...getAnimationDurationStyle(),
|
|
205
|
+
}}
|
|
206
|
+
/>
|
|
207
|
+
)}
|
|
208
|
+
|
|
209
|
+
{/* Glow Animation - Brief highlight */}
|
|
210
|
+
{animationType === 'glow' && (
|
|
211
|
+
<path
|
|
212
|
+
d={edgePath}
|
|
213
|
+
fill="none"
|
|
214
|
+
stroke={typeDefinition.animation?.color || color}
|
|
215
|
+
strokeWidth={width + 4}
|
|
216
|
+
className={getAnimationClass()}
|
|
217
|
+
style={{
|
|
218
|
+
...getAnimationDurationStyle(),
|
|
219
|
+
filter: 'blur(3px)',
|
|
220
|
+
}}
|
|
221
|
+
/>
|
|
222
|
+
)}
|
|
223
|
+
|
|
224
|
+
{/* Particle Animation - Traveling dot */}
|
|
225
|
+
{animationType === 'particle' && particlePos && (
|
|
226
|
+
<circle
|
|
227
|
+
cx={particlePos.x}
|
|
228
|
+
cy={particlePos.y}
|
|
229
|
+
r={width * 1.5}
|
|
230
|
+
fill={typeDefinition.animation?.color || color}
|
|
231
|
+
style={{
|
|
232
|
+
filter: 'drop-shadow(0 0 3px rgba(0,0,0,0.3))',
|
|
233
|
+
}}
|
|
234
|
+
/>
|
|
235
|
+
)}
|
|
236
|
+
|
|
237
|
+
{/* Fallback: Legacy animated style support */}
|
|
238
|
+
{!animationType && typeDefinition.style === 'animated' && (
|
|
239
|
+
<path
|
|
240
|
+
d={edgePath}
|
|
241
|
+
fill="none"
|
|
242
|
+
stroke={typeDefinition.animation?.color || color}
|
|
243
|
+
strokeWidth={width}
|
|
244
|
+
strokeDasharray="5 5"
|
|
245
|
+
className="edge-flow-forward"
|
|
246
|
+
style={{
|
|
247
|
+
animationDuration: '500ms',
|
|
248
|
+
}}
|
|
249
|
+
/>
|
|
250
|
+
)}
|
|
251
|
+
|
|
252
|
+
{/* Label */}
|
|
253
|
+
{labelText && (
|
|
254
|
+
<EdgeLabelRenderer>
|
|
255
|
+
<div
|
|
256
|
+
style={{
|
|
257
|
+
position: 'absolute',
|
|
258
|
+
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
|
|
259
|
+
backgroundColor: 'white',
|
|
260
|
+
padding: '2px 6px',
|
|
261
|
+
borderRadius: '4px',
|
|
262
|
+
fontSize: '10px',
|
|
263
|
+
fontWeight: 500,
|
|
264
|
+
border: `1px solid ${color}`,
|
|
265
|
+
pointerEvents: 'all',
|
|
266
|
+
}}
|
|
267
|
+
className="nodrag nopan"
|
|
268
|
+
>
|
|
269
|
+
{labelText}
|
|
270
|
+
</div>
|
|
271
|
+
</EdgeLabelRenderer>
|
|
272
|
+
)}
|
|
273
|
+
|
|
274
|
+
{/* CSS animations for all edge animation types */}
|
|
275
|
+
<style>{`
|
|
276
|
+
/* Flow animation - forward direction */
|
|
277
|
+
.edge-flow-forward {
|
|
278
|
+
animation: flow-forward linear infinite;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
@keyframes flow-forward {
|
|
282
|
+
to {
|
|
283
|
+
stroke-dashoffset: -15;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/* Flow animation - backward direction */
|
|
288
|
+
.edge-flow-backward {
|
|
289
|
+
animation: flow-backward linear infinite;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
@keyframes flow-backward {
|
|
293
|
+
to {
|
|
294
|
+
stroke-dashoffset: 15;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/* Flow animation - bidirectional (alternating) */
|
|
299
|
+
.edge-flow-bidirectional {
|
|
300
|
+
animation: flow-bidirectional linear infinite alternate;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
@keyframes flow-bidirectional {
|
|
304
|
+
0% {
|
|
305
|
+
stroke-dashoffset: -15;
|
|
306
|
+
}
|
|
307
|
+
100% {
|
|
308
|
+
stroke-dashoffset: 15;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/* Pulse animation - wave effect */
|
|
313
|
+
.edge-pulse {
|
|
314
|
+
animation: pulse ease-in-out infinite;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
@keyframes pulse {
|
|
318
|
+
0%, 100% {
|
|
319
|
+
opacity: 0.3;
|
|
320
|
+
stroke-width: inherit;
|
|
321
|
+
}
|
|
322
|
+
50% {
|
|
323
|
+
opacity: 1;
|
|
324
|
+
stroke-width: calc(inherit + 2);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/* Glow animation - brief highlight */
|
|
329
|
+
.edge-glow {
|
|
330
|
+
animation: glow ease-out forwards;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
@keyframes glow {
|
|
334
|
+
0% {
|
|
335
|
+
opacity: 1;
|
|
336
|
+
}
|
|
337
|
+
100% {
|
|
338
|
+
opacity: 0;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
`}</style>
|
|
342
|
+
</>
|
|
343
|
+
);
|
|
344
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { EdgeTypeDefinition } from '@principal-ai/principal-view-core';
|
|
3
|
+
|
|
4
|
+
export interface GenericEdgeProps {
|
|
5
|
+
/** Edge ID */
|
|
6
|
+
id: string;
|
|
7
|
+
|
|
8
|
+
/** Edge type definition from configuration */
|
|
9
|
+
typeDefinition: EdgeTypeDefinition;
|
|
10
|
+
|
|
11
|
+
/** Edge data */
|
|
12
|
+
data?: Record<string, unknown>;
|
|
13
|
+
|
|
14
|
+
/** Whether this edge has violations */
|
|
15
|
+
hasViolations?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Generic edge renderer that adapts based on EdgeTypeDefinition
|
|
20
|
+
* TODO: Implement different styles, animations, and labels
|
|
21
|
+
*/
|
|
22
|
+
export const GenericEdge: React.FC<GenericEdgeProps> = ({
|
|
23
|
+
id,
|
|
24
|
+
typeDefinition,
|
|
25
|
+
hasViolations,
|
|
26
|
+
}) => {
|
|
27
|
+
const color = hasViolations ? 'red' : (typeDefinition.color || '#888');
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div>
|
|
31
|
+
{/* This is a placeholder - actual edge rendering happens in xyflow */}
|
|
32
|
+
<div style={{ fontSize: '10px', color }}>
|
|
33
|
+
Edge: {id} ({typeDefinition.style})
|
|
34
|
+
</div>
|
|
35
|
+
<div style={{ fontSize: '8px' }}>
|
|
36
|
+
<strong>TODO:</strong> Render in xyflow with proper styling
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
);
|
|
40
|
+
};
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook for processing path-based events and mapping them to animations
|
|
3
|
+
*
|
|
4
|
+
* Handles ComponentActivityEvent and ComponentActionEvent from
|
|
5
|
+
* PathBasedEventProcessor (Milestone 1 & 2)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useEffect, useCallback } from 'react';
|
|
9
|
+
import type {
|
|
10
|
+
ComponentActivityEvent,
|
|
11
|
+
ComponentActionEvent,
|
|
12
|
+
EdgeAnimationEvent,
|
|
13
|
+
PathBasedEvent
|
|
14
|
+
} from '@principal-ai/principal-view-core';
|
|
15
|
+
import {
|
|
16
|
+
logLevelToNodeAnimation,
|
|
17
|
+
actionToNodeAnimation,
|
|
18
|
+
actionToEdgeAnimation,
|
|
19
|
+
type NodeAnimation,
|
|
20
|
+
type EdgeAnimation
|
|
21
|
+
} from '../utils/animationMapping';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Animation update callbacks
|
|
25
|
+
*/
|
|
26
|
+
export interface AnimationCallbacks {
|
|
27
|
+
onNodeAnimation: (nodeId: string, animation: NodeAnimation & { timestamp: number }) => void;
|
|
28
|
+
onEdgeAnimation: (edgeId: string, animation: EdgeAnimation & { timestamp: number }) => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Hook options
|
|
33
|
+
*/
|
|
34
|
+
export interface UsePathBasedEventsOptions {
|
|
35
|
+
/** Path-based events to process */
|
|
36
|
+
events: PathBasedEvent[];
|
|
37
|
+
|
|
38
|
+
/** Callbacks for triggering animations */
|
|
39
|
+
callbacks: AnimationCallbacks;
|
|
40
|
+
|
|
41
|
+
/** Optional callback when event is processed */
|
|
42
|
+
onEventProcessed?: (event: PathBasedEvent) => void;
|
|
43
|
+
|
|
44
|
+
/** Minimum log level to trigger animations (default: 'info') */
|
|
45
|
+
minLogLevel?: 'debug' | 'info' | 'warn' | 'error';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Process path-based events and trigger appropriate animations
|
|
50
|
+
*/
|
|
51
|
+
export function usePathBasedEvents({
|
|
52
|
+
events,
|
|
53
|
+
callbacks,
|
|
54
|
+
onEventProcessed,
|
|
55
|
+
minLogLevel = 'info'
|
|
56
|
+
}: UsePathBasedEventsOptions): void {
|
|
57
|
+
const { onNodeAnimation, onEdgeAnimation } = callbacks;
|
|
58
|
+
|
|
59
|
+
// Process component activity events (Milestone 1)
|
|
60
|
+
const processActivityEvent = useCallback((event: ComponentActivityEvent) => {
|
|
61
|
+
// Map log level to animation
|
|
62
|
+
const animation = logLevelToNodeAnimation(event.level);
|
|
63
|
+
|
|
64
|
+
// Trigger node animation
|
|
65
|
+
onNodeAnimation(event.componentId, {
|
|
66
|
+
...animation,
|
|
67
|
+
timestamp: event.timestamp
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
onEventProcessed?.(event);
|
|
71
|
+
}, [onNodeAnimation, onEventProcessed]);
|
|
72
|
+
|
|
73
|
+
// Process component action events (Milestone 2)
|
|
74
|
+
const processActionEvent = useCallback((event: ComponentActionEvent) => {
|
|
75
|
+
// Map action/state to animation
|
|
76
|
+
const animation = actionToNodeAnimation(event.action, event.state);
|
|
77
|
+
|
|
78
|
+
// Trigger node animation
|
|
79
|
+
onNodeAnimation(event.componentId, {
|
|
80
|
+
...animation,
|
|
81
|
+
timestamp: event.timestamp
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
onEventProcessed?.(event);
|
|
85
|
+
}, [onNodeAnimation, onEventProcessed]);
|
|
86
|
+
|
|
87
|
+
// Process edge animation events (Milestone 2)
|
|
88
|
+
const processEdgeAnimationEvent = useCallback((event: EdgeAnimationEvent) => {
|
|
89
|
+
const animation = actionToEdgeAnimation(event.triggeredBy?.action || 'unknown', {
|
|
90
|
+
type: event.animation,
|
|
91
|
+
duration: event.duration,
|
|
92
|
+
direction: event.direction
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
onEdgeAnimation(event.edgeId, {
|
|
96
|
+
...animation,
|
|
97
|
+
timestamp: event.timestamp
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
onEventProcessed?.(event);
|
|
101
|
+
}, [onEdgeAnimation, onEventProcessed]);
|
|
102
|
+
|
|
103
|
+
// Process events when they change
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
if (events.length === 0) return;
|
|
106
|
+
|
|
107
|
+
// Process the latest event
|
|
108
|
+
const latestEvent = events[events.length - 1];
|
|
109
|
+
|
|
110
|
+
switch (latestEvent.type) {
|
|
111
|
+
case 'component-activity':
|
|
112
|
+
// Milestone 1: Component activity from log
|
|
113
|
+
// Check if log level is high enough to trigger animation
|
|
114
|
+
const levels = ['debug', 'info', 'warn', 'error'];
|
|
115
|
+
if (levels.indexOf(latestEvent.level) >= levels.indexOf(minLogLevel)) {
|
|
116
|
+
processActivityEvent(latestEvent);
|
|
117
|
+
}
|
|
118
|
+
break;
|
|
119
|
+
|
|
120
|
+
case 'component-action':
|
|
121
|
+
// Milestone 2: Specific action from pattern match
|
|
122
|
+
processActionEvent(latestEvent);
|
|
123
|
+
break;
|
|
124
|
+
|
|
125
|
+
case 'edge-animation':
|
|
126
|
+
// Milestone 2: Edge activation from action trigger
|
|
127
|
+
processEdgeAnimationEvent(latestEvent);
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
}, [events, processActivityEvent, processActionEvent, processEdgeAnimationEvent, minLogLevel]);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Process a batch of path-based events
|
|
135
|
+
* Useful for processing historical logs or bulk updates
|
|
136
|
+
*/
|
|
137
|
+
export function processBatchEvents(
|
|
138
|
+
events: PathBasedEvent[],
|
|
139
|
+
callbacks: AnimationCallbacks,
|
|
140
|
+
minLogLevel: 'debug' | 'info' | 'warn' | 'error' = 'info'
|
|
141
|
+
): void {
|
|
142
|
+
const levels = ['debug', 'info', 'warn', 'error'];
|
|
143
|
+
const minLevelIndex = levels.indexOf(minLogLevel);
|
|
144
|
+
|
|
145
|
+
for (const event of events) {
|
|
146
|
+
switch (event.type) {
|
|
147
|
+
case 'component-activity':
|
|
148
|
+
if (levels.indexOf(event.level) >= minLevelIndex) {
|
|
149
|
+
const animation = logLevelToNodeAnimation(event.level);
|
|
150
|
+
callbacks.onNodeAnimation(event.componentId, {
|
|
151
|
+
...animation,
|
|
152
|
+
timestamp: event.timestamp
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
break;
|
|
156
|
+
|
|
157
|
+
case 'component-action':
|
|
158
|
+
{
|
|
159
|
+
const animation = actionToNodeAnimation(event.action, event.state);
|
|
160
|
+
callbacks.onNodeAnimation(event.componentId, {
|
|
161
|
+
...animation,
|
|
162
|
+
timestamp: event.timestamp
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
break;
|
|
166
|
+
|
|
167
|
+
case 'edge-animation':
|
|
168
|
+
{
|
|
169
|
+
const animation = actionToEdgeAnimation(event.triggeredBy?.action || 'unknown', {
|
|
170
|
+
type: event.animation,
|
|
171
|
+
duration: event.duration,
|
|
172
|
+
direction: event.direction
|
|
173
|
+
});
|
|
174
|
+
callbacks.onEdgeAnimation(event.edgeId, {
|
|
175
|
+
...animation,
|
|
176
|
+
timestamp: event.timestamp
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @principal-ai/visual-validation-react
|
|
3
|
+
* React components for graph-based visual validation framework
|
|
4
|
+
*
|
|
5
|
+
* This library provides UI building blocks for creating graph visualization panels.
|
|
6
|
+
* The actual "panel" application should be built separately using these components.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Re-export types from core
|
|
10
|
+
export type {
|
|
11
|
+
GraphConfiguration,
|
|
12
|
+
GraphEvent,
|
|
13
|
+
GraphMetrics,
|
|
14
|
+
Violation,
|
|
15
|
+
Warning,
|
|
16
|
+
ValidationResult,
|
|
17
|
+
EventStream,
|
|
18
|
+
NodeTypeDefinition,
|
|
19
|
+
EdgeTypeDefinition,
|
|
20
|
+
ConnectionRule,
|
|
21
|
+
NodeState,
|
|
22
|
+
EdgeState,
|
|
23
|
+
ConfigurationFile,
|
|
24
|
+
ConfigurationLoadResult,
|
|
25
|
+
// Library types for loading .vgc/library.yaml
|
|
26
|
+
ComponentLibrary,
|
|
27
|
+
LibraryNodeComponent,
|
|
28
|
+
LibraryEdgeComponent,
|
|
29
|
+
} from '@principal-ai/principal-view-core';
|
|
30
|
+
|
|
31
|
+
// Export components
|
|
32
|
+
export { GraphRenderer } from './components/GraphRenderer';
|
|
33
|
+
export type { GraphRendererProps, GraphRendererHandle, NodePositionChange, PendingChanges } from './components/GraphRenderer';
|
|
34
|
+
|
|
35
|
+
export { EventLog } from './components/EventLog';
|
|
36
|
+
export type { EventLogProps } from './components/EventLog';
|
|
37
|
+
|
|
38
|
+
export { MetricsDashboard } from './components/MetricsDashboard';
|
|
39
|
+
export type { MetricsDashboardProps } from './components/MetricsDashboard';
|
|
40
|
+
|
|
41
|
+
export { EdgeInfoPanel } from './components/EdgeInfoPanel';
|
|
42
|
+
export type { EdgeInfoPanelProps } from './components/EdgeInfoPanel';
|
|
43
|
+
|
|
44
|
+
export { NodeInfoPanel } from './components/NodeInfoPanel';
|
|
45
|
+
export type { NodeInfoPanelProps } from './components/NodeInfoPanel';
|
|
46
|
+
|
|
47
|
+
export { ConfigurationSelector } from './components/ConfigurationSelector';
|
|
48
|
+
export type { ConfigurationSelectorProps } from './components/ConfigurationSelector';
|
|
49
|
+
|
|
50
|
+
// Export node/edge renderers
|
|
51
|
+
export { GenericNode } from './nodes/GenericNode';
|
|
52
|
+
export type { GenericNodeProps } from './nodes/GenericNode';
|
|
53
|
+
|
|
54
|
+
export { CustomNode } from './nodes/CustomNode';
|
|
55
|
+
export type { CustomNodeData } from './nodes/CustomNode';
|
|
56
|
+
|
|
57
|
+
export { GenericEdge } from './edges/GenericEdge';
|
|
58
|
+
export type { GenericEdgeProps } from './edges/GenericEdge';
|
|
59
|
+
|
|
60
|
+
export { CustomEdge } from './edges/CustomEdge';
|
|
61
|
+
export type { CustomEdgeData } from './edges/CustomEdge';
|
|
62
|
+
|
|
63
|
+
// Export utilities
|
|
64
|
+
export { convertToXYFlowNodes, convertToXYFlowEdges, autoLayoutNodes } from './utils/graphConverter';
|
|
65
|
+
export type { EdgeStateWithHandles } from './utils/graphConverter';
|
|
66
|
+
export { Icon, resolveIcon } from './utils/iconResolver';
|
|
67
|
+
export type { IconProps } from './utils/iconResolver';
|