@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.
- package/README.md +11 -85
- package/dist/index.d.ts +7 -44
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -32
- package/dist/index.js.map +1 -1
- package/dist/subsystem/SubsystemCarousel.d.ts +36 -0
- package/dist/subsystem/SubsystemCarousel.d.ts.map +1 -0
- package/dist/subsystem/SubsystemCarousel.js +163 -0
- package/dist/subsystem/SubsystemCarousel.js.map +1 -0
- package/dist/subsystem/SubsystemComponentGraph.d.ts +25 -0
- package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.js +329 -68
- package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
- package/package.json +4 -4
- package/src/index.ts +9 -199
- package/src/subsystem/SubsystemComponentGraph.tsx +396 -50
- package/src/components/ConfigurationSelector.tsx +0 -133
- package/src/components/GraphRenderer.tsx +0 -3195
- package/src/components/MultiCanvasRenderer.tsx +0 -295
- package/src/components/NodeTooltip.tsx +0 -279
- package/src/components/PendingChanges.test.tsx +0 -375
- package/src/components/SelectionSidebar.tsx +0 -352
- package/src/components/SequenceDiagramRenderer.tsx +0 -1156
- package/src/components/WorkflowSequenceDiagram.tsx +0 -270
- package/src/components/dashboard/DashboardRenderer.tsx +0 -317
- package/src/components/dashboard/MetricPanel.tsx +0 -254
- package/src/components/dashboard/MockDataProvider.ts +0 -330
- package/src/components/dashboard/components/BarChart.tsx +0 -299
- package/src/components/dashboard/components/LineChart.tsx +0 -279
- package/src/components/dashboard/components/MetricCard.tsx +0 -270
- package/src/components/dashboard/components/SourceLink.tsx +0 -63
- package/src/components/dashboard/components/TimeRangeSelector.tsx +0 -280
- package/src/components/dashboard/components/index.ts +0 -5
- package/src/components/dashboard/index.ts +0 -47
- package/src/components/dashboard/types.ts +0 -126
- package/src/components/state-view/PipelineView.tsx +0 -347
- package/src/components/state-view/index.ts +0 -14
- package/src/components/state-view/types.ts +0 -261
- package/src/components/state-view/useStateView.ts +0 -205
- package/src/contexts/GraphEditContext.tsx +0 -27
- package/src/contexts/TooltipPortalContext.ts +0 -8
- package/src/edges/CustomEdge.tsx +0 -402
- package/src/edges/GenericEdge.tsx +0 -36
- package/src/hooks/usePathBasedEvents.ts +0 -192
- package/src/hooks/useSequenceLayout.ts +0 -543
- package/src/hooks/useUndoRedo.ts +0 -144
- package/src/nodes/CustomNode.tsx +0 -1231
- package/src/nodes/GenericNode.tsx +0 -55
- package/src/nodes/otel/OtelBoundaryNode.tsx +0 -224
- package/src/nodes/otel/OtelEventNode.tsx +0 -225
- package/src/nodes/otel/OtelResourceNode.tsx +0 -251
- package/src/nodes/otel/OtelScopeNode.tsx +0 -221
- package/src/nodes/otel/OtelSpanConventionNode.tsx +0 -295
- package/src/nodes/otel/index.ts +0 -23
- package/src/nodes/otel/shared/NodeBadges.tsx +0 -295
- package/src/nodes/otel/shared/NodeContent.tsx +0 -204
- package/src/nodes/otel/shared/index.ts +0 -8
- package/src/nodes/otel/shared/types.ts +0 -138
- package/src/nodes/otel/shared/useNodeBehavior.ts +0 -114
- package/src/stories/Audit/BoundaryNodes.stories.tsx +0 -385
- package/src/stories/Audit/CanvasEdgeTypes.stories.tsx +0 -1448
- package/src/stories/Audit/CanvasNodeTypes.stories.tsx +0 -1049
- package/src/stories/Audit/ColorPriority.stories.tsx +0 -895
- package/src/stories/Audit/DiamondBadges.stories.tsx +0 -629
- package/src/stories/Audit/NodeDimensionsTesting.stories.tsx +0 -724
- package/src/stories/Audit/NodeFieldsAudit.stories.tsx +0 -996
- package/src/stories/Audit/NodeFontSizeTesting.stories.tsx +0 -715
- package/src/stories/Audit/NodeShapes.stories.tsx +0 -1208
- package/src/stories/Audit/WideNodesTesting.stories.tsx +0 -1045
- package/src/stories/Components/DashboardRenderer.stories.tsx +0 -263
- package/src/stories/Components/GraphRenderer.stories.tsx +0 -3236
- package/src/stories/Components/MultiCanvasRenderer.stories.tsx +0 -370
- package/src/stories/Components/SequenceDiagram.stories.tsx +0 -255
- package/src/stories/Components/StateView.stories.tsx +0 -417
- package/src/stories/Components/TraceViewer.stories.tsx +0 -442
- package/src/stories/Components/WorkflowSequenceDiagram.stories.tsx +0 -802
- package/src/stories/Features/AnimationWorkshop.stories.tsx +0 -727
- package/src/stories/Features/ChangeTypeVisual.stories.tsx +0 -67
- package/src/stories/Features/EventDrivenAnimations.stories.tsx +0 -513
- package/src/stories/Features/MultiConfig.stories.tsx +0 -539
- package/src/stories/Layout/ElkEdgeRouting.stories.tsx +0 -539
- package/src/stories/Layout/GraphOrientation.stories.tsx +0 -500
- package/src/stories/Layout/MultiDirectionalConnections.stories.tsx +0 -352
- package/src/stories/Nodes/NodeDefinitionComparison.stories.tsx +0 -560
- package/src/stories/Nodes/NodeNameFallbacks.stories.tsx +0 -491
- package/src/stories/Nodes/NumericColors.stories.tsx +0 -440
- package/src/stories/OTEL/EventNameDisplay.stories.tsx +0 -694
- package/src/stories/OTEL/EventNameFallback.stories.tsx +0 -578
- package/src/stories/OTEL/OtelComponents.stories.tsx +0 -422
- package/src/stories/OTEL/OtelNodeTypesPrototype.stories.tsx +0 -806
- package/src/stories/OTEL/RawEventCards.stories.tsx +0 -57
- package/src/stories/OTEL/SpanBadges.stories.tsx +0 -840
- package/src/stories/OTEL/ValidationEventsCanvas.stories.tsx +0 -209
- package/src/stories/RealWorld/BacklogMdCanvases.stories.tsx +0 -721
- package/src/stories/RealWorld/FileCitySequence.stories.tsx +0 -516
- package/src/stories/RealWorld/RealCanvasFiles.stories.tsx +0 -336
- package/src/stories/__traces__/test-run.canvas.json +0 -241
- package/src/stories/data/activity-feed-analytics.dashboard.json +0 -300
- package/src/stories/data/file-city-images-transformed.otel.canvas.json +0 -295
- package/src/stories/data/file-city-workflows.json +0 -269
- package/src/stories/data/graph-converter-test-execution.json +0 -443
- package/src/stories/data/graph-converter-test.workflow.json +0 -105
- package/src/stories/data/graph-converter-validated-execution.json +0 -58
- package/src/utils/animationMapping.ts +0 -166
- package/src/utils/canvasBounds.ts +0 -146
- package/src/utils/graphConverter.ts +0 -148
- package/src/utils/iconResolver.tsx +0 -53
- package/src/utils/orientationUtils.ts +0 -73
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Animation mapping utilities
|
|
3
|
-
*
|
|
4
|
-
* Maps log levels and component events to appropriate animations
|
|
5
|
-
* for Milestone 1 default visualization behavior
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { LogLevel } from '@principal-ai/subsystems-core';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Node animation configuration
|
|
12
|
-
*/
|
|
13
|
-
export interface NodeAnimation {
|
|
14
|
-
type: 'pulse' | 'flash' | 'shake' | 'entry';
|
|
15
|
-
duration: number;
|
|
16
|
-
intensity?: number; // 0-1 scale
|
|
17
|
-
color?: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Edge animation configuration
|
|
22
|
-
*/
|
|
23
|
-
export interface EdgeAnimation {
|
|
24
|
-
type: 'flow' | 'particle' | 'pulse' | 'glow';
|
|
25
|
-
duration: number;
|
|
26
|
-
direction?: 'forward' | 'backward' | 'bidirectional';
|
|
27
|
-
color?: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Map log level to node animation
|
|
32
|
-
*
|
|
33
|
-
* Default behavior for Milestone 1:
|
|
34
|
-
* - debug: subtle pulse (low intensity)
|
|
35
|
-
* - info: standard pulse (medium intensity)
|
|
36
|
-
* - warn: amber pulse (high intensity)
|
|
37
|
-
* - error: red flash + shake
|
|
38
|
-
*/
|
|
39
|
-
export function logLevelToNodeAnimation(level: LogLevel): NodeAnimation {
|
|
40
|
-
switch (level) {
|
|
41
|
-
case 'debug':
|
|
42
|
-
return {
|
|
43
|
-
type: 'pulse',
|
|
44
|
-
duration: 800,
|
|
45
|
-
intensity: 0.3,
|
|
46
|
-
color: '#94a3b8', // slate-400
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
case 'info':
|
|
50
|
-
return {
|
|
51
|
-
type: 'pulse',
|
|
52
|
-
duration: 1000,
|
|
53
|
-
intensity: 0.5,
|
|
54
|
-
color: '#3b82f6', // blue-500
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
case 'warn':
|
|
58
|
-
return {
|
|
59
|
-
type: 'pulse',
|
|
60
|
-
duration: 1200,
|
|
61
|
-
intensity: 1.0,
|
|
62
|
-
color: '#f59e0b', // amber-500
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
case 'error':
|
|
66
|
-
return {
|
|
67
|
-
type: 'flash', // More dramatic animation for errors
|
|
68
|
-
duration: 1500,
|
|
69
|
-
intensity: 1.0,
|
|
70
|
-
color: '#ef4444', // red-500
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Map component action to node animation (Milestone 2)
|
|
77
|
-
*/
|
|
78
|
-
export function actionToNodeAnimation(_action: string, state?: string): NodeAnimation {
|
|
79
|
-
// Default mapping - can be overridden by configuration
|
|
80
|
-
switch (state) {
|
|
81
|
-
case 'acquired':
|
|
82
|
-
case 'active':
|
|
83
|
-
case 'processing':
|
|
84
|
-
return {
|
|
85
|
-
type: 'pulse',
|
|
86
|
-
duration: 1000,
|
|
87
|
-
intensity: 0.8,
|
|
88
|
-
color: '#22c55e', // green-500
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
case 'waiting':
|
|
92
|
-
case 'pending':
|
|
93
|
-
return {
|
|
94
|
-
type: 'pulse',
|
|
95
|
-
duration: 1500,
|
|
96
|
-
intensity: 0.5,
|
|
97
|
-
color: '#eab308', // yellow-500
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
case 'error':
|
|
101
|
-
case 'failed':
|
|
102
|
-
return {
|
|
103
|
-
type: 'shake',
|
|
104
|
-
duration: 600,
|
|
105
|
-
intensity: 1.0,
|
|
106
|
-
color: '#ef4444', // red-500
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
case 'completed':
|
|
110
|
-
case 'success':
|
|
111
|
-
return {
|
|
112
|
-
type: 'flash',
|
|
113
|
-
duration: 800,
|
|
114
|
-
intensity: 0.7,
|
|
115
|
-
color: '#22c55e', // green-500
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
default:
|
|
119
|
-
// Generic action animation
|
|
120
|
-
return {
|
|
121
|
-
type: 'pulse',
|
|
122
|
-
duration: 1000,
|
|
123
|
-
intensity: 0.6,
|
|
124
|
-
color: '#3b82f6', // blue-500
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Get animation config for edge based on action (Milestone 2)
|
|
131
|
-
*/
|
|
132
|
-
export function actionToEdgeAnimation(_action: string, edgeConfig?: EdgeAnimation): EdgeAnimation {
|
|
133
|
-
// Use edge config if provided, otherwise default
|
|
134
|
-
return (
|
|
135
|
-
edgeConfig || {
|
|
136
|
-
type: 'flow',
|
|
137
|
-
duration: 2000,
|
|
138
|
-
direction: 'forward',
|
|
139
|
-
}
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Check if log level should trigger animation
|
|
145
|
-
*/
|
|
146
|
-
export function shouldAnimate(level: LogLevel, minLevel: LogLevel = 'info'): boolean {
|
|
147
|
-
const levels: LogLevel[] = ['debug', 'info', 'warn', 'error'];
|
|
148
|
-
const minIndex = levels.indexOf(minLevel);
|
|
149
|
-
const currentIndex = levels.indexOf(level);
|
|
150
|
-
|
|
151
|
-
return currentIndex >= minIndex;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Calculate animation intensity based on frequency
|
|
156
|
-
* Used to adjust animation intensity when multiple logs occur rapidly
|
|
157
|
-
*/
|
|
158
|
-
export function calculateIntensity(
|
|
159
|
-
baseIntensity: number,
|
|
160
|
-
eventCount: number,
|
|
161
|
-
_timeWindow: number = 1000
|
|
162
|
-
): number {
|
|
163
|
-
// Reduce intensity if too many events in time window
|
|
164
|
-
const scaleFactor = Math.min(1.0, 5.0 / eventCount);
|
|
165
|
-
return Math.max(0.2, baseIntensity * scaleFactor);
|
|
166
|
-
}
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
import type { ExtendedCanvas } from '@principal-ai/subsystems-core';
|
|
2
|
-
|
|
3
|
-
export interface CanvasBounds {
|
|
4
|
-
minX: number;
|
|
5
|
-
minY: number;
|
|
6
|
-
maxX: number;
|
|
7
|
-
maxY: number;
|
|
8
|
-
width: number;
|
|
9
|
-
height: number;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const DEFAULT_NODE_WIDTH = 200;
|
|
13
|
-
const DEFAULT_NODE_HEIGHT = 100;
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Calculate the bounding box of all nodes in a canvas.
|
|
17
|
-
* Returns the min/max coordinates and total dimensions.
|
|
18
|
-
*/
|
|
19
|
-
export function getCanvasBounds(canvas: ExtendedCanvas): CanvasBounds {
|
|
20
|
-
if (!canvas.nodes || canvas.nodes.length === 0) {
|
|
21
|
-
return {
|
|
22
|
-
minX: 0,
|
|
23
|
-
minY: 0,
|
|
24
|
-
maxX: DEFAULT_NODE_WIDTH,
|
|
25
|
-
maxY: DEFAULT_NODE_HEIGHT,
|
|
26
|
-
width: DEFAULT_NODE_WIDTH,
|
|
27
|
-
height: DEFAULT_NODE_HEIGHT,
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
let minX = Infinity;
|
|
32
|
-
let minY = Infinity;
|
|
33
|
-
let maxX = -Infinity;
|
|
34
|
-
let maxY = -Infinity;
|
|
35
|
-
|
|
36
|
-
for (const node of canvas.nodes) {
|
|
37
|
-
const x = node.x ?? 0;
|
|
38
|
-
const y = node.y ?? 0;
|
|
39
|
-
const width = node.width ?? DEFAULT_NODE_WIDTH;
|
|
40
|
-
const height = node.height ?? DEFAULT_NODE_HEIGHT;
|
|
41
|
-
|
|
42
|
-
minX = Math.min(minX, x);
|
|
43
|
-
minY = Math.min(minY, y);
|
|
44
|
-
maxX = Math.max(maxX, x + width);
|
|
45
|
-
maxY = Math.max(maxY, y + height);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return {
|
|
49
|
-
minX,
|
|
50
|
-
minY,
|
|
51
|
-
maxX,
|
|
52
|
-
maxY,
|
|
53
|
-
width: maxX - minX,
|
|
54
|
-
height: maxY - minY,
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export interface Viewport {
|
|
59
|
-
x: number;
|
|
60
|
-
y: number;
|
|
61
|
-
zoom: number;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Calculate the initial viewport to fit all nodes within a container.
|
|
66
|
-
* This allows setting defaultViewport on ReactFlow to avoid the
|
|
67
|
-
* "zoom in then animate out" effect.
|
|
68
|
-
*
|
|
69
|
-
* @param bounds - The bounding box of all nodes
|
|
70
|
-
* @param containerWidth - Width of the container in pixels
|
|
71
|
-
* @param containerHeight - Height of the container in pixels
|
|
72
|
-
* @param options - Additional options for padding and zoom limits
|
|
73
|
-
* @returns The viewport { x, y, zoom } to pass to ReactFlow's defaultViewport
|
|
74
|
-
*/
|
|
75
|
-
export function calculateInitialViewport(
|
|
76
|
-
bounds: CanvasBounds,
|
|
77
|
-
containerWidth: number,
|
|
78
|
-
containerHeight: number,
|
|
79
|
-
options: {
|
|
80
|
-
padding?: number;
|
|
81
|
-
minZoom?: number;
|
|
82
|
-
maxZoom?: number;
|
|
83
|
-
} = {}
|
|
84
|
-
): Viewport {
|
|
85
|
-
const { padding = 0.2, minZoom = 0.1, maxZoom = 1.5 } = options;
|
|
86
|
-
|
|
87
|
-
// Calculate the zoom level needed to fit the bounds within the container
|
|
88
|
-
// Account for padding by reducing the effective container size
|
|
89
|
-
const effectiveWidth = containerWidth * (1 - padding);
|
|
90
|
-
const effectiveHeight = containerHeight * (1 - padding);
|
|
91
|
-
|
|
92
|
-
const zoomX = effectiveWidth / bounds.width;
|
|
93
|
-
const zoomY = effectiveHeight / bounds.height;
|
|
94
|
-
|
|
95
|
-
// Use the smaller zoom to ensure both dimensions fit
|
|
96
|
-
let zoom = Math.min(zoomX, zoomY);
|
|
97
|
-
|
|
98
|
-
// Clamp to min/max zoom
|
|
99
|
-
zoom = Math.max(minZoom, Math.min(maxZoom, zoom));
|
|
100
|
-
|
|
101
|
-
// Calculate the center of the bounds
|
|
102
|
-
const boundsCenterX = bounds.minX + bounds.width / 2;
|
|
103
|
-
const boundsCenterY = bounds.minY + bounds.height / 2;
|
|
104
|
-
|
|
105
|
-
// Calculate viewport position to center the bounds
|
|
106
|
-
// ReactFlow viewport x,y represent the offset of the canvas origin from the container's top-left
|
|
107
|
-
const x = containerWidth / 2 - boundsCenterX * zoom;
|
|
108
|
-
const y = containerHeight / 2 - boundsCenterY * zoom;
|
|
109
|
-
|
|
110
|
-
return { x, y, zoom };
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Calculate a recommended display size for a canvas cell.
|
|
115
|
-
* Adds padding and enforces minimum dimensions.
|
|
116
|
-
*/
|
|
117
|
-
export function getCanvasDisplaySize(
|
|
118
|
-
canvas: ExtendedCanvas,
|
|
119
|
-
options: {
|
|
120
|
-
padding?: number;
|
|
121
|
-
minWidth?: number;
|
|
122
|
-
minHeight?: number;
|
|
123
|
-
maxWidth?: number;
|
|
124
|
-
maxHeight?: number;
|
|
125
|
-
} = {}
|
|
126
|
-
): { width: number; height: number } {
|
|
127
|
-
const {
|
|
128
|
-
padding = 100,
|
|
129
|
-
minWidth = 300,
|
|
130
|
-
minHeight = 200,
|
|
131
|
-
maxWidth = 1200,
|
|
132
|
-
maxHeight = 800,
|
|
133
|
-
} = options;
|
|
134
|
-
|
|
135
|
-
const bounds = getCanvasBounds(canvas);
|
|
136
|
-
|
|
137
|
-
// Add padding to content dimensions
|
|
138
|
-
const contentWidth = bounds.width + padding * 2;
|
|
139
|
-
const contentHeight = bounds.height + padding * 2;
|
|
140
|
-
|
|
141
|
-
// Clamp to min/max
|
|
142
|
-
const width = Math.min(maxWidth, Math.max(minWidth, contentWidth));
|
|
143
|
-
const height = Math.min(maxHeight, Math.max(minHeight, contentHeight));
|
|
144
|
-
|
|
145
|
-
return { width, height };
|
|
146
|
-
}
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
import { MarkerType, type Node, type Edge } from '@xyflow/react';
|
|
2
|
-
import type {
|
|
3
|
-
NodeState,
|
|
4
|
-
EdgeState,
|
|
5
|
-
GraphConfiguration,
|
|
6
|
-
Violation,
|
|
7
|
-
} from '@principal-ai/subsystems-core';
|
|
8
|
-
import type { CustomNodeData } from '../nodes/CustomNode';
|
|
9
|
-
import type { CustomEdgeData } from '../edges/CustomEdge';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Convert our NodeState to xyflow Node format
|
|
13
|
-
*/
|
|
14
|
-
export function convertToXYFlowNodes(
|
|
15
|
-
nodes: NodeState[],
|
|
16
|
-
configuration: GraphConfiguration,
|
|
17
|
-
violations: Violation[] = []
|
|
18
|
-
): Node<CustomNodeData>[] {
|
|
19
|
-
return nodes.map((node) => {
|
|
20
|
-
let typeDefinition = configuration.nodeTypes[node.type];
|
|
21
|
-
|
|
22
|
-
// Warn if node type is not defined in configuration
|
|
23
|
-
if (!typeDefinition) {
|
|
24
|
-
console.warn(`Node type "${node.type}" not found in configuration for node "${node.id}"`);
|
|
25
|
-
// Provide minimal typeDefinition with global states fallback to prevent undefined errors
|
|
26
|
-
typeDefinition = {
|
|
27
|
-
description: '',
|
|
28
|
-
shape: 'rectangle',
|
|
29
|
-
dataSchema: {},
|
|
30
|
-
states: configuration.states,
|
|
31
|
-
};
|
|
32
|
-
} else if (!typeDefinition.states && configuration.states) {
|
|
33
|
-
// If typeDefinition exists but has no states, use global states as fallback
|
|
34
|
-
typeDefinition = {
|
|
35
|
-
...typeDefinition,
|
|
36
|
-
states: configuration.states,
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const hasViolations = violations.some((v) => v.context?.nodeId === node.id);
|
|
41
|
-
|
|
42
|
-
// Groups should render behind other nodes and edges
|
|
43
|
-
const isGroup = node.data?.canvasType === 'group';
|
|
44
|
-
|
|
45
|
-
return {
|
|
46
|
-
id: node.id,
|
|
47
|
-
type: 'custom',
|
|
48
|
-
position: node.position || { x: 0, y: 0 },
|
|
49
|
-
// Groups have lower zIndex to render behind other nodes
|
|
50
|
-
// Use 1 for groups, 100 for regular nodes (positive values to avoid event handling issues)
|
|
51
|
-
zIndex: isGroup ? 1 : 100,
|
|
52
|
-
// Apply persisted dimensions if available
|
|
53
|
-
...(node.width !== undefined ? { width: node.width } : {}),
|
|
54
|
-
...(node.height !== undefined ? { height: node.height } : {}),
|
|
55
|
-
data: {
|
|
56
|
-
name: node.name || node.id,
|
|
57
|
-
typeDefinition,
|
|
58
|
-
state: node.state,
|
|
59
|
-
hasViolations,
|
|
60
|
-
data: node.data,
|
|
61
|
-
},
|
|
62
|
-
};
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/** Extended edge state with optional handle information for ReactFlow */
|
|
67
|
-
export interface EdgeStateWithHandles extends EdgeState {
|
|
68
|
-
sourceHandle?: string;
|
|
69
|
-
targetHandle?: string;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Map canvas side to source handle ID
|
|
74
|
-
* Source handles use '-out' suffix
|
|
75
|
-
*/
|
|
76
|
-
function sideToSourceHandle(side?: string): string | undefined {
|
|
77
|
-
if (!side) return undefined;
|
|
78
|
-
return `${side}-out`;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Map canvas side to target handle ID
|
|
83
|
-
* Target handles use the side name directly
|
|
84
|
-
*/
|
|
85
|
-
function sideToTargetHandle(side?: string): string | undefined {
|
|
86
|
-
return side;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Convert our EdgeState to xyflow Edge format
|
|
91
|
-
*/
|
|
92
|
-
export function convertToXYFlowEdges(
|
|
93
|
-
edges: (EdgeState | EdgeStateWithHandles)[],
|
|
94
|
-
configuration: GraphConfiguration,
|
|
95
|
-
violations: Violation[] = []
|
|
96
|
-
): Edge<CustomEdgeData>[] {
|
|
97
|
-
return edges.map((edge) => {
|
|
98
|
-
const typeDefinition = configuration.edgeTypes[edge.type];
|
|
99
|
-
|
|
100
|
-
// Warn if edge type is not defined in configuration
|
|
101
|
-
if (!typeDefinition) {
|
|
102
|
-
console.warn(`Edge type "${edge.type}" not found in configuration for edge "${edge.id}"`);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const hasViolations = violations.some((v) => v.context?.edgeId === edge.id);
|
|
106
|
-
const edgeWithHandles = edge as EdgeStateWithHandles;
|
|
107
|
-
|
|
108
|
-
// Get handle IDs from edge data (fromSide/toSide) or explicit handles
|
|
109
|
-
// If no side specified, default to right->left for left-to-right flow
|
|
110
|
-
const fromSide = edge.data?.fromSide as string | undefined;
|
|
111
|
-
const toSide = edge.data?.toSide as string | undefined;
|
|
112
|
-
const sourceHandle = edgeWithHandles.sourceHandle || sideToSourceHandle(fromSide || 'right');
|
|
113
|
-
const targetHandle = edgeWithHandles.targetHandle || sideToTargetHandle(toSide || 'left');
|
|
114
|
-
|
|
115
|
-
// Add arrow marker if edge type is directed
|
|
116
|
-
// Color priority: edge data color > type definition color > default
|
|
117
|
-
const edgeColor = edge.data?.color as string | undefined;
|
|
118
|
-
const markerEnd =
|
|
119
|
-
typeDefinition?.directed !== false
|
|
120
|
-
? {
|
|
121
|
-
type: MarkerType.ArrowClosed,
|
|
122
|
-
color: edgeColor || typeDefinition?.color || '#888',
|
|
123
|
-
width: 20,
|
|
124
|
-
height: 20,
|
|
125
|
-
}
|
|
126
|
-
: undefined;
|
|
127
|
-
|
|
128
|
-
return {
|
|
129
|
-
id: edge.id,
|
|
130
|
-
source: edge.from,
|
|
131
|
-
target: edge.to,
|
|
132
|
-
sourceHandle,
|
|
133
|
-
targetHandle,
|
|
134
|
-
type: 'custom',
|
|
135
|
-
// Edges should render above groups (zIndex: 1) but below regular nodes (zIndex: 100)
|
|
136
|
-
zIndex: 50,
|
|
137
|
-
animated: typeDefinition?.style === 'animated',
|
|
138
|
-
markerEnd,
|
|
139
|
-
data: {
|
|
140
|
-
typeDefinition,
|
|
141
|
-
hasViolations,
|
|
142
|
-
data: edge.data,
|
|
143
|
-
edgeType: edge.type,
|
|
144
|
-
},
|
|
145
|
-
};
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import * as LucideIcons from 'lucide-react';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Icon Resolver
|
|
6
|
-
*
|
|
7
|
-
* Resolves icon strings to React components. Supports:
|
|
8
|
-
* 1. Lucide icon names (e.g., "Database", "Settings", "Package")
|
|
9
|
-
* 2. Emoji/Unicode strings (e.g., "⚙️", "💾", "📦")
|
|
10
|
-
*
|
|
11
|
-
* This keeps configurations JSON-serializable while supporting icon libraries.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
export interface IconProps {
|
|
15
|
-
icon?: string;
|
|
16
|
-
size?: number;
|
|
17
|
-
className?: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Resolves an icon string to a React element
|
|
22
|
-
*
|
|
23
|
-
* @param icon - Icon name (Lucide) or emoji/text string
|
|
24
|
-
* @param size - Icon size in pixels (default: 20)
|
|
25
|
-
* @param className - Optional CSS class
|
|
26
|
-
* @returns React element or null
|
|
27
|
-
*/
|
|
28
|
-
export function resolveIcon(icon?: string, size: number = 20, className?: string): React.ReactNode {
|
|
29
|
-
if (!icon) return null;
|
|
30
|
-
|
|
31
|
-
// Try to resolve as Lucide icon first
|
|
32
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
33
|
-
const LucideIcon = (LucideIcons as any)[icon];
|
|
34
|
-
|
|
35
|
-
// Check if it's a valid React component (function or object with $$typeof)
|
|
36
|
-
if (LucideIcon && (typeof LucideIcon === 'function' || typeof LucideIcon === 'object')) {
|
|
37
|
-
return <LucideIcon size={size} className={className} />;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Fall back to rendering as text (emoji or unicode)
|
|
41
|
-
return (
|
|
42
|
-
<span className={className} style={{ fontSize: `${size}px` }}>
|
|
43
|
-
{icon}
|
|
44
|
-
</span>
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Icon component that resolves icon strings
|
|
50
|
-
*/
|
|
51
|
-
export const Icon: React.FC<IconProps> = ({ icon, size = 20, className }) => {
|
|
52
|
-
return <>{resolveIcon(icon, size, className)}</>;
|
|
53
|
-
};
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import type { NodeState, EdgeState, CanvasSide } from '@principal-ai/subsystems-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
|
-
}
|