@principal-ai/subsystems-react 0.17.0 → 0.18.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 (98) hide show
  1. package/dist/index.d.ts +7 -44
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +6 -32
  4. package/dist/index.js.map +1 -1
  5. package/package.json +2 -2
  6. package/src/index.ts +9 -199
  7. package/src/components/ConfigurationSelector.tsx +0 -133
  8. package/src/components/GraphRenderer.tsx +0 -3195
  9. package/src/components/MultiCanvasRenderer.tsx +0 -295
  10. package/src/components/NodeTooltip.tsx +0 -279
  11. package/src/components/PendingChanges.test.tsx +0 -375
  12. package/src/components/SelectionSidebar.tsx +0 -352
  13. package/src/components/SequenceDiagramRenderer.tsx +0 -1156
  14. package/src/components/WorkflowSequenceDiagram.tsx +0 -270
  15. package/src/components/dashboard/DashboardRenderer.tsx +0 -317
  16. package/src/components/dashboard/MetricPanel.tsx +0 -254
  17. package/src/components/dashboard/MockDataProvider.ts +0 -330
  18. package/src/components/dashboard/components/BarChart.tsx +0 -299
  19. package/src/components/dashboard/components/LineChart.tsx +0 -279
  20. package/src/components/dashboard/components/MetricCard.tsx +0 -270
  21. package/src/components/dashboard/components/SourceLink.tsx +0 -63
  22. package/src/components/dashboard/components/TimeRangeSelector.tsx +0 -280
  23. package/src/components/dashboard/components/index.ts +0 -5
  24. package/src/components/dashboard/index.ts +0 -47
  25. package/src/components/dashboard/types.ts +0 -126
  26. package/src/components/state-view/PipelineView.tsx +0 -347
  27. package/src/components/state-view/index.ts +0 -14
  28. package/src/components/state-view/types.ts +0 -261
  29. package/src/components/state-view/useStateView.ts +0 -205
  30. package/src/contexts/GraphEditContext.tsx +0 -27
  31. package/src/contexts/TooltipPortalContext.ts +0 -8
  32. package/src/edges/CustomEdge.tsx +0 -402
  33. package/src/edges/GenericEdge.tsx +0 -36
  34. package/src/hooks/usePathBasedEvents.ts +0 -192
  35. package/src/hooks/useSequenceLayout.ts +0 -543
  36. package/src/hooks/useUndoRedo.ts +0 -144
  37. package/src/nodes/CustomNode.tsx +0 -1231
  38. package/src/nodes/GenericNode.tsx +0 -55
  39. package/src/nodes/otel/OtelBoundaryNode.tsx +0 -224
  40. package/src/nodes/otel/OtelEventNode.tsx +0 -225
  41. package/src/nodes/otel/OtelResourceNode.tsx +0 -251
  42. package/src/nodes/otel/OtelScopeNode.tsx +0 -221
  43. package/src/nodes/otel/OtelSpanConventionNode.tsx +0 -295
  44. package/src/nodes/otel/index.ts +0 -23
  45. package/src/nodes/otel/shared/NodeBadges.tsx +0 -295
  46. package/src/nodes/otel/shared/NodeContent.tsx +0 -204
  47. package/src/nodes/otel/shared/index.ts +0 -8
  48. package/src/nodes/otel/shared/types.ts +0 -138
  49. package/src/nodes/otel/shared/useNodeBehavior.ts +0 -114
  50. package/src/stories/Audit/BoundaryNodes.stories.tsx +0 -385
  51. package/src/stories/Audit/CanvasEdgeTypes.stories.tsx +0 -1448
  52. package/src/stories/Audit/CanvasNodeTypes.stories.tsx +0 -1049
  53. package/src/stories/Audit/ColorPriority.stories.tsx +0 -895
  54. package/src/stories/Audit/DiamondBadges.stories.tsx +0 -629
  55. package/src/stories/Audit/NodeDimensionsTesting.stories.tsx +0 -724
  56. package/src/stories/Audit/NodeFieldsAudit.stories.tsx +0 -996
  57. package/src/stories/Audit/NodeFontSizeTesting.stories.tsx +0 -715
  58. package/src/stories/Audit/NodeShapes.stories.tsx +0 -1208
  59. package/src/stories/Audit/WideNodesTesting.stories.tsx +0 -1045
  60. package/src/stories/Components/DashboardRenderer.stories.tsx +0 -263
  61. package/src/stories/Components/GraphRenderer.stories.tsx +0 -3236
  62. package/src/stories/Components/MultiCanvasRenderer.stories.tsx +0 -370
  63. package/src/stories/Components/SequenceDiagram.stories.tsx +0 -255
  64. package/src/stories/Components/StateView.stories.tsx +0 -417
  65. package/src/stories/Components/TraceViewer.stories.tsx +0 -442
  66. package/src/stories/Components/WorkflowSequenceDiagram.stories.tsx +0 -802
  67. package/src/stories/Features/AnimationWorkshop.stories.tsx +0 -727
  68. package/src/stories/Features/ChangeTypeVisual.stories.tsx +0 -67
  69. package/src/stories/Features/EventDrivenAnimations.stories.tsx +0 -513
  70. package/src/stories/Features/MultiConfig.stories.tsx +0 -539
  71. package/src/stories/Layout/ElkEdgeRouting.stories.tsx +0 -539
  72. package/src/stories/Layout/GraphOrientation.stories.tsx +0 -500
  73. package/src/stories/Layout/MultiDirectionalConnections.stories.tsx +0 -352
  74. package/src/stories/Nodes/NodeDefinitionComparison.stories.tsx +0 -560
  75. package/src/stories/Nodes/NodeNameFallbacks.stories.tsx +0 -491
  76. package/src/stories/Nodes/NumericColors.stories.tsx +0 -440
  77. package/src/stories/OTEL/EventNameDisplay.stories.tsx +0 -694
  78. package/src/stories/OTEL/EventNameFallback.stories.tsx +0 -578
  79. package/src/stories/OTEL/OtelComponents.stories.tsx +0 -422
  80. package/src/stories/OTEL/OtelNodeTypesPrototype.stories.tsx +0 -806
  81. package/src/stories/OTEL/RawEventCards.stories.tsx +0 -57
  82. package/src/stories/OTEL/SpanBadges.stories.tsx +0 -840
  83. package/src/stories/OTEL/ValidationEventsCanvas.stories.tsx +0 -209
  84. package/src/stories/RealWorld/BacklogMdCanvases.stories.tsx +0 -721
  85. package/src/stories/RealWorld/FileCitySequence.stories.tsx +0 -516
  86. package/src/stories/RealWorld/RealCanvasFiles.stories.tsx +0 -336
  87. package/src/stories/__traces__/test-run.canvas.json +0 -241
  88. package/src/stories/data/activity-feed-analytics.dashboard.json +0 -300
  89. package/src/stories/data/file-city-images-transformed.otel.canvas.json +0 -295
  90. package/src/stories/data/file-city-workflows.json +0 -269
  91. package/src/stories/data/graph-converter-test-execution.json +0 -443
  92. package/src/stories/data/graph-converter-test.workflow.json +0 -105
  93. package/src/stories/data/graph-converter-validated-execution.json +0 -58
  94. package/src/utils/animationMapping.ts +0 -166
  95. package/src/utils/canvasBounds.ts +0 -146
  96. package/src/utils/graphConverter.ts +0 -148
  97. package/src/utils/iconResolver.tsx +0 -53
  98. 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
- }