@principal-ai/subsystems-react 0.17.0 → 0.19.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.
Files changed (108) hide show
  1. package/README.md +11 -85
  2. package/dist/index.d.ts +7 -44
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +6 -32
  5. package/dist/index.js.map +1 -1
  6. package/dist/subsystem/SubsystemCarousel.d.ts +36 -0
  7. package/dist/subsystem/SubsystemCarousel.d.ts.map +1 -0
  8. package/dist/subsystem/SubsystemCarousel.js +163 -0
  9. package/dist/subsystem/SubsystemCarousel.js.map +1 -0
  10. package/dist/subsystem/SubsystemComponentGraph.d.ts +25 -0
  11. package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
  12. package/dist/subsystem/SubsystemComponentGraph.js +329 -68
  13. package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
  14. package/package.json +4 -4
  15. package/src/index.ts +9 -199
  16. package/src/subsystem/SubsystemComponentGraph.tsx +396 -50
  17. package/src/components/ConfigurationSelector.tsx +0 -133
  18. package/src/components/GraphRenderer.tsx +0 -3195
  19. package/src/components/MultiCanvasRenderer.tsx +0 -295
  20. package/src/components/NodeTooltip.tsx +0 -279
  21. package/src/components/PendingChanges.test.tsx +0 -375
  22. package/src/components/SelectionSidebar.tsx +0 -352
  23. package/src/components/SequenceDiagramRenderer.tsx +0 -1156
  24. package/src/components/WorkflowSequenceDiagram.tsx +0 -270
  25. package/src/components/dashboard/DashboardRenderer.tsx +0 -317
  26. package/src/components/dashboard/MetricPanel.tsx +0 -254
  27. package/src/components/dashboard/MockDataProvider.ts +0 -330
  28. package/src/components/dashboard/components/BarChart.tsx +0 -299
  29. package/src/components/dashboard/components/LineChart.tsx +0 -279
  30. package/src/components/dashboard/components/MetricCard.tsx +0 -270
  31. package/src/components/dashboard/components/SourceLink.tsx +0 -63
  32. package/src/components/dashboard/components/TimeRangeSelector.tsx +0 -280
  33. package/src/components/dashboard/components/index.ts +0 -5
  34. package/src/components/dashboard/index.ts +0 -47
  35. package/src/components/dashboard/types.ts +0 -126
  36. package/src/components/state-view/PipelineView.tsx +0 -347
  37. package/src/components/state-view/index.ts +0 -14
  38. package/src/components/state-view/types.ts +0 -261
  39. package/src/components/state-view/useStateView.ts +0 -205
  40. package/src/contexts/GraphEditContext.tsx +0 -27
  41. package/src/contexts/TooltipPortalContext.ts +0 -8
  42. package/src/edges/CustomEdge.tsx +0 -402
  43. package/src/edges/GenericEdge.tsx +0 -36
  44. package/src/hooks/usePathBasedEvents.ts +0 -192
  45. package/src/hooks/useSequenceLayout.ts +0 -543
  46. package/src/hooks/useUndoRedo.ts +0 -144
  47. package/src/nodes/CustomNode.tsx +0 -1231
  48. package/src/nodes/GenericNode.tsx +0 -55
  49. package/src/nodes/otel/OtelBoundaryNode.tsx +0 -224
  50. package/src/nodes/otel/OtelEventNode.tsx +0 -225
  51. package/src/nodes/otel/OtelResourceNode.tsx +0 -251
  52. package/src/nodes/otel/OtelScopeNode.tsx +0 -221
  53. package/src/nodes/otel/OtelSpanConventionNode.tsx +0 -295
  54. package/src/nodes/otel/index.ts +0 -23
  55. package/src/nodes/otel/shared/NodeBadges.tsx +0 -295
  56. package/src/nodes/otel/shared/NodeContent.tsx +0 -204
  57. package/src/nodes/otel/shared/index.ts +0 -8
  58. package/src/nodes/otel/shared/types.ts +0 -138
  59. package/src/nodes/otel/shared/useNodeBehavior.ts +0 -114
  60. package/src/stories/Audit/BoundaryNodes.stories.tsx +0 -385
  61. package/src/stories/Audit/CanvasEdgeTypes.stories.tsx +0 -1448
  62. package/src/stories/Audit/CanvasNodeTypes.stories.tsx +0 -1049
  63. package/src/stories/Audit/ColorPriority.stories.tsx +0 -895
  64. package/src/stories/Audit/DiamondBadges.stories.tsx +0 -629
  65. package/src/stories/Audit/NodeDimensionsTesting.stories.tsx +0 -724
  66. package/src/stories/Audit/NodeFieldsAudit.stories.tsx +0 -996
  67. package/src/stories/Audit/NodeFontSizeTesting.stories.tsx +0 -715
  68. package/src/stories/Audit/NodeShapes.stories.tsx +0 -1208
  69. package/src/stories/Audit/WideNodesTesting.stories.tsx +0 -1045
  70. package/src/stories/Components/DashboardRenderer.stories.tsx +0 -263
  71. package/src/stories/Components/GraphRenderer.stories.tsx +0 -3236
  72. package/src/stories/Components/MultiCanvasRenderer.stories.tsx +0 -370
  73. package/src/stories/Components/SequenceDiagram.stories.tsx +0 -255
  74. package/src/stories/Components/StateView.stories.tsx +0 -417
  75. package/src/stories/Components/TraceViewer.stories.tsx +0 -442
  76. package/src/stories/Components/WorkflowSequenceDiagram.stories.tsx +0 -802
  77. package/src/stories/Features/AnimationWorkshop.stories.tsx +0 -727
  78. package/src/stories/Features/ChangeTypeVisual.stories.tsx +0 -67
  79. package/src/stories/Features/EventDrivenAnimations.stories.tsx +0 -513
  80. package/src/stories/Features/MultiConfig.stories.tsx +0 -539
  81. package/src/stories/Layout/ElkEdgeRouting.stories.tsx +0 -539
  82. package/src/stories/Layout/GraphOrientation.stories.tsx +0 -500
  83. package/src/stories/Layout/MultiDirectionalConnections.stories.tsx +0 -352
  84. package/src/stories/Nodes/NodeDefinitionComparison.stories.tsx +0 -560
  85. package/src/stories/Nodes/NodeNameFallbacks.stories.tsx +0 -491
  86. package/src/stories/Nodes/NumericColors.stories.tsx +0 -440
  87. package/src/stories/OTEL/EventNameDisplay.stories.tsx +0 -694
  88. package/src/stories/OTEL/EventNameFallback.stories.tsx +0 -578
  89. package/src/stories/OTEL/OtelComponents.stories.tsx +0 -422
  90. package/src/stories/OTEL/OtelNodeTypesPrototype.stories.tsx +0 -806
  91. package/src/stories/OTEL/RawEventCards.stories.tsx +0 -57
  92. package/src/stories/OTEL/SpanBadges.stories.tsx +0 -840
  93. package/src/stories/OTEL/ValidationEventsCanvas.stories.tsx +0 -209
  94. package/src/stories/RealWorld/BacklogMdCanvases.stories.tsx +0 -721
  95. package/src/stories/RealWorld/FileCitySequence.stories.tsx +0 -516
  96. package/src/stories/RealWorld/RealCanvasFiles.stories.tsx +0 -336
  97. package/src/stories/__traces__/test-run.canvas.json +0 -241
  98. package/src/stories/data/activity-feed-analytics.dashboard.json +0 -300
  99. package/src/stories/data/file-city-images-transformed.otel.canvas.json +0 -295
  100. package/src/stories/data/file-city-workflows.json +0 -269
  101. package/src/stories/data/graph-converter-test-execution.json +0 -443
  102. package/src/stories/data/graph-converter-test.workflow.json +0 -105
  103. package/src/stories/data/graph-converter-validated-execution.json +0 -58
  104. package/src/utils/animationMapping.ts +0 -166
  105. package/src/utils/canvasBounds.ts +0 -146
  106. package/src/utils/graphConverter.ts +0 -148
  107. package/src/utils/iconResolver.tsx +0 -53
  108. package/src/utils/orientationUtils.ts +0 -73
