@principal-ai/principal-view-react 0.14.3 → 0.14.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/nodes/CustomNode.d.ts +4 -0
- package/dist/nodes/CustomNode.d.ts.map +1 -1
- package/dist/nodes/CustomNode.js +30 -1
- package/dist/nodes/CustomNode.js.map +1 -1
- package/dist/nodes/otel/OtelBoundaryNode.d.ts +55 -0
- package/dist/nodes/otel/OtelBoundaryNode.d.ts.map +1 -0
- package/dist/nodes/otel/OtelBoundaryNode.js +90 -0
- package/dist/nodes/otel/OtelBoundaryNode.js.map +1 -0
- package/dist/nodes/otel/OtelEventNode.d.ts +59 -0
- package/dist/nodes/otel/OtelEventNode.d.ts.map +1 -0
- package/dist/nodes/otel/OtelEventNode.js +90 -0
- package/dist/nodes/otel/OtelEventNode.js.map +1 -0
- package/dist/nodes/otel/OtelResourceNode.d.ts +53 -0
- package/dist/nodes/otel/OtelResourceNode.d.ts.map +1 -0
- package/dist/nodes/otel/OtelResourceNode.js +114 -0
- package/dist/nodes/otel/OtelResourceNode.js.map +1 -0
- package/dist/nodes/otel/OtelScopeNode.d.ts +53 -0
- package/dist/nodes/otel/OtelScopeNode.d.ts.map +1 -0
- package/dist/nodes/otel/OtelScopeNode.js +90 -0
- package/dist/nodes/otel/OtelScopeNode.js.map +1 -0
- package/dist/nodes/otel/OtelSpanConventionNode.d.ts +61 -0
- package/dist/nodes/otel/OtelSpanConventionNode.d.ts.map +1 -0
- package/dist/nodes/otel/OtelSpanConventionNode.js +143 -0
- package/dist/nodes/otel/OtelSpanConventionNode.js.map +1 -0
- package/dist/nodes/otel/index.d.ts +17 -0
- package/dist/nodes/otel/index.d.ts.map +1 -0
- package/dist/nodes/otel/index.js +13 -0
- package/dist/nodes/otel/index.js.map +1 -0
- package/dist/nodes/otel/shared/NodeBadges.d.ts +10 -0
- package/dist/nodes/otel/shared/NodeBadges.d.ts.map +1 -0
- package/dist/nodes/otel/shared/NodeBadges.js +178 -0
- package/dist/nodes/otel/shared/NodeBadges.js.map +1 -0
- package/dist/nodes/otel/shared/NodeContent.d.ts +10 -0
- package/dist/nodes/otel/shared/NodeContent.d.ts.map +1 -0
- package/dist/nodes/otel/shared/NodeContent.js +96 -0
- package/dist/nodes/otel/shared/NodeContent.js.map +1 -0
- package/dist/nodes/otel/shared/index.d.ts +8 -0
- package/dist/nodes/otel/shared/index.d.ts.map +1 -0
- package/dist/nodes/otel/shared/index.js +8 -0
- package/dist/nodes/otel/shared/index.js.map +1 -0
- package/dist/nodes/otel/shared/types.d.ts +129 -0
- package/dist/nodes/otel/shared/types.d.ts.map +1 -0
- package/dist/nodes/otel/shared/types.js +5 -0
- package/dist/nodes/otel/shared/types.js.map +1 -0
- package/dist/nodes/otel/shared/useNodeBehavior.d.ts +42 -0
- package/dist/nodes/otel/shared/useNodeBehavior.d.ts.map +1 -0
- package/dist/nodes/otel/shared/useNodeBehavior.js +61 -0
- package/dist/nodes/otel/shared/useNodeBehavior.js.map +1 -0
- package/package.json +1 -1
- package/src/index.ts +17 -0
- package/src/nodes/CustomNode.tsx +40 -1
- package/src/nodes/otel/OtelBoundaryNode.tsx +234 -0
- package/src/nodes/otel/OtelEventNode.tsx +233 -0
- package/src/nodes/otel/OtelResourceNode.tsx +261 -0
- package/src/nodes/otel/OtelScopeNode.tsx +231 -0
- package/src/nodes/otel/OtelSpanConventionNode.tsx +309 -0
- package/src/nodes/otel/index.ts +23 -0
- package/src/nodes/otel/shared/NodeBadges.tsx +295 -0
- package/src/nodes/otel/shared/NodeContent.tsx +204 -0
- package/src/nodes/otel/shared/index.ts +8 -0
- package/src/nodes/otel/shared/types.ts +138 -0
- package/src/nodes/otel/shared/useNodeBehavior.ts +114 -0
package/src/nodes/CustomNode.tsx
CHANGED
|
@@ -8,6 +8,15 @@ import { NodeTooltip } from '../components/NodeTooltip';
|
|
|
8
8
|
import type { OtelInfo } from '../components/NodeTooltip';
|
|
9
9
|
import { useGraphEdit } from '../contexts/GraphEditContext';
|
|
10
10
|
|
|
11
|
+
// OTEL node components
|
|
12
|
+
import {
|
|
13
|
+
OtelSpanConventionNode,
|
|
14
|
+
OtelEventNode,
|
|
15
|
+
OtelScopeNode,
|
|
16
|
+
OtelResourceNode,
|
|
17
|
+
OtelBoundaryNode,
|
|
18
|
+
} from './otel';
|
|
19
|
+
|
|
11
20
|
/**
|
|
12
21
|
* Converts a hex color to a lighter/tinted version (opaque, not transparent)
|
|
13
22
|
* @param hexColor - Hex color string (e.g., "#FF5733" or "#888")
|
|
@@ -61,8 +70,38 @@ export interface CustomNodeData extends Record<string, unknown> {
|
|
|
61
70
|
|
|
62
71
|
/**
|
|
63
72
|
* Custom node component for xyflow that renders based on NodeTypeDefinition
|
|
73
|
+
*
|
|
74
|
+
* This component now delegates to specialized OTEL node components when the
|
|
75
|
+
* node type matches an OTEL concept. This allows each OTEL node type to have
|
|
76
|
+
* its own specialized rendering and features (e.g., workflow chips on span nodes).
|
|
64
77
|
*/
|
|
65
|
-
export const CustomNode: React.FC<NodeProps<Node<CustomNodeData>>> = (
|
|
78
|
+
export const CustomNode: React.FC<NodeProps<Node<CustomNodeData>>> = (props) => {
|
|
79
|
+
const { data, selected, dragging } = props;
|
|
80
|
+
|
|
81
|
+
// Determine the OTEL node type from nodeData.nodeType (pv.nodeType in canvas)
|
|
82
|
+
// The typeDefinition.shape can also hint at OTEL types but nodeType is authoritative
|
|
83
|
+
const nodeType = data.data?.nodeType as string | undefined;
|
|
84
|
+
|
|
85
|
+
// Delegate to specialized OTEL node components
|
|
86
|
+
switch (nodeType) {
|
|
87
|
+
case 'otel-span-convention':
|
|
88
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
89
|
+
return <OtelSpanConventionNode {...(props as any)} />;
|
|
90
|
+
case 'otel-event':
|
|
91
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
92
|
+
return <OtelEventNode {...(props as any)} />;
|
|
93
|
+
case 'otel-scope':
|
|
94
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
95
|
+
return <OtelScopeNode {...(props as any)} />;
|
|
96
|
+
case 'otel-resource':
|
|
97
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
98
|
+
return <OtelResourceNode {...(props as any)} />;
|
|
99
|
+
case 'otel-boundary':
|
|
100
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
101
|
+
return <OtelBoundaryNode {...(props as any)} />;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Fall through to legacy rendering for non-OTEL nodes
|
|
66
105
|
const { theme } = useTheme();
|
|
67
106
|
const { onNodeResizeEnd, onToggleNodeHidden, onHideUnconnectedNodes } = useGraphEdit();
|
|
68
107
|
const nodeId = useNodeId();
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OTEL Boundary Node
|
|
3
|
+
*
|
|
4
|
+
* Renders boundary nodes as rounded rectangles with:
|
|
5
|
+
* - Direction identifier (inbound/outbound)
|
|
6
|
+
* - Direction badge (arrow indicating flow)
|
|
7
|
+
* - Status and references badges
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import React from 'react';
|
|
11
|
+
import { Handle, Position, NodeResizer } from '@xyflow/react';
|
|
12
|
+
import type { NodeProps, Node } from '@xyflow/react';
|
|
13
|
+
import { useTheme } from '@principal-ade/industry-theme';
|
|
14
|
+
import { NodeTooltip } from '../../components/NodeTooltip';
|
|
15
|
+
import type { OtelInfo } from '../../components/NodeTooltip';
|
|
16
|
+
import { NodeBadges } from './shared/NodeBadges';
|
|
17
|
+
import { NodeContent } from './shared/NodeContent';
|
|
18
|
+
import { useNodeBehavior } from './shared/useNodeBehavior';
|
|
19
|
+
import type { BoundaryData } from './shared/types';
|
|
20
|
+
|
|
21
|
+
function hexToLightColor(hexColor: string, lightness = 0.88): string {
|
|
22
|
+
const hex = hexColor.replace('#', '');
|
|
23
|
+
const r = parseInt(hex.substring(0, 2), 16);
|
|
24
|
+
const g = parseInt(hex.substring(2, 4), 16);
|
|
25
|
+
const b = parseInt(hex.substring(4, 6), 16);
|
|
26
|
+
const newR = Math.round(r + (255 - r) * lightness);
|
|
27
|
+
const newG = Math.round(g + (255 - g) * lightness);
|
|
28
|
+
const newB = Math.round(b + (255 - b) * lightness);
|
|
29
|
+
const toHex = (n: number) => n.toString(16).padStart(2, '0');
|
|
30
|
+
return `#${toHex(newR)}${toHex(newG)}${toHex(newB)}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface OtelBoundaryNodeData extends Record<string, unknown> {
|
|
34
|
+
name: string;
|
|
35
|
+
typeDefinition: {
|
|
36
|
+
color?: string;
|
|
37
|
+
icon?: string;
|
|
38
|
+
states?: Record<string, { color?: string; label?: string; icon?: string }>;
|
|
39
|
+
};
|
|
40
|
+
state?: string;
|
|
41
|
+
hasViolations?: boolean;
|
|
42
|
+
data: {
|
|
43
|
+
color?: string;
|
|
44
|
+
scopeColor?: string;
|
|
45
|
+
spanColor?: string;
|
|
46
|
+
stroke?: string;
|
|
47
|
+
icon?: string;
|
|
48
|
+
status?: 'draft' | 'approved' | 'implemented';
|
|
49
|
+
description?: string;
|
|
50
|
+
sources?: string[];
|
|
51
|
+
references?: string[];
|
|
52
|
+
states?: Record<string, { color?: string; label?: string; icon?: string }>;
|
|
53
|
+
boundary?: BoundaryData;
|
|
54
|
+
otel?: { files?: string[] };
|
|
55
|
+
};
|
|
56
|
+
editable?: boolean;
|
|
57
|
+
tooltipsEnabled?: boolean;
|
|
58
|
+
shiftKeyPressed?: boolean;
|
|
59
|
+
isHighlighted?: boolean;
|
|
60
|
+
isActive?: boolean;
|
|
61
|
+
isHidden?: boolean;
|
|
62
|
+
animationType?: 'pulse' | 'flash' | 'shake' | 'entry' | null;
|
|
63
|
+
animationDuration?: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const OtelBoundaryNode: React.FC<NodeProps<Node<OtelBoundaryNodeData>>> = ({
|
|
67
|
+
data,
|
|
68
|
+
selected,
|
|
69
|
+
dragging,
|
|
70
|
+
}) => {
|
|
71
|
+
const { theme } = useTheme();
|
|
72
|
+
const nodeProps = data;
|
|
73
|
+
const {
|
|
74
|
+
typeDefinition,
|
|
75
|
+
state,
|
|
76
|
+
hasViolations,
|
|
77
|
+
data: nodeData,
|
|
78
|
+
editable = false,
|
|
79
|
+
tooltipsEnabled = true,
|
|
80
|
+
shiftKeyPressed = false,
|
|
81
|
+
isHighlighted = false,
|
|
82
|
+
isActive = true,
|
|
83
|
+
isHidden = false,
|
|
84
|
+
animationType,
|
|
85
|
+
animationDuration = 1000,
|
|
86
|
+
} = nodeProps;
|
|
87
|
+
|
|
88
|
+
const {
|
|
89
|
+
nodeRef,
|
|
90
|
+
showTooltip,
|
|
91
|
+
handleMouseDown,
|
|
92
|
+
handleMouseEnter,
|
|
93
|
+
handleMouseLeave,
|
|
94
|
+
handleResizeEnd,
|
|
95
|
+
} = useNodeBehavior({ editable, tooltipsEnabled, shiftKeyPressed, selected, dragging });
|
|
96
|
+
|
|
97
|
+
const nodeOpacity = isHidden ? 0.4 : isActive ? 1 : 0.1;
|
|
98
|
+
|
|
99
|
+
// Color resolution
|
|
100
|
+
const spanColor = nodeData.spanColor as string | undefined;
|
|
101
|
+
const nodeDataColor = nodeData.color as string | undefined;
|
|
102
|
+
const baseFillColor = spanColor || nodeDataColor || typeDefinition.color || '#06b6d4';
|
|
103
|
+
const fillColor = baseFillColor;
|
|
104
|
+
const scopeColor = nodeData.scopeColor as string | undefined;
|
|
105
|
+
const nodeDataStroke = nodeData.stroke as string | undefined;
|
|
106
|
+
const strokeColor = nodeDataStroke || scopeColor || fillColor;
|
|
107
|
+
|
|
108
|
+
// Display info
|
|
109
|
+
const displayName = nodeProps.name;
|
|
110
|
+
const boundaryData = nodeData.boundary;
|
|
111
|
+
const identifier = boundaryData?.direction;
|
|
112
|
+
|
|
113
|
+
// Badge data
|
|
114
|
+
const status = nodeData.status;
|
|
115
|
+
const sourceFiles = nodeData.otel?.files || nodeData.sources;
|
|
116
|
+
const references = nodeData.references;
|
|
117
|
+
const description = nodeData.description;
|
|
118
|
+
|
|
119
|
+
// Icon
|
|
120
|
+
const icon =
|
|
121
|
+
(nodeData.icon as string) ||
|
|
122
|
+
(state && nodeData.states?.[state]?.icon) ||
|
|
123
|
+
typeDefinition.icon;
|
|
124
|
+
|
|
125
|
+
const stateDefinitions = nodeData.states || typeDefinition.states;
|
|
126
|
+
|
|
127
|
+
const getAnimationClass = () => {
|
|
128
|
+
switch (animationType) {
|
|
129
|
+
case 'pulse': return 'node-pulse';
|
|
130
|
+
case 'flash': return 'node-flash';
|
|
131
|
+
case 'shake': return 'node-shake';
|
|
132
|
+
case 'entry': return 'node-entry';
|
|
133
|
+
default: return '';
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const borderStyle = status === 'draft' ? 'dotted' : status === 'approved' ? 'dashed' : 'solid';
|
|
138
|
+
|
|
139
|
+
const boundaryStyle: React.CSSProperties = {
|
|
140
|
+
padding: '12px 16px',
|
|
141
|
+
backgroundColor: hexToLightColor(fillColor),
|
|
142
|
+
color: '#000',
|
|
143
|
+
border: `2px ${borderStyle} ${hasViolations ? '#D0021B' : strokeColor}`,
|
|
144
|
+
fontSize: theme.fontSizes[0],
|
|
145
|
+
fontWeight: theme.fontWeights.medium,
|
|
146
|
+
fontFamily: theme.fonts.body,
|
|
147
|
+
width: '100%',
|
|
148
|
+
height: '100%',
|
|
149
|
+
minWidth: 20,
|
|
150
|
+
minHeight: 20,
|
|
151
|
+
display: 'flex',
|
|
152
|
+
flexDirection: 'column',
|
|
153
|
+
alignItems: 'center',
|
|
154
|
+
justifyContent: 'center',
|
|
155
|
+
gap: '4px',
|
|
156
|
+
boxShadow: isHighlighted
|
|
157
|
+
? '0 0 0 3px #3b82f6, 0 0 20px rgba(59, 130, 246, 0.5)'
|
|
158
|
+
: selected
|
|
159
|
+
? `0 0 0 2px ${strokeColor}`
|
|
160
|
+
: '0 2px 4px rgba(0,0,0,0.1)',
|
|
161
|
+
opacity: nodeOpacity,
|
|
162
|
+
transition: 'box-shadow 0.2s ease, opacity 0.3s ease',
|
|
163
|
+
animationDuration: animationType ? `${animationDuration}ms` : undefined,
|
|
164
|
+
boxSizing: 'border-box',
|
|
165
|
+
borderRadius: '8px', // Rounded corners for boundary nodes
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const handleStyle = editable
|
|
169
|
+
? { background: fillColor, width: 12, height: 12, border: '2px solid white', boxShadow: '0 0 0 1px ' + fillColor }
|
|
170
|
+
: { background: fillColor, width: 8, height: 8, opacity: 0, pointerEvents: 'none' as const };
|
|
171
|
+
|
|
172
|
+
return (
|
|
173
|
+
<>
|
|
174
|
+
{editable && (
|
|
175
|
+
<NodeResizer
|
|
176
|
+
color={strokeColor}
|
|
177
|
+
isVisible={selected}
|
|
178
|
+
minWidth={40}
|
|
179
|
+
minHeight={30}
|
|
180
|
+
onResizeEnd={handleResizeEnd}
|
|
181
|
+
handleStyle={{ width: 8, height: 8, borderRadius: 2, zIndex: 20 }}
|
|
182
|
+
lineStyle={{ borderWidth: 1, zIndex: 20 }}
|
|
183
|
+
/>
|
|
184
|
+
)}
|
|
185
|
+
|
|
186
|
+
<Handle type="target" position={Position.Top} id="top" style={handleStyle} />
|
|
187
|
+
<Handle type="target" position={Position.Bottom} id="bottom" style={handleStyle} />
|
|
188
|
+
<Handle type="target" position={Position.Left} id="left" style={handleStyle} />
|
|
189
|
+
<Handle type="target" position={Position.Right} id="right" style={handleStyle} />
|
|
190
|
+
|
|
191
|
+
<div
|
|
192
|
+
ref={nodeRef}
|
|
193
|
+
style={{ position: 'relative', width: '100%', height: '100%' }}
|
|
194
|
+
onMouseDown={handleMouseDown}
|
|
195
|
+
onMouseEnter={handleMouseEnter}
|
|
196
|
+
onMouseLeave={handleMouseLeave}
|
|
197
|
+
>
|
|
198
|
+
<NodeBadges
|
|
199
|
+
shape="rectangle"
|
|
200
|
+
status={status}
|
|
201
|
+
sourceFiles={sourceFiles}
|
|
202
|
+
references={references}
|
|
203
|
+
boundary={boundaryData}
|
|
204
|
+
opacity={nodeOpacity}
|
|
205
|
+
/>
|
|
206
|
+
<div style={boundaryStyle} className={getAnimationClass()}>
|
|
207
|
+
<NodeContent
|
|
208
|
+
displayName={displayName}
|
|
209
|
+
identifier={identifier}
|
|
210
|
+
icon={icon}
|
|
211
|
+
state={state}
|
|
212
|
+
stateDefinitions={stateDefinitions}
|
|
213
|
+
hasViolations={hasViolations}
|
|
214
|
+
/>
|
|
215
|
+
</div>
|
|
216
|
+
{tooltipsEnabled && (
|
|
217
|
+
<NodeTooltip
|
|
218
|
+
description={description}
|
|
219
|
+
otel={nodeData.otel as OtelInfo}
|
|
220
|
+
sources={nodeData.sources}
|
|
221
|
+
references={references}
|
|
222
|
+
visible={showTooltip}
|
|
223
|
+
nodeRef={nodeRef}
|
|
224
|
+
/>
|
|
225
|
+
)}
|
|
226
|
+
</div>
|
|
227
|
+
|
|
228
|
+
<Handle type="source" position={Position.Top} id="top-out" style={handleStyle} />
|
|
229
|
+
<Handle type="source" position={Position.Bottom} id="bottom-out" style={handleStyle} />
|
|
230
|
+
<Handle type="source" position={Position.Left} id="left-out" style={handleStyle} />
|
|
231
|
+
<Handle type="source" position={Position.Right} id="right-out" style={handleStyle} />
|
|
232
|
+
</>
|
|
233
|
+
);
|
|
234
|
+
};
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OTEL Event Node
|
|
3
|
+
*
|
|
4
|
+
* Renders event nodes as rectangles with:
|
|
5
|
+
* - Event name identifier
|
|
6
|
+
* - Event attributes schema
|
|
7
|
+
* - Status, sources, and references badges
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import React from 'react';
|
|
11
|
+
import { Handle, Position, NodeResizer } from '@xyflow/react';
|
|
12
|
+
import type { NodeProps, Node } from '@xyflow/react';
|
|
13
|
+
import { useTheme } from '@principal-ade/industry-theme';
|
|
14
|
+
import { NodeTooltip } from '../../components/NodeTooltip';
|
|
15
|
+
import type { OtelInfo } from '../../components/NodeTooltip';
|
|
16
|
+
import { NodeBadges } from './shared/NodeBadges';
|
|
17
|
+
import { NodeContent } from './shared/NodeContent';
|
|
18
|
+
import { useNodeBehavior } from './shared/useNodeBehavior';
|
|
19
|
+
|
|
20
|
+
function hexToLightColor(hexColor: string, lightness = 0.88): string {
|
|
21
|
+
const hex = hexColor.replace('#', '');
|
|
22
|
+
const r = parseInt(hex.substring(0, 2), 16);
|
|
23
|
+
const g = parseInt(hex.substring(2, 4), 16);
|
|
24
|
+
const b = parseInt(hex.substring(4, 6), 16);
|
|
25
|
+
const newR = Math.round(r + (255 - r) * lightness);
|
|
26
|
+
const newG = Math.round(g + (255 - g) * lightness);
|
|
27
|
+
const newB = Math.round(b + (255 - b) * lightness);
|
|
28
|
+
const toHex = (n: number) => n.toString(16).padStart(2, '0');
|
|
29
|
+
return `#${toHex(newR)}${toHex(newG)}${toHex(newB)}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface OtelEventNodeData extends Record<string, unknown> {
|
|
33
|
+
name: string;
|
|
34
|
+
typeDefinition: {
|
|
35
|
+
color?: string;
|
|
36
|
+
icon?: string;
|
|
37
|
+
states?: Record<string, { color?: string; label?: string; icon?: string }>;
|
|
38
|
+
};
|
|
39
|
+
state?: string;
|
|
40
|
+
hasViolations?: boolean;
|
|
41
|
+
data: {
|
|
42
|
+
color?: string;
|
|
43
|
+
scopeColor?: string;
|
|
44
|
+
spanColor?: string;
|
|
45
|
+
stroke?: string;
|
|
46
|
+
icon?: string;
|
|
47
|
+
status?: 'draft' | 'approved' | 'implemented';
|
|
48
|
+
description?: string;
|
|
49
|
+
sources?: string[];
|
|
50
|
+
references?: string[];
|
|
51
|
+
states?: Record<string, { color?: string; label?: string; icon?: string }>;
|
|
52
|
+
event?: { name?: string; description?: string; attributes?: Record<string, unknown> };
|
|
53
|
+
eventRef?: string;
|
|
54
|
+
otel?: { files?: string[] };
|
|
55
|
+
};
|
|
56
|
+
editable?: boolean;
|
|
57
|
+
tooltipsEnabled?: boolean;
|
|
58
|
+
shiftKeyPressed?: boolean;
|
|
59
|
+
isHighlighted?: boolean;
|
|
60
|
+
isActive?: boolean;
|
|
61
|
+
isHidden?: boolean;
|
|
62
|
+
animationType?: 'pulse' | 'flash' | 'shake' | 'entry' | null;
|
|
63
|
+
animationDuration?: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const OtelEventNode: React.FC<NodeProps<Node<OtelEventNodeData>>> = ({
|
|
67
|
+
data,
|
|
68
|
+
selected,
|
|
69
|
+
dragging,
|
|
70
|
+
}) => {
|
|
71
|
+
const { theme } = useTheme();
|
|
72
|
+
const nodeProps = data;
|
|
73
|
+
const {
|
|
74
|
+
typeDefinition,
|
|
75
|
+
state,
|
|
76
|
+
hasViolations,
|
|
77
|
+
data: nodeData,
|
|
78
|
+
editable = false,
|
|
79
|
+
tooltipsEnabled = true,
|
|
80
|
+
shiftKeyPressed = false,
|
|
81
|
+
isHighlighted = false,
|
|
82
|
+
isActive = true,
|
|
83
|
+
isHidden = false,
|
|
84
|
+
animationType,
|
|
85
|
+
animationDuration = 1000,
|
|
86
|
+
} = nodeProps;
|
|
87
|
+
|
|
88
|
+
const {
|
|
89
|
+
nodeRef,
|
|
90
|
+
showTooltip,
|
|
91
|
+
handleMouseDown,
|
|
92
|
+
handleMouseEnter,
|
|
93
|
+
handleMouseLeave,
|
|
94
|
+
handleResizeEnd,
|
|
95
|
+
} = useNodeBehavior({ editable, tooltipsEnabled, shiftKeyPressed, selected, dragging });
|
|
96
|
+
|
|
97
|
+
const nodeOpacity = isHidden ? 0.4 : isActive ? 1 : 0.1;
|
|
98
|
+
|
|
99
|
+
// Color resolution
|
|
100
|
+
const spanColor = nodeData.spanColor as string | undefined;
|
|
101
|
+
const nodeDataColor = nodeData.color as string | undefined;
|
|
102
|
+
const baseFillColor = spanColor || nodeDataColor || typeDefinition.color || '#3b82f6';
|
|
103
|
+
const fillColor = baseFillColor;
|
|
104
|
+
const scopeColor = nodeData.scopeColor as string | undefined;
|
|
105
|
+
const nodeDataStroke = nodeData.stroke as string | undefined;
|
|
106
|
+
const strokeColor = nodeDataStroke || scopeColor || fillColor;
|
|
107
|
+
|
|
108
|
+
// Display info
|
|
109
|
+
const displayName = nodeProps.name;
|
|
110
|
+
const eventData = nodeData.event;
|
|
111
|
+
const identifier = eventData?.name || (nodeData.eventRef as string);
|
|
112
|
+
|
|
113
|
+
// Badge data
|
|
114
|
+
const status = nodeData.status;
|
|
115
|
+
const sourceFiles = nodeData.otel?.files || nodeData.sources;
|
|
116
|
+
const references = nodeData.references;
|
|
117
|
+
const description = nodeData.description;
|
|
118
|
+
|
|
119
|
+
// Icon
|
|
120
|
+
const icon =
|
|
121
|
+
(nodeData.icon as string) ||
|
|
122
|
+
(state && nodeData.states?.[state]?.icon) ||
|
|
123
|
+
typeDefinition.icon;
|
|
124
|
+
|
|
125
|
+
const stateDefinitions = nodeData.states || typeDefinition.states;
|
|
126
|
+
|
|
127
|
+
const getAnimationClass = () => {
|
|
128
|
+
switch (animationType) {
|
|
129
|
+
case 'pulse': return 'node-pulse';
|
|
130
|
+
case 'flash': return 'node-flash';
|
|
131
|
+
case 'shake': return 'node-shake';
|
|
132
|
+
case 'entry': return 'node-entry';
|
|
133
|
+
default: return '';
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const borderStyle = status === 'draft' ? 'dotted' : status === 'approved' ? 'dashed' : 'solid';
|
|
138
|
+
|
|
139
|
+
const rectangleStyle: React.CSSProperties = {
|
|
140
|
+
padding: '12px 16px',
|
|
141
|
+
backgroundColor: hexToLightColor(fillColor),
|
|
142
|
+
color: '#000',
|
|
143
|
+
border: `2px ${borderStyle} ${hasViolations ? '#D0021B' : strokeColor}`,
|
|
144
|
+
fontSize: theme.fontSizes[0],
|
|
145
|
+
fontWeight: theme.fontWeights.medium,
|
|
146
|
+
fontFamily: theme.fonts.body,
|
|
147
|
+
width: '100%',
|
|
148
|
+
height: '100%',
|
|
149
|
+
minWidth: 20,
|
|
150
|
+
minHeight: 20,
|
|
151
|
+
display: 'flex',
|
|
152
|
+
flexDirection: 'column',
|
|
153
|
+
alignItems: 'center',
|
|
154
|
+
justifyContent: 'center',
|
|
155
|
+
gap: '4px',
|
|
156
|
+
boxShadow: isHighlighted
|
|
157
|
+
? '0 0 0 3px #3b82f6, 0 0 20px rgba(59, 130, 246, 0.5)'
|
|
158
|
+
: selected
|
|
159
|
+
? `0 0 0 2px ${strokeColor}`
|
|
160
|
+
: '0 2px 4px rgba(0,0,0,0.1)',
|
|
161
|
+
opacity: nodeOpacity,
|
|
162
|
+
transition: 'box-shadow 0.2s ease, opacity 0.3s ease',
|
|
163
|
+
animationDuration: animationType ? `${animationDuration}ms` : undefined,
|
|
164
|
+
boxSizing: 'border-box',
|
|
165
|
+
borderRadius: 0,
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const handleStyle = editable
|
|
169
|
+
? { background: fillColor, width: 12, height: 12, border: '2px solid white', boxShadow: '0 0 0 1px ' + fillColor }
|
|
170
|
+
: { background: fillColor, width: 8, height: 8, opacity: 0, pointerEvents: 'none' as const };
|
|
171
|
+
|
|
172
|
+
return (
|
|
173
|
+
<>
|
|
174
|
+
{editable && (
|
|
175
|
+
<NodeResizer
|
|
176
|
+
color={strokeColor}
|
|
177
|
+
isVisible={selected}
|
|
178
|
+
minWidth={40}
|
|
179
|
+
minHeight={30}
|
|
180
|
+
onResizeEnd={handleResizeEnd}
|
|
181
|
+
handleStyle={{ width: 8, height: 8, borderRadius: 2, zIndex: 20 }}
|
|
182
|
+
lineStyle={{ borderWidth: 1, zIndex: 20 }}
|
|
183
|
+
/>
|
|
184
|
+
)}
|
|
185
|
+
|
|
186
|
+
<Handle type="target" position={Position.Top} id="top" style={handleStyle} />
|
|
187
|
+
<Handle type="target" position={Position.Bottom} id="bottom" style={handleStyle} />
|
|
188
|
+
<Handle type="target" position={Position.Left} id="left" style={handleStyle} />
|
|
189
|
+
<Handle type="target" position={Position.Right} id="right" style={handleStyle} />
|
|
190
|
+
|
|
191
|
+
<div
|
|
192
|
+
ref={nodeRef}
|
|
193
|
+
style={{ position: 'relative', width: '100%', height: '100%' }}
|
|
194
|
+
onMouseDown={handleMouseDown}
|
|
195
|
+
onMouseEnter={handleMouseEnter}
|
|
196
|
+
onMouseLeave={handleMouseLeave}
|
|
197
|
+
>
|
|
198
|
+
<NodeBadges
|
|
199
|
+
shape="rectangle"
|
|
200
|
+
status={status}
|
|
201
|
+
sourceFiles={sourceFiles}
|
|
202
|
+
references={references}
|
|
203
|
+
opacity={nodeOpacity}
|
|
204
|
+
/>
|
|
205
|
+
<div style={rectangleStyle} className={getAnimationClass()}>
|
|
206
|
+
<NodeContent
|
|
207
|
+
displayName={displayName}
|
|
208
|
+
identifier={identifier}
|
|
209
|
+
icon={icon}
|
|
210
|
+
state={state}
|
|
211
|
+
stateDefinitions={stateDefinitions}
|
|
212
|
+
hasViolations={hasViolations}
|
|
213
|
+
/>
|
|
214
|
+
</div>
|
|
215
|
+
{tooltipsEnabled && (
|
|
216
|
+
<NodeTooltip
|
|
217
|
+
description={description}
|
|
218
|
+
otel={nodeData.otel as OtelInfo}
|
|
219
|
+
sources={nodeData.sources}
|
|
220
|
+
references={references}
|
|
221
|
+
visible={showTooltip}
|
|
222
|
+
nodeRef={nodeRef}
|
|
223
|
+
/>
|
|
224
|
+
)}
|
|
225
|
+
</div>
|
|
226
|
+
|
|
227
|
+
<Handle type="source" position={Position.Top} id="top-out" style={handleStyle} />
|
|
228
|
+
<Handle type="source" position={Position.Bottom} id="bottom-out" style={handleStyle} />
|
|
229
|
+
<Handle type="source" position={Position.Left} id="left-out" style={handleStyle} />
|
|
230
|
+
<Handle type="source" position={Position.Right} id="right-out" style={handleStyle} />
|
|
231
|
+
</>
|
|
232
|
+
);
|
|
233
|
+
};
|