@principal-ai/principal-view-react 0.6.8 → 0.6.9
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/components/NodeInfoPanel.d.ts.map +1 -1
- package/dist/components/NodeInfoPanel.js +91 -108
- package/dist/components/NodeInfoPanel.js.map +1 -1
- package/dist/nodes/CustomNode.d.ts +1 -1
- package/dist/nodes/CustomNode.d.ts.map +1 -1
- package/dist/nodes/CustomNode.js +4 -5
- package/dist/nodes/CustomNode.js.map +1 -1
- package/dist/utils/graphConverter.js +1 -1
- package/dist/utils/graphConverter.js.map +1 -1
- package/package.json +2 -2
- package/src/components/NodeInfoPanel.tsx +232 -237
- package/src/nodes/CustomNode.tsx +5 -8
- package/src/stories/EventDrivenAnimations.stories.tsx +6 -6
- package/src/stories/GraphRenderer.stories.tsx +27 -27
- package/src/stories/MultiConfig.stories.tsx +24 -24
- package/src/stories/MultiDirectionalConnections.stories.tsx +20 -20
- package/src/stories/NodeFieldsAudit.stories.tsx +1095 -0
- package/src/stories/NodeShapes.stories.tsx +37 -37
- package/src/utils/graphConverter.ts +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
2
|
import type { NodeState, NodeTypeDefinition } from '@principal-ai/principal-view-core';
|
|
3
3
|
import { resolveIcon } from '../utils/iconResolver';
|
|
4
4
|
|
|
@@ -40,10 +40,9 @@ export const NodeInfoPanel: React.FC<NodeInfoPanelProps> = ({
|
|
|
40
40
|
const color = typeDefinition?.color || '#888';
|
|
41
41
|
const canEdit = Boolean(onUpdate);
|
|
42
42
|
|
|
43
|
-
// Local state for
|
|
44
|
-
const [editingName, setEditingName] = useState(false);
|
|
45
|
-
const [nameValue, setNameValue] = useState('');
|
|
43
|
+
// Local state for UI
|
|
46
44
|
const [showIconPicker, setShowIconPicker] = useState(false);
|
|
45
|
+
const [showDetails, setShowDetails] = useState(false);
|
|
47
46
|
|
|
48
47
|
// Current icon - either from node data override or type definition
|
|
49
48
|
const currentIcon = (node.data?.icon as string) || typeDefinition?.icon;
|
|
@@ -53,13 +52,6 @@ export const NodeInfoPanel: React.FC<NodeInfoPanelProps> = ({
|
|
|
53
52
|
? Object.entries(typeDefinition.dataSchema).find(([, schema]) => schema.displayInLabel)?.[0]
|
|
54
53
|
: null;
|
|
55
54
|
|
|
56
|
-
// Initialize name value when node changes
|
|
57
|
-
useEffect(() => {
|
|
58
|
-
if (nameField && node.data?.[nameField]) {
|
|
59
|
-
setNameValue(String(node.data[nameField]));
|
|
60
|
-
}
|
|
61
|
-
}, [node.id, nameField, node.data]);
|
|
62
|
-
|
|
63
55
|
// Get fields to display based on dataSchema
|
|
64
56
|
const displayFields = typeDefinition?.dataSchema
|
|
65
57
|
? Object.entries(typeDefinition.dataSchema)
|
|
@@ -73,16 +65,10 @@ export const NodeInfoPanel: React.FC<NodeInfoPanelProps> = ({
|
|
|
73
65
|
|
|
74
66
|
// Always show basic node data if no schema is defined
|
|
75
67
|
const hasSchemaFields = displayFields.length > 0;
|
|
76
|
-
const nodeDataEntries = node.data ? Object.entries(node.data).filter(([key]) =>
|
|
68
|
+
const nodeDataEntries = node.data ? Object.entries(node.data).filter(([key]) => !['icon', 'name', 'description', 'sources'].includes(key)) : [];
|
|
77
69
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
onUpdate(node.id, {
|
|
81
|
-
data: { ...node.data, [nameField]: nameValue },
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
setEditingName(false);
|
|
85
|
-
};
|
|
70
|
+
// Get sources from node data
|
|
71
|
+
const sources = (node.data?.sources as string[]) || [];
|
|
86
72
|
|
|
87
73
|
const handleTypeChange = (newType: string) => {
|
|
88
74
|
if (onUpdate && newType !== node.type) {
|
|
@@ -114,7 +100,7 @@ export const NodeInfoPanel: React.FC<NodeInfoPanelProps> = ({
|
|
|
114
100
|
zIndex: 1000,
|
|
115
101
|
}}
|
|
116
102
|
>
|
|
117
|
-
{/* Header */}
|
|
103
|
+
{/* Header - shows node name */}
|
|
118
104
|
<div style={{
|
|
119
105
|
display: 'flex',
|
|
120
106
|
justifyContent: 'space-between',
|
|
@@ -124,7 +110,7 @@ export const NodeInfoPanel: React.FC<NodeInfoPanelProps> = ({
|
|
|
124
110
|
borderBottom: `2px solid ${color}`,
|
|
125
111
|
}}>
|
|
126
112
|
<div style={{ fontWeight: 'bold', fontSize: '14px' }}>
|
|
127
|
-
|
|
113
|
+
{node.data?.name || node.id}
|
|
128
114
|
</div>
|
|
129
115
|
<button
|
|
130
116
|
onClick={onClose}
|
|
@@ -146,256 +132,265 @@ export const NodeInfoPanel: React.FC<NodeInfoPanelProps> = ({
|
|
|
146
132
|
</button>
|
|
147
133
|
</div>
|
|
148
134
|
|
|
149
|
-
{/*
|
|
150
|
-
|
|
151
|
-
<div style={{ fontSize: '10px', color: '#666', marginBottom: '4px' }}>
|
|
152
|
-
Icon
|
|
153
|
-
</div>
|
|
154
|
-
<div style={{ position: 'relative' }}>
|
|
155
|
-
<button
|
|
156
|
-
onClick={() => canEdit && setShowIconPicker(!showIconPicker)}
|
|
157
|
-
disabled={!canEdit}
|
|
158
|
-
style={{
|
|
159
|
-
display: 'flex',
|
|
160
|
-
alignItems: 'center',
|
|
161
|
-
gap: '8px',
|
|
162
|
-
padding: '6px 10px',
|
|
163
|
-
backgroundColor: '#f5f5f5',
|
|
164
|
-
border: canEdit ? '1px dashed #ccc' : '1px solid #eee',
|
|
165
|
-
borderRadius: '4px',
|
|
166
|
-
cursor: canEdit ? 'pointer' : 'default',
|
|
167
|
-
fontSize: '12px',
|
|
168
|
-
width: '100%',
|
|
169
|
-
justifyContent: 'flex-start',
|
|
170
|
-
}}
|
|
171
|
-
>
|
|
172
|
-
<span style={{ display: 'flex', alignItems: 'center' }}>
|
|
173
|
-
{resolveIcon(currentIcon, 18)}
|
|
174
|
-
</span>
|
|
175
|
-
<span>{currentIcon || 'No icon'}</span>
|
|
176
|
-
{canEdit && <span style={{ marginLeft: 'auto', color: '#999', fontSize: '10px' }}>✎</span>}
|
|
177
|
-
</button>
|
|
178
|
-
|
|
179
|
-
{/* Icon Picker Dropdown */}
|
|
180
|
-
{showIconPicker && (
|
|
181
|
-
<div
|
|
182
|
-
style={{
|
|
183
|
-
position: 'absolute',
|
|
184
|
-
top: '100%',
|
|
185
|
-
left: 0,
|
|
186
|
-
right: 0,
|
|
187
|
-
marginTop: '4px',
|
|
188
|
-
backgroundColor: 'white',
|
|
189
|
-
border: '1px solid #ddd',
|
|
190
|
-
borderRadius: '4px',
|
|
191
|
-
boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
|
|
192
|
-
padding: '8px',
|
|
193
|
-
maxHeight: '200px',
|
|
194
|
-
overflowY: 'auto',
|
|
195
|
-
zIndex: 1001,
|
|
196
|
-
}}
|
|
197
|
-
>
|
|
198
|
-
<div
|
|
199
|
-
style={{
|
|
200
|
-
display: 'grid',
|
|
201
|
-
gridTemplateColumns: 'repeat(6, 1fr)',
|
|
202
|
-
gap: '4px',
|
|
203
|
-
}}
|
|
204
|
-
>
|
|
205
|
-
{COMMON_ICONS.map(iconName => (
|
|
206
|
-
<button
|
|
207
|
-
key={iconName}
|
|
208
|
-
onClick={() => handleIconSelect(iconName)}
|
|
209
|
-
title={iconName}
|
|
210
|
-
style={{
|
|
211
|
-
padding: '6px',
|
|
212
|
-
border: currentIcon === iconName ? `2px solid ${color}` : '1px solid #eee',
|
|
213
|
-
borderRadius: '4px',
|
|
214
|
-
backgroundColor: currentIcon === iconName ? '#f0f7ff' : 'white',
|
|
215
|
-
cursor: 'pointer',
|
|
216
|
-
display: 'flex',
|
|
217
|
-
alignItems: 'center',
|
|
218
|
-
justifyContent: 'center',
|
|
219
|
-
}}
|
|
220
|
-
>
|
|
221
|
-
{resolveIcon(iconName, 16)}
|
|
222
|
-
</button>
|
|
223
|
-
))}
|
|
224
|
-
</div>
|
|
225
|
-
</div>
|
|
226
|
-
)}
|
|
227
|
-
</div>
|
|
228
|
-
</div>
|
|
229
|
-
|
|
230
|
-
{/* Node Type - Editable if availableNodeTypes provided */}
|
|
231
|
-
<div style={{ marginBottom: '12px' }}>
|
|
232
|
-
<div style={{ fontSize: '10px', color: '#666', marginBottom: '4px' }}>
|
|
233
|
-
Type
|
|
234
|
-
</div>
|
|
235
|
-
{canEdit && availableNodeTypes && Object.keys(availableNodeTypes).length > 1 ? (
|
|
236
|
-
<select
|
|
237
|
-
value={node.type}
|
|
238
|
-
onChange={(e) => handleTypeChange(e.target.value)}
|
|
239
|
-
style={{
|
|
240
|
-
fontSize: '12px',
|
|
241
|
-
padding: '4px 8px',
|
|
242
|
-
borderRadius: '4px',
|
|
243
|
-
border: '1px solid #ccc',
|
|
244
|
-
backgroundColor: 'white',
|
|
245
|
-
cursor: 'pointer',
|
|
246
|
-
width: '100%',
|
|
247
|
-
}}
|
|
248
|
-
>
|
|
249
|
-
{Object.entries(availableNodeTypes).map(([typeName, typeDef]) => (
|
|
250
|
-
<option key={typeName} value={typeName}>
|
|
251
|
-
{typeName} ({typeDef.shape})
|
|
252
|
-
</option>
|
|
253
|
-
))}
|
|
254
|
-
</select>
|
|
255
|
-
) : (
|
|
256
|
-
<div style={{
|
|
257
|
-
fontSize: '12px',
|
|
258
|
-
padding: '4px 8px',
|
|
259
|
-
backgroundColor: color,
|
|
260
|
-
color: 'white',
|
|
261
|
-
borderRadius: '4px',
|
|
262
|
-
display: 'inline-block',
|
|
263
|
-
}}>
|
|
264
|
-
{node.type}
|
|
265
|
-
</div>
|
|
266
|
-
)}
|
|
267
|
-
</div>
|
|
268
|
-
|
|
269
|
-
{/* Node State */}
|
|
270
|
-
{node.state && (
|
|
135
|
+
{/* Description - first field under header */}
|
|
136
|
+
{node.data?.description && (
|
|
271
137
|
<div style={{ marginBottom: '12px' }}>
|
|
272
138
|
<div style={{ fontSize: '10px', color: '#666', marginBottom: '4px' }}>
|
|
273
|
-
|
|
139
|
+
Description
|
|
274
140
|
</div>
|
|
275
141
|
<div style={{
|
|
276
142
|
fontSize: '12px',
|
|
277
|
-
|
|
278
|
-
backgroundColor: typeDefinition?.states?.[node.state]?.color || '#888',
|
|
279
|
-
color: 'white',
|
|
280
|
-
borderRadius: '4px',
|
|
281
|
-
display: 'inline-block',
|
|
143
|
+
color: '#333',
|
|
282
144
|
}}>
|
|
283
|
-
{
|
|
145
|
+
{String(node.data.description)}
|
|
284
146
|
</div>
|
|
285
147
|
</div>
|
|
286
148
|
)}
|
|
287
149
|
|
|
288
|
-
{/*
|
|
289
|
-
{
|
|
150
|
+
{/* Sources - shown after description */}
|
|
151
|
+
{sources.length > 0 && (
|
|
290
152
|
<div style={{ marginBottom: '12px' }}>
|
|
291
153
|
<div style={{ fontSize: '10px', color: '#666', marginBottom: '4px' }}>
|
|
292
|
-
|
|
154
|
+
Sources
|
|
293
155
|
</div>
|
|
294
|
-
{
|
|
295
|
-
|
|
296
|
-
<
|
|
297
|
-
|
|
298
|
-
value={nameValue}
|
|
299
|
-
onChange={(e) => setNameValue(e.target.value)}
|
|
300
|
-
onKeyDown={(e) => {
|
|
301
|
-
if (e.key === 'Enter') handleNameSave();
|
|
302
|
-
if (e.key === 'Escape') setEditingName(false);
|
|
303
|
-
}}
|
|
304
|
-
autoFocus
|
|
156
|
+
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '4px' }}>
|
|
157
|
+
{sources.map((source, index) => (
|
|
158
|
+
<span
|
|
159
|
+
key={index}
|
|
305
160
|
style={{
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
161
|
+
fontSize: '11px',
|
|
162
|
+
padding: '2px 8px',
|
|
163
|
+
backgroundColor: '#f0f0f0',
|
|
309
164
|
borderRadius: '4px',
|
|
310
|
-
|
|
311
|
-
outline: 'none',
|
|
165
|
+
color: '#555',
|
|
312
166
|
}}
|
|
313
|
-
|
|
167
|
+
>
|
|
168
|
+
{source}
|
|
169
|
+
</span>
|
|
170
|
+
))}
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
)}
|
|
174
|
+
|
|
175
|
+
{/* Expand/Collapse button for additional details */}
|
|
176
|
+
<button
|
|
177
|
+
onClick={() => setShowDetails(!showDetails)}
|
|
178
|
+
style={{
|
|
179
|
+
width: '100%',
|
|
180
|
+
padding: '8px',
|
|
181
|
+
backgroundColor: '#f5f5f5',
|
|
182
|
+
border: '1px solid #ddd',
|
|
183
|
+
borderRadius: '4px',
|
|
184
|
+
cursor: 'pointer',
|
|
185
|
+
fontSize: '12px',
|
|
186
|
+
color: '#666',
|
|
187
|
+
display: 'flex',
|
|
188
|
+
alignItems: 'center',
|
|
189
|
+
justifyContent: 'center',
|
|
190
|
+
gap: '6px',
|
|
191
|
+
marginBottom: showDetails ? '12px' : '0',
|
|
192
|
+
}}
|
|
193
|
+
>
|
|
194
|
+
<span style={{ transform: showDetails ? 'rotate(180deg)' : 'rotate(0deg)', transition: 'transform 0.2s' }}>
|
|
195
|
+
▼
|
|
196
|
+
</span>
|
|
197
|
+
{showDetails ? 'Hide Details' : 'Show Details'}
|
|
198
|
+
</button>
|
|
199
|
+
|
|
200
|
+
{/* Expandable details section */}
|
|
201
|
+
{showDetails && (
|
|
202
|
+
<>
|
|
203
|
+
{/* Icon Selector */}
|
|
204
|
+
<div style={{ marginBottom: '12px' }}>
|
|
205
|
+
<div style={{ fontSize: '10px', color: '#666', marginBottom: '4px' }}>
|
|
206
|
+
Icon
|
|
207
|
+
</div>
|
|
208
|
+
<div style={{ position: 'relative' }}>
|
|
314
209
|
<button
|
|
315
|
-
onClick={
|
|
210
|
+
onClick={() => canEdit && setShowIconPicker(!showIconPicker)}
|
|
211
|
+
disabled={!canEdit}
|
|
316
212
|
style={{
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
213
|
+
display: 'flex',
|
|
214
|
+
alignItems: 'center',
|
|
215
|
+
gap: '8px',
|
|
216
|
+
padding: '6px 10px',
|
|
217
|
+
backgroundColor: '#f5f5f5',
|
|
218
|
+
border: canEdit ? '1px dashed #ccc' : '1px solid #eee',
|
|
321
219
|
borderRadius: '4px',
|
|
322
|
-
cursor: 'pointer',
|
|
323
|
-
fontSize: '
|
|
220
|
+
cursor: canEdit ? 'pointer' : 'default',
|
|
221
|
+
fontSize: '12px',
|
|
222
|
+
width: '100%',
|
|
223
|
+
justifyContent: 'flex-start',
|
|
324
224
|
}}
|
|
325
225
|
>
|
|
326
|
-
|
|
226
|
+
<span style={{ display: 'flex', alignItems: 'center' }}>
|
|
227
|
+
{resolveIcon(currentIcon, 18)}
|
|
228
|
+
</span>
|
|
229
|
+
<span>{currentIcon || 'No icon'}</span>
|
|
230
|
+
{canEdit && <span style={{ marginLeft: 'auto', color: '#999', fontSize: '10px' }}>✎</span>}
|
|
327
231
|
</button>
|
|
232
|
+
|
|
233
|
+
{/* Icon Picker Dropdown */}
|
|
234
|
+
{showIconPicker && (
|
|
235
|
+
<div
|
|
236
|
+
style={{
|
|
237
|
+
position: 'absolute',
|
|
238
|
+
top: '100%',
|
|
239
|
+
left: 0,
|
|
240
|
+
right: 0,
|
|
241
|
+
marginTop: '4px',
|
|
242
|
+
backgroundColor: 'white',
|
|
243
|
+
border: '1px solid #ddd',
|
|
244
|
+
borderRadius: '4px',
|
|
245
|
+
boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
|
|
246
|
+
padding: '8px',
|
|
247
|
+
maxHeight: '200px',
|
|
248
|
+
overflowY: 'auto',
|
|
249
|
+
zIndex: 1001,
|
|
250
|
+
}}
|
|
251
|
+
>
|
|
252
|
+
<div
|
|
253
|
+
style={{
|
|
254
|
+
display: 'grid',
|
|
255
|
+
gridTemplateColumns: 'repeat(6, 1fr)',
|
|
256
|
+
gap: '4px',
|
|
257
|
+
}}
|
|
258
|
+
>
|
|
259
|
+
{COMMON_ICONS.map(iconName => (
|
|
260
|
+
<button
|
|
261
|
+
key={iconName}
|
|
262
|
+
onClick={() => handleIconSelect(iconName)}
|
|
263
|
+
title={iconName}
|
|
264
|
+
style={{
|
|
265
|
+
padding: '6px',
|
|
266
|
+
border: currentIcon === iconName ? `2px solid ${color}` : '1px solid #eee',
|
|
267
|
+
borderRadius: '4px',
|
|
268
|
+
backgroundColor: currentIcon === iconName ? '#f0f7ff' : 'white',
|
|
269
|
+
cursor: 'pointer',
|
|
270
|
+
display: 'flex',
|
|
271
|
+
alignItems: 'center',
|
|
272
|
+
justifyContent: 'center',
|
|
273
|
+
}}
|
|
274
|
+
>
|
|
275
|
+
{resolveIcon(iconName, 16)}
|
|
276
|
+
</button>
|
|
277
|
+
))}
|
|
278
|
+
</div>
|
|
279
|
+
</div>
|
|
280
|
+
)}
|
|
281
|
+
</div>
|
|
282
|
+
</div>
|
|
283
|
+
|
|
284
|
+
{/* Node Type - Editable if availableNodeTypes provided */}
|
|
285
|
+
<div style={{ marginBottom: '12px' }}>
|
|
286
|
+
<div style={{ fontSize: '10px', color: '#666', marginBottom: '4px' }}>
|
|
287
|
+
Type
|
|
328
288
|
</div>
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
289
|
+
{canEdit && availableNodeTypes && Object.keys(availableNodeTypes).length > 1 ? (
|
|
290
|
+
<select
|
|
291
|
+
value={node.type}
|
|
292
|
+
onChange={(e) => handleTypeChange(e.target.value)}
|
|
293
|
+
style={{
|
|
294
|
+
fontSize: '12px',
|
|
295
|
+
padding: '4px 8px',
|
|
296
|
+
borderRadius: '4px',
|
|
297
|
+
border: '1px solid #ccc',
|
|
298
|
+
backgroundColor: 'white',
|
|
299
|
+
cursor: 'pointer',
|
|
300
|
+
width: '100%',
|
|
301
|
+
}}
|
|
302
|
+
>
|
|
303
|
+
{Object.entries(availableNodeTypes).map(([typeName, typeDef]) => (
|
|
304
|
+
<option key={typeName} value={typeName}>
|
|
305
|
+
{typeName} ({typeDef.shape})
|
|
306
|
+
</option>
|
|
307
|
+
))}
|
|
308
|
+
</select>
|
|
309
|
+
) : (
|
|
310
|
+
<div style={{
|
|
333
311
|
fontSize: '12px',
|
|
334
312
|
padding: '4px 8px',
|
|
335
|
-
backgroundColor:
|
|
313
|
+
backgroundColor: color,
|
|
314
|
+
color: 'white',
|
|
336
315
|
borderRadius: '4px',
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
{node.data?.[nameField] ?? '-'}
|
|
343
|
-
{canEdit && <span style={{ marginLeft: '8px', color: '#999', fontSize: '10px' }}>✎</span>}
|
|
344
|
-
</div>
|
|
345
|
-
)}
|
|
346
|
-
</div>
|
|
347
|
-
)}
|
|
348
|
-
|
|
349
|
-
{/* Display other schema-defined fields (non-editable for now) */}
|
|
350
|
-
{hasSchemaFields && displayFields.filter(f => f.field !== nameField).length > 0 && (
|
|
351
|
-
<div style={{ marginBottom: '12px' }}>
|
|
352
|
-
<div style={{ fontSize: '10px', color: '#666', marginBottom: '8px', fontWeight: 'bold' }}>
|
|
353
|
-
Properties
|
|
316
|
+
display: 'inline-block',
|
|
317
|
+
}}>
|
|
318
|
+
{node.type}
|
|
319
|
+
</div>
|
|
320
|
+
)}
|
|
354
321
|
</div>
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
322
|
+
|
|
323
|
+
{/* Node State */}
|
|
324
|
+
{node.state && (
|
|
325
|
+
<div style={{ marginBottom: '12px' }}>
|
|
326
|
+
<div style={{ fontSize: '10px', color: '#666', marginBottom: '4px' }}>
|
|
327
|
+
State
|
|
359
328
|
</div>
|
|
360
|
-
<div style={{
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
329
|
+
<div style={{
|
|
330
|
+
fontSize: '12px',
|
|
331
|
+
padding: '4px 8px',
|
|
332
|
+
backgroundColor: typeDefinition?.states?.[node.state]?.color || '#888',
|
|
333
|
+
color: 'white',
|
|
334
|
+
borderRadius: '4px',
|
|
335
|
+
display: 'inline-block',
|
|
336
|
+
}}>
|
|
337
|
+
{typeDefinition?.states?.[node.state]?.label || node.state}
|
|
366
338
|
</div>
|
|
367
339
|
</div>
|
|
368
|
-
)
|
|
369
|
-
</div>
|
|
370
|
-
)}
|
|
340
|
+
)}
|
|
371
341
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
</div>
|
|
378
|
-
{nodeDataEntries.map(([key, value]) => (
|
|
379
|
-
<div key={key} style={{ marginBottom: '8px' }}>
|
|
380
|
-
<div style={{ fontSize: '10px', color: '#666', marginBottom: '2px' }}>
|
|
381
|
-
{key}
|
|
342
|
+
{/* Display other schema-defined fields (non-editable for now) */}
|
|
343
|
+
{hasSchemaFields && displayFields.filter(f => f.field !== nameField).length > 0 && (
|
|
344
|
+
<div style={{ marginBottom: '12px' }}>
|
|
345
|
+
<div style={{ fontSize: '10px', color: '#666', marginBottom: '8px', fontWeight: 'bold' }}>
|
|
346
|
+
Properties
|
|
382
347
|
</div>
|
|
383
|
-
|
|
384
|
-
{
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
: '
|
|
348
|
+
{displayFields.filter(f => f.field !== nameField).map(({ field, label, value }) => (
|
|
349
|
+
<div key={field} style={{ marginBottom: '8px' }}>
|
|
350
|
+
<div style={{ fontSize: '10px', color: '#666', marginBottom: '2px' }}>
|
|
351
|
+
{label}
|
|
352
|
+
</div>
|
|
353
|
+
<div style={{ fontSize: '12px', color: '#333' }}>
|
|
354
|
+
{value !== undefined && value !== null
|
|
355
|
+
? typeof value === 'object'
|
|
356
|
+
? JSON.stringify(value, null, 2)
|
|
357
|
+
: String(value)
|
|
358
|
+
: '-'}
|
|
359
|
+
</div>
|
|
360
|
+
</div>
|
|
361
|
+
))}
|
|
362
|
+
</div>
|
|
363
|
+
)}
|
|
364
|
+
|
|
365
|
+
{/* Show all node data if no schema is defined */}
|
|
366
|
+
{!hasSchemaFields && nodeDataEntries.length > 0 && (
|
|
367
|
+
<div style={{ marginBottom: '12px' }}>
|
|
368
|
+
<div style={{ fontSize: '10px', color: '#666', marginBottom: '8px', fontWeight: 'bold' }}>
|
|
369
|
+
Data
|
|
389
370
|
</div>
|
|
371
|
+
{nodeDataEntries.map(([key, value]) => (
|
|
372
|
+
<div key={key} style={{ marginBottom: '8px' }}>
|
|
373
|
+
<div style={{ fontSize: '10px', color: '#666', marginBottom: '2px' }}>
|
|
374
|
+
{key}
|
|
375
|
+
</div>
|
|
376
|
+
<div style={{ fontSize: '12px', color: '#333', wordBreak: 'break-word' }}>
|
|
377
|
+
{value !== undefined && value !== null
|
|
378
|
+
? typeof value === 'object'
|
|
379
|
+
? JSON.stringify(value, null, 2)
|
|
380
|
+
: String(value)
|
|
381
|
+
: '-'}
|
|
382
|
+
</div>
|
|
383
|
+
</div>
|
|
384
|
+
))}
|
|
390
385
|
</div>
|
|
391
|
-
)
|
|
392
|
-
</div>
|
|
393
|
-
)}
|
|
386
|
+
)}
|
|
394
387
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
388
|
+
{/* Metadata */}
|
|
389
|
+
<div style={{ fontSize: '10px', color: '#999', marginTop: '12px', paddingTop: '8px', borderTop: '1px solid #eee' }}>
|
|
390
|
+
ID: {node.id}
|
|
391
|
+
</div>
|
|
392
|
+
</>
|
|
393
|
+
)}
|
|
399
394
|
|
|
400
395
|
{/* Delete Button */}
|
|
401
396
|
{onDelete && (
|
package/src/nodes/CustomNode.tsx
CHANGED
|
@@ -5,7 +5,7 @@ import type { NodeTypeDefinition } from '@principal-ai/principal-view-core';
|
|
|
5
5
|
import { resolveIcon } from '../utils/iconResolver';
|
|
6
6
|
|
|
7
7
|
export interface CustomNodeData extends Record<string, unknown> {
|
|
8
|
-
|
|
8
|
+
name: string;
|
|
9
9
|
typeDefinition: NodeTypeDefinition;
|
|
10
10
|
state?: string;
|
|
11
11
|
hasViolations?: boolean;
|
|
@@ -60,11 +60,8 @@ export const CustomNode: React.FC<NodeProps<any>> = ({ data, selected }) => {
|
|
|
60
60
|
// Use fillColor as the primary "color" for backwards compatibility
|
|
61
61
|
const color = fillColor;
|
|
62
62
|
|
|
63
|
-
// Get
|
|
64
|
-
const
|
|
65
|
-
([, schema]) => schema.displayInLabel
|
|
66
|
-
)?.[0];
|
|
67
|
-
const displayLabel = labelField && nodeData[labelField] ? String(nodeData[labelField]) : nodeProps.label;
|
|
63
|
+
// Get display name - use name from props (falls back to node.id in converter)
|
|
64
|
+
const displayName = nodeProps.name;
|
|
68
65
|
|
|
69
66
|
// Icon priority: node data override > state icon (node data states first) > type definition icon
|
|
70
67
|
const icon = (nodeData.icon as string)
|
|
@@ -263,7 +260,7 @@ export const CustomNode: React.FC<NodeProps<any>> = ({ data, selected }) => {
|
|
|
263
260
|
<div style={hexagonInnerStyle}>
|
|
264
261
|
{icon && <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>{resolveIcon(icon, 20)}</div>}
|
|
265
262
|
<div style={{ textAlign: 'center', wordBreak: 'break-word' }}>
|
|
266
|
-
{
|
|
263
|
+
{displayName}
|
|
267
264
|
</div>
|
|
268
265
|
{state && (
|
|
269
266
|
<div style={{
|
|
@@ -294,7 +291,7 @@ export const CustomNode: React.FC<NodeProps<any>> = ({ data, selected }) => {
|
|
|
294
291
|
<div style={isDiamond ? { transform: 'rotate(-45deg)' } : {}}>
|
|
295
292
|
{icon && <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>{resolveIcon(icon, 20)}</div>}
|
|
296
293
|
<div style={{ textAlign: 'center', wordBreak: 'break-word' }}>
|
|
297
|
-
{
|
|
294
|
+
{displayName}
|
|
298
295
|
</div>
|
|
299
296
|
{state && (
|
|
300
297
|
<div style={{
|
|
@@ -28,7 +28,7 @@ const sampleCanvas: ExtendedCanvas = {
|
|
|
28
28
|
height: 70,
|
|
29
29
|
text: 'Source',
|
|
30
30
|
color: '#4A90E2',
|
|
31
|
-
|
|
31
|
+
pv: {
|
|
32
32
|
nodeType: 'process',
|
|
33
33
|
shape: 'rectangle',
|
|
34
34
|
icon: 'Settings',
|
|
@@ -49,7 +49,7 @@ const sampleCanvas: ExtendedCanvas = {
|
|
|
49
49
|
height: 70,
|
|
50
50
|
text: 'Processor',
|
|
51
51
|
color: '#4A90E2',
|
|
52
|
-
|
|
52
|
+
pv: {
|
|
53
53
|
nodeType: 'process',
|
|
54
54
|
shape: 'rectangle',
|
|
55
55
|
icon: 'Settings',
|
|
@@ -70,7 +70,7 @@ const sampleCanvas: ExtendedCanvas = {
|
|
|
70
70
|
height: 100,
|
|
71
71
|
text: 'Storage',
|
|
72
72
|
color: '#7B68EE',
|
|
73
|
-
|
|
73
|
+
pv: {
|
|
74
74
|
nodeType: 'data',
|
|
75
75
|
shape: 'circle',
|
|
76
76
|
icon: 'Database',
|
|
@@ -82,16 +82,16 @@ const sampleCanvas: ExtendedCanvas = {
|
|
|
82
82
|
id: 'edge-1',
|
|
83
83
|
fromNode: 'node-1',
|
|
84
84
|
toNode: 'node-2',
|
|
85
|
-
|
|
85
|
+
pv: { edgeType: 'dataflow' },
|
|
86
86
|
},
|
|
87
87
|
{
|
|
88
88
|
id: 'edge-2',
|
|
89
89
|
fromNode: 'node-2',
|
|
90
90
|
toNode: 'node-3',
|
|
91
|
-
|
|
91
|
+
pv: { edgeType: 'dataflow' },
|
|
92
92
|
},
|
|
93
93
|
],
|
|
94
|
-
|
|
94
|
+
pv: {
|
|
95
95
|
version: '1.0.0',
|
|
96
96
|
name: 'Event-Driven Animation Demo',
|
|
97
97
|
description: 'Demonstrates event-driven animations',
|