@@ -1,402 +0,0 @@
1
- import React, { useState, useEffect, useRef } from 'react';
2
- import { BaseEdge, EdgeLabelRenderer, getSmoothStepPath } from '@xyflow/react';
3
- import type { EdgeProps, Edge } from '@xyflow/react';
4
- import type { EdgeTypeDefinition } from '@principal-ai/subsystems-core';
5
- import { useTheme } from '@principal-ade/industry-theme';
6
-
7
- export interface CustomEdgeData extends Record<string, unknown> {
8
- typeDefinition: EdgeTypeDefinition;
9
- hasViolations?: boolean;
10
- data?: Record<string, unknown>;
11
- edgeType?: string;
12
- // Animation control
13
- animationType?: 'flow' | 'particle' | 'pulse' | 'glow' | null;
14
- animationDuration?: number;
15
- animationDirection?: 'forward' | 'backward' | 'bidirectional';
16
- // Whether tooltips are enabled (defaults to true)
17
- tooltipsEnabled?: boolean;
18
- // Whether shift key is currently pressed (for tooltip control)
19
- shiftKeyPressed?: boolean;
20
- // ELK-computed path (circuit-board style routing)
21
- elkPath?: string;
22
- // ELK-computed label position
23
- elkLabelPosition?: { x: number; y: number };
24
- }
25
-
26
- /**
27
- * Custom edge component for xyflow that renders based on EdgeTypeDefinition
28
- */
29
- export const CustomEdge: React.FC<EdgeProps<Edge<CustomEdgeData>>> = ({
30
- id,
31
- sourceX,
32
- sourceY,
33
- targetX,
34
- targetY,
35
- sourcePosition,
36
- targetPosition,
37
- data,
38
- markerEnd,
39
- selected,
40
- }) => {
41
- const { theme } = useTheme();
42
- const edgeProps = data;
43
- const {
44
- typeDefinition,
45
- hasViolations,
46
- data: edgeData,
47
- edgeType,
48
- animationType,
49
- animationDuration = 1000,
50
- animationDirection = 'forward',
51
- tooltipsEnabled = true,
52
- shiftKeyPressed = false,
53
- elkPath,
54
- elkLabelPosition,
55
- } = edgeProps || ({} as CustomEdgeData);
56
-
57
- const [particlePosition, setParticlePosition] = useState(0);
58
- const [isHovered, setIsHovered] = useState(false);
59
- const pathRef = useRef<SVGPathElement>(null);
60
-
61
- // Particle animation effect
62
- useEffect(() => {
63
- if (animationType !== 'particle') return;
64
-
65
- const animate = () => {
66
- setParticlePosition((prev) => {
67
- const next = prev + (100 / animationDuration) * 16; // ~60fps
68
- return next >= 100 ? 0 : next;
69
- });
70
- };
71
-
72
- const intervalId = setInterval(animate, 16);
73
- return () => clearInterval(intervalId);
74
- }, [animationType, animationDuration]);
75
-
76
- // Early return after hooks
77
- if (!typeDefinition) {
78
- return null;
79
- }
80
-
81
- // Color priority: violations > edge data color > type definition color > default
82
- const edgeColor = edgeData?.color as string | undefined;
83
- const color = hasViolations ? '#D0021B' : edgeColor || typeDefinition.color || '#888';
84
- const width = typeDefinition.width || 2;
85
-
86
- // Get edge path - use ELK path if available, otherwise fall back to SmoothStep
87
- const [defaultPath, defaultLabelX, defaultLabelY] = getSmoothStepPath({
88
- sourceX,
89
- sourceY,
90
- sourcePosition,
91
- targetX,
92
- targetY,
93
- targetPosition,
94
- borderRadius: 0,
95
- offset: 20,
96
- });
97
-
98
- // Use ELK-computed path for circuit-board style routing when available
99
- const edgePath = elkPath || defaultPath;
100
- const labelX = elkLabelPosition?.x ?? defaultLabelX;
101
- const labelY = elkLabelPosition?.y ?? defaultLabelY;
102
-
103
- // Style based on edge type
104
- const getStrokeStyle = () => {
105
- switch (typeDefinition.style) {
106
- case 'dashed':
107
- return '5 5';
108
- case 'dotted':
109
- return '2 2';
110
- default:
111
- return 'none';
112
- }
113
- };
114
-
115
- // Label configuration
116
- const labelConfig = typeDefinition.label;
117
- const labelField = labelConfig?.field;
118
- const labelText = labelField && edgeData?.[labelField] ? String(edgeData[labelField]) : '';
119
-
120
- // Animation-specific rendering helpers
121
- const getAnimationClass = () => {
122
- if (!animationType) {
123
- return typeDefinition.style === 'animated' ? 'edge-flow-forward' : '';
124
- }
125
-
126
- switch (animationType) {
127
- case 'flow':
128
- return animationDirection === 'backward'
129
- ? 'edge-flow-backward'
130
- : animationDirection === 'bidirectional'
131
- ? 'edge-flow-bidirectional'
132
- : 'edge-flow-forward';
133
- case 'pulse':
134
- return 'edge-pulse';
135
- case 'glow':
136
- return 'edge-glow';
137
- default:
138
- return '';
139
- }
140
- };
141
-
142
- const getAnimationDurationStyle = () => {
143
- if (!animationType) return {};
144
- return {
145
- animationDuration: `${animationDuration}ms`,
146
- };
147
- };
148
-
149
- // Calculate particle position along path using SVG path methods
150
- const getParticleTransform = () => {
151
- if (!pathRef.current) {
152
- // Fallback to linear interpolation if path ref not available yet
153
- const progress =
154
- animationDirection === 'backward' ? 1 - particlePosition / 100 : particlePosition / 100;
155
- const x = sourceX + (targetX - sourceX) * progress;
156
- const y = sourceY + (targetY - sourceY) * progress;
157
- return { x, y };
158
- }
159
-
160
- // Use actual path to get point along the curve
161
- const pathLength = pathRef.current.getTotalLength();
162
- const progress =
163
- animationDirection === 'backward' ? 1 - particlePosition / 100 : particlePosition / 100;
164
- const distance = pathLength * progress;
165
- const point = pathRef.current.getPointAtLength(distance);
166
-
167
- return { x: point.x, y: point.y };
168
- };
169
-
170
- const particlePos = animationType === 'particle' ? getParticleTransform() : null;
171
-
172
- return (
173
- <g
174
- onMouseEnter={() => setIsHovered(true)}
175
- onMouseLeave={() => setIsHovered(false)}
176
- style={{ cursor: 'pointer' }}
177
- >
178
- {/* Hidden path for particle position calculation */}
179
- <path
180
- ref={pathRef}
181
- d={edgePath}
182
- fill="none"
183
- stroke="transparent"
184
- strokeWidth={0}
185
- style={{ pointerEvents: 'none' }}
186
- />
187
-
188
- {/* Invisible wide path for easier clicking */}
189
- <path
190
- d={edgePath}
191
- fill="none"
192
- stroke="transparent"
193
- strokeWidth={Math.max(width + 10, 20)}
194
- style={{ pointerEvents: 'stroke' }}
195
- />
196
-
197
- <BaseEdge
198
- id={id}
199
- path={edgePath}
200
- markerEnd={markerEnd as string}
201
- style={{
202
- stroke: color,
203
- strokeWidth: selected ? width + 2 : width,
204
- strokeDasharray: getStrokeStyle(),
205
- opacity: animationType ? 0.7 : 1,
206
- cursor: 'pointer',
207
- }}
208
- />
209
-
210
- {/* Flow Animation - Animated dashed overlay */}
211
- {animationType === 'flow' && (
212
- <path
213
- d={edgePath}
214
- fill="none"
215
- stroke={typeDefinition.animation?.color || color}
216
- strokeWidth={width}
217
- strokeDasharray="10 5"
218
- className={getAnimationClass()}
219
- style={{
220
- ...getAnimationDurationStyle(),
221
- opacity: 0.8,
222
- }}
223
- />
224
- )}
225
-
226
- {/* Pulse Animation - Wave effect */}
227
- {animationType === 'pulse' && (
228
- <path
229
- d={edgePath}
230
- fill="none"
231
- stroke={typeDefinition.animation?.color || color}
232
- strokeWidth={width + 2}
233
- className={getAnimationClass()}
234
- style={{
235
- ...getAnimationDurationStyle(),
236
- }}
237
- />
238
- )}
239
-
240
- {/* Glow Animation - Brief highlight */}
241
- {animationType === 'glow' && (
242
- <path
243
- d={edgePath}
244
- fill="none"
245
- stroke={typeDefinition.animation?.color || color}
246
- strokeWidth={width + 4}
247
- className={getAnimationClass()}
248
- style={{
249
- ...getAnimationDurationStyle(),
250
- filter: 'blur(3px)',
251
- }}
252
- />
253
- )}
254
-
255
- {/* Particle Animation - Traveling dot */}
256
- {animationType === 'particle' && particlePos && (
257
- <circle
258
- cx={particlePos.x}
259
- cy={particlePos.y}
260
- r={width * 1.5}
261
- fill={typeDefinition.animation?.color || color}
262
- style={{
263
- filter: 'drop-shadow(0 0 3px rgba(0,0,0,0.3))',
264
- }}
265
- />
266
- )}
267
-
268
- {/* Fallback: Legacy animated style support */}
269
- {!animationType && typeDefinition.style === 'animated' && (
270
- <path
271
- d={edgePath}
272
- fill="none"
273
- stroke={typeDefinition.animation?.color || color}
274
- strokeWidth={width}
275
- strokeDasharray="5 5"
276
- className="edge-flow-forward"
277
- style={{
278
- animationDuration: '500ms',
279
- }}
280
- />
281
- )}
282
-
283
- {/* Label */}
284
- {labelText && (
285
- <EdgeLabelRenderer>
286
- <div
287
- style={{
288
- position: 'absolute',
289
- transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
290
- backgroundColor: 'white',
291
- padding: '2px 6px',
292
- borderRadius: '4px',
293
- fontSize: theme.fontSizes[0],
294
- fontWeight: theme.fontWeights.medium,
295
- fontFamily: theme.fonts.body,
296
- border: `1px solid ${color}`,
297
- pointerEvents: 'all',
298
- zIndex: 1000,
299
- }}
300
- className="nodrag nopan"
301
- >
302
- {labelText}
303
- </div>
304
- </EdgeLabelRenderer>
305
- )}
306
-
307
- {/* Hover tooltip showing edge type - only shown when shift key is pressed */}
308
- {tooltipsEnabled && isHovered && shiftKeyPressed && (
309
- <EdgeLabelRenderer>
310
- <div
311
- style={{
312
- position: 'absolute',
313
- transform: `translate(-50%, -100%) translate(${labelX}px,${labelY - 12}px)`,
314
- backgroundColor: 'rgba(0, 0, 0, 0.85)',
315
- color: 'white',
316
- padding: '4px 8px',
317
- borderRadius: '4px',
318
- fontSize: theme.fontSizes[0],
319
- fontWeight: theme.fontWeights.medium,
320
- fontFamily: theme.fonts.body,
321
- whiteSpace: 'nowrap',
322
- pointerEvents: 'none',
323
- zIndex: 1000,
324
- boxShadow: '0 2px 8px rgba(0,0,0,0.2)',
325
- }}
326
- >
327
- {edgeType || 'edge'}
328
- </div>
329
- </EdgeLabelRenderer>
330
- )}
331
-
332
- {/* CSS animations for all edge animation types */}
333
- <style>{`
334
- /* Flow animation - forward direction */
335
- .edge-flow-forward {
336
- animation: flow-forward linear infinite;
337
- }
338
-
339
- @keyframes flow-forward {
340
- to {
341
- stroke-dashoffset: -15;
342
- }
343
- }
344
-
345
- /* Flow animation - backward direction */
346
- .edge-flow-backward {
347
- animation: flow-backward linear infinite;
348
- }
349
-
350
- @keyframes flow-backward {
351
- to {
352
- stroke-dashoffset: 15;
353
- }
354
- }
355
-
356
- /* Flow animation - bidirectional (alternating) */
357
- .edge-flow-bidirectional {
358
- animation: flow-bidirectional linear infinite alternate;
359
- }
360
-
361
- @keyframes flow-bidirectional {
362
- 0% {
363
- stroke-dashoffset: -15;
364
- }
365
- 100% {
366
- stroke-dashoffset: 15;
367
- }
368
- }
369
-
370
- /* Pulse animation - wave effect */
371
- .edge-pulse {
372
- animation: pulse ease-in-out infinite;
373
- }
374
-
375
- @keyframes pulse {
376
- 0%, 100% {
377
- opacity: 0.3;
378
- stroke-width: inherit;
379
- }
380
- 50% {
381
- opacity: 1;
382
- stroke-width: calc(inherit + 2);
383
- }
384
- }
385
-
386
- /* Glow animation - brief highlight */
387
- .edge-glow {
388
- animation: glow ease-out forwards;
389
- }
390
-
391
- @keyframes glow {
392
- 0% {
393
- opacity: 1;
394
- }
395
- 100% {
396
- opacity: 0;
397
- }
398
- }
399
- `}</style>
400
- </g>
401
- );
402
- };
@@ -1,36 +0,0 @@
1
- import React from 'react';
2
- import type { EdgeTypeDefinition } from '@principal-ai/subsystems-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> = ({ id, typeDefinition, hasViolations }) => {
23
- const color = hasViolations ? 'red' : typeDefinition.color || '#888';
24
-
25
- return (
26
- <div>
27
- {/* This is a placeholder - actual edge rendering happens in xyflow */}
28
- <div style={{ fontSize: '10px', color }}>
29
- Edge: {id} ({typeDefinition.style})
30
- </div>
31
- <div style={{ fontSize: '8px' }}>
32
- <strong>TODO:</strong> Render in xyflow with proper styling
33
- </div>
34
- </div>
35
- );
36
- };
@@ -1,192 +0,0 @@
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/subsystems-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(
61
- (event: ComponentActivityEvent) => {
62
- // Map log level to animation
63
- const animation = logLevelToNodeAnimation(event.level);
64
-
65
- // Trigger node animation
66
- onNodeAnimation(event.componentId, {
67
- ...animation,
68
- timestamp: event.timestamp,
69
- });
70
-
71
- onEventProcessed?.(event);
72
- },
73
- [onNodeAnimation, onEventProcessed]
74
- );
75
-
76
- // Process component action events (Milestone 2)
77
- const processActionEvent = useCallback(
78
- (event: ComponentActionEvent) => {
79
- // Map action/state to animation
80
- const animation = actionToNodeAnimation(event.action, event.state);
81
-
82
- // Trigger node animation
83
- onNodeAnimation(event.componentId, {
84
- ...animation,
85
- timestamp: event.timestamp,
86
- });
87
-
88
- onEventProcessed?.(event);
89
- },
90
- [onNodeAnimation, onEventProcessed]
91
- );
92
-
93
- // Process edge animation events (Milestone 2)
94
- const processEdgeAnimationEvent = useCallback(
95
- (event: EdgeAnimationEvent) => {
96
- const animation = actionToEdgeAnimation(event.triggeredBy?.action || 'unknown', {
97
- type: event.animation,
98
- duration: event.duration,
99
- direction: event.direction,
100
- });
101
-
102
- onEdgeAnimation(event.edgeId, {
103
- ...animation,
104
- timestamp: event.timestamp,
105
- });
106
-
107
- onEventProcessed?.(event);
108
- },
109
- [onEdgeAnimation, onEventProcessed]
110
- );
111
-
112
- // Process events when they change
113
- useEffect(() => {
114
- if (events.length === 0) return;
115
-
116
- // Process the latest event
117
- const latestEvent = events[events.length - 1];
118
-
119
- switch (latestEvent.type) {
120
- case 'component-activity': {
121
- // Milestone 1: Component activity from log
122
- // Check if log level is high enough to trigger animation
123
- const levels = ['debug', 'info', 'warn', 'error'];
124
- if (levels.indexOf(latestEvent.level) >= levels.indexOf(minLogLevel)) {
125
- processActivityEvent(latestEvent);
126
- }
127
- break;
128
- }
129
-
130
- case 'component-action':
131
- // Milestone 2: Specific action from pattern match
132
- processActionEvent(latestEvent);
133
- break;
134
-
135
- case 'edge-animation':
136
- // Milestone 2: Edge activation from action trigger
137
- processEdgeAnimationEvent(latestEvent);
138
- break;
139
- }
140
- }, [events, processActivityEvent, processActionEvent, processEdgeAnimationEvent, minLogLevel]);
141
- }
142
-
143
- /**
144
- * Process a batch of path-based events
145
- * Useful for processing historical logs or bulk updates
146
- */
147
- export function processBatchEvents(
148
- events: PathBasedEvent[],
149
- callbacks: AnimationCallbacks,
150
- minLogLevel: 'debug' | 'info' | 'warn' | 'error' = 'info'
151
- ): void {
152
- const levels = ['debug', 'info', 'warn', 'error'];
153
- const minLevelIndex = levels.indexOf(minLogLevel);
154
-
155
- for (const event of events) {
156
- switch (event.type) {
157
- case 'component-activity':
158
- if (levels.indexOf(event.level) >= minLevelIndex) {
159
- const animation = logLevelToNodeAnimation(event.level);
160
- callbacks.onNodeAnimation(event.componentId, {
161
- ...animation,
162
- timestamp: event.timestamp,
163
- });
164
- }
165
- break;
166
-
167
- case 'component-action':
168
- {
169
- const animation = actionToNodeAnimation(event.action, event.state);
170
- callbacks.onNodeAnimation(event.componentId, {
171
- ...animation,
172
- timestamp: event.timestamp,
173
- });
174
- }
175
- break;
176
-
177
- case 'edge-animation':
178
- {
179
- const animation = actionToEdgeAnimation(event.triggeredBy?.action || 'unknown', {
180
- type: event.animation,
181
- duration: event.duration,
182
- direction: event.direction,
183
- });
184
- callbacks.onEdgeAnimation(event.edgeId, {
185
- ...animation,
186
- timestamp: event.timestamp,
187
- });
188
- }
189
- break;
190
- }
191
- }
192
- }