@principal-ai/principal-view-react 0.6.6

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 (96) hide show
  1. package/README.md +111 -0
  2. package/dist/components/ConfigurationSelector.d.ts +37 -0
  3. package/dist/components/ConfigurationSelector.d.ts.map +1 -0
  4. package/dist/components/ConfigurationSelector.js +67 -0
  5. package/dist/components/ConfigurationSelector.js.map +1 -0
  6. package/dist/components/EdgeInfoPanel.d.ts +16 -0
  7. package/dist/components/EdgeInfoPanel.d.ts.map +1 -0
  8. package/dist/components/EdgeInfoPanel.js +85 -0
  9. package/dist/components/EdgeInfoPanel.js.map +1 -0
  10. package/dist/components/EventLog.d.ts +20 -0
  11. package/dist/components/EventLog.d.ts.map +1 -0
  12. package/dist/components/EventLog.js +13 -0
  13. package/dist/components/EventLog.js.map +1 -0
  14. package/dist/components/EventLog.test.d.ts +2 -0
  15. package/dist/components/EventLog.test.d.ts.map +1 -0
  16. package/dist/components/EventLog.test.js +73 -0
  17. package/dist/components/EventLog.test.js.map +1 -0
  18. package/dist/components/GraphRenderer.d.ts +121 -0
  19. package/dist/components/GraphRenderer.d.ts.map +1 -0
  20. package/dist/components/GraphRenderer.js +809 -0
  21. package/dist/components/GraphRenderer.js.map +1 -0
  22. package/dist/components/GraphRenderer.test.d.ts +2 -0
  23. package/dist/components/GraphRenderer.test.d.ts.map +1 -0
  24. package/dist/components/GraphRenderer.test.js +88 -0
  25. package/dist/components/GraphRenderer.test.js.map +1 -0
  26. package/dist/components/MetricsDashboard.d.ts +14 -0
  27. package/dist/components/MetricsDashboard.d.ts.map +1 -0
  28. package/dist/components/MetricsDashboard.js +13 -0
  29. package/dist/components/MetricsDashboard.js.map +1 -0
  30. package/dist/components/NodeInfoPanel.d.ts +21 -0
  31. package/dist/components/NodeInfoPanel.d.ts.map +1 -0
  32. package/dist/components/NodeInfoPanel.js +217 -0
  33. package/dist/components/NodeInfoPanel.js.map +1 -0
  34. package/dist/edges/CustomEdge.d.ts +16 -0
  35. package/dist/edges/CustomEdge.d.ts.map +1 -0
  36. package/dist/edges/CustomEdge.js +200 -0
  37. package/dist/edges/CustomEdge.js.map +1 -0
  38. package/dist/edges/GenericEdge.d.ts +18 -0
  39. package/dist/edges/GenericEdge.d.ts.map +1 -0
  40. package/dist/edges/GenericEdge.js +14 -0
  41. package/dist/edges/GenericEdge.js.map +1 -0
  42. package/dist/hooks/usePathBasedEvents.d.ts +42 -0
  43. package/dist/hooks/usePathBasedEvents.d.ts.map +1 -0
  44. package/dist/hooks/usePathBasedEvents.js +122 -0
  45. package/dist/hooks/usePathBasedEvents.js.map +1 -0
  46. package/dist/index.d.ts +33 -0
  47. package/dist/index.d.ts.map +1 -0
  48. package/dist/index.js +41 -0
  49. package/dist/index.js.map +1 -0
  50. package/dist/nodes/CustomNode.d.ts +18 -0
  51. package/dist/nodes/CustomNode.d.ts.map +1 -0
  52. package/dist/nodes/CustomNode.js +298 -0
  53. package/dist/nodes/CustomNode.js.map +1 -0
  54. package/dist/nodes/GenericNode.d.ts +20 -0
  55. package/dist/nodes/GenericNode.d.ts.map +1 -0
  56. package/dist/nodes/GenericNode.js +24 -0
  57. package/dist/nodes/GenericNode.js.map +1 -0
  58. package/dist/utils/animationMapping.d.ts +53 -0
  59. package/dist/utils/animationMapping.d.ts.map +1 -0
  60. package/dist/utils/animationMapping.js +133 -0
  61. package/dist/utils/animationMapping.js.map +1 -0
  62. package/dist/utils/graphConverter.d.ts +22 -0
  63. package/dist/utils/graphConverter.d.ts.map +1 -0
  64. package/dist/utils/graphConverter.js +176 -0
  65. package/dist/utils/graphConverter.js.map +1 -0
  66. package/dist/utils/iconResolver.d.ts +29 -0
  67. package/dist/utils/iconResolver.d.ts.map +1 -0
  68. package/dist/utils/iconResolver.js +68 -0
  69. package/dist/utils/iconResolver.js.map +1 -0
  70. package/package.json +61 -0
  71. package/src/components/ConfigurationSelector.tsx +147 -0
  72. package/src/components/EdgeInfoPanel.tsx +198 -0
  73. package/src/components/EventLog.test.tsx +85 -0
  74. package/src/components/EventLog.tsx +51 -0
  75. package/src/components/GraphRenderer.test.tsx +118 -0
  76. package/src/components/GraphRenderer.tsx +1222 -0
  77. package/src/components/MetricsDashboard.tsx +40 -0
  78. package/src/components/NodeInfoPanel.tsx +425 -0
  79. package/src/edges/CustomEdge.tsx +344 -0
  80. package/src/edges/GenericEdge.tsx +40 -0
  81. package/src/hooks/usePathBasedEvents.ts +182 -0
  82. package/src/index.ts +67 -0
  83. package/src/nodes/CustomNode.tsx +432 -0
  84. package/src/nodes/GenericNode.tsx +54 -0
  85. package/src/stories/AnimationWorkshop.stories.tsx +608 -0
  86. package/src/stories/EventDrivenAnimations.stories.tsx +499 -0
  87. package/src/stories/EventLog.stories.tsx +161 -0
  88. package/src/stories/GraphRenderer.stories.tsx +628 -0
  89. package/src/stories/Introduction.mdx +51 -0
  90. package/src/stories/MetricsDashboard.stories.tsx +227 -0
  91. package/src/stories/MultiConfig.stories.tsx +531 -0
  92. package/src/stories/MultiDirectionalConnections.stories.tsx +345 -0
  93. package/src/stories/NodeShapes.stories.tsx +769 -0
  94. package/src/utils/animationMapping.ts +170 -0
  95. package/src/utils/graphConverter.ts +218 -0
  96. package/src/utils/iconResolver.tsx +49 -0
@@ -0,0 +1,40 @@
1
+ import React from 'react';
2
+ import type { GraphMetrics } from '@principal-ai/principal-view-core';
3
+
4
+ export interface MetricsDashboardProps {
5
+ /** Current metrics */
6
+ metrics: GraphMetrics;
7
+
8
+ /** Optional class name */
9
+ className?: string;
10
+ }
11
+
12
+ /**
13
+ * Metrics dashboard component for displaying graph statistics
14
+ * TODO: Implement full metrics visualization
15
+ */
16
+ export const MetricsDashboard: React.FC<MetricsDashboardProps> = ({ metrics, className }) => {
17
+ return (
18
+ <div className={className} style={{ padding: '20px', border: '1px solid #ccc' }}>
19
+ <h3>Metrics Dashboard (TODO)</h3>
20
+ <div>
21
+ <p>Total Nodes: {metrics.nodes.total}</p>
22
+ <p>Total Edges: {metrics.edges.total}</p>
23
+ <p>Total Events: {metrics.events.total}</p>
24
+ <p>Violations: {metrics.validation.violations}</p>
25
+ <p>Health Score: {metrics.validation.healthScore}</p>
26
+ </div>
27
+ <div>
28
+ <strong>TODO:</strong>
29
+ <ul>
30
+ <li>Add visual charts/graphs</li>
31
+ <li>Show breakdown by node type</li>
32
+ <li>Show breakdown by edge type</li>
33
+ <li>Show event rate over time</li>
34
+ <li>Add health score indicator</li>
35
+ <li>Add performance metrics</li>
36
+ </ul>
37
+ </div>
38
+ </div>
39
+ );
40
+ };
@@ -0,0 +1,425 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import type { NodeState, NodeTypeDefinition } from '@principal-ai/principal-view-core';
3
+ import { resolveIcon } from '../utils/iconResolver';
4
+
5
+ // Common icons for the icon selector
6
+ const COMMON_ICONS = [
7
+ 'Settings', 'Database', 'Package', 'Server', 'Cloud', 'Globe',
8
+ 'File', 'Folder', 'Code', 'Terminal', 'Cpu', 'HardDrive',
9
+ 'Network', 'Wifi', 'Lock', 'Unlock', 'Key', 'Shield',
10
+ 'User', 'Users', 'Mail', 'MessageSquare', 'Bell', 'Calendar',
11
+ 'Clock', 'Timer', 'Zap', 'Activity', 'BarChart', 'PieChart',
12
+ 'CheckCircle', 'XCircle', 'AlertCircle', 'Info', 'HelpCircle',
13
+ 'Play', 'Pause', 'Square', 'Circle', 'Triangle', 'Hexagon',
14
+ 'Box', 'Layers', 'GitBranch', 'GitCommit', 'GitMerge', 'GitPullRequest',
15
+ ];
16
+
17
+ export interface NodeInfoPanelProps {
18
+ node: NodeState;
19
+ typeDefinition: NodeTypeDefinition;
20
+ /** Available node types for the type selector */
21
+ availableNodeTypes?: Record<string, NodeTypeDefinition>;
22
+ onClose: () => void;
23
+ /** Optional callback to delete the node. If not provided, delete button is hidden. */
24
+ onDelete?: (nodeId: string) => void;
25
+ /** Optional callback to update the node. If not provided, edit fields are disabled. */
26
+ onUpdate?: (nodeId: string, updates: { type?: string; data?: Record<string, unknown> }) => void;
27
+ }
28
+
29
+ /**
30
+ * Panel that displays information about a selected node with optional editing
31
+ */
32
+ export const NodeInfoPanel: React.FC<NodeInfoPanelProps> = ({
33
+ node,
34
+ typeDefinition,
35
+ availableNodeTypes,
36
+ onClose,
37
+ onDelete,
38
+ onUpdate,
39
+ }) => {
40
+ const color = typeDefinition?.color || '#888';
41
+ const canEdit = Boolean(onUpdate);
42
+
43
+ // Local state for editing
44
+ const [editingName, setEditingName] = useState(false);
45
+ const [nameValue, setNameValue] = useState('');
46
+ const [showIconPicker, setShowIconPicker] = useState(false);
47
+
48
+ // Current icon - either from node data override or type definition
49
+ const currentIcon = (node.data?.icon as string) || typeDefinition?.icon;
50
+
51
+ // Find the name field from data schema
52
+ const nameField = typeDefinition?.dataSchema
53
+ ? Object.entries(typeDefinition.dataSchema).find(([, schema]) => schema.displayInLabel)?.[0]
54
+ : null;
55
+
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
+ // Get fields to display based on dataSchema
64
+ const displayFields = typeDefinition?.dataSchema
65
+ ? Object.entries(typeDefinition.dataSchema)
66
+ .filter(([, schema]) => schema.displayInLabel)
67
+ .map(([field]) => ({
68
+ field,
69
+ label: field,
70
+ value: node.data?.[field],
71
+ }))
72
+ : [];
73
+
74
+ // Always show basic node data if no schema is defined
75
+ const hasSchemaFields = displayFields.length > 0;
76
+ const nodeDataEntries = node.data ? Object.entries(node.data).filter(([key]) => key !== 'icon') : [];
77
+
78
+ const handleNameSave = () => {
79
+ if (onUpdate && nameField && nameValue !== node.data?.[nameField]) {
80
+ onUpdate(node.id, {
81
+ data: { ...node.data, [nameField]: nameValue },
82
+ });
83
+ }
84
+ setEditingName(false);
85
+ };
86
+
87
+ const handleTypeChange = (newType: string) => {
88
+ if (onUpdate && newType !== node.type) {
89
+ onUpdate(node.id, { type: newType });
90
+ }
91
+ };
92
+
93
+ const handleIconSelect = (iconName: string) => {
94
+ if (onUpdate) {
95
+ onUpdate(node.id, {
96
+ data: { ...node.data, icon: iconName },
97
+ });
98
+ }
99
+ setShowIconPicker(false);
100
+ };
101
+
102
+ return (
103
+ <div
104
+ style={{
105
+ position: 'absolute',
106
+ top: '60px',
107
+ right: '20px',
108
+ backgroundColor: 'white',
109
+ borderRadius: '8px',
110
+ boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
111
+ padding: '16px',
112
+ minWidth: '250px',
113
+ maxWidth: '350px',
114
+ zIndex: 1000,
115
+ }}
116
+ >
117
+ {/* Header */}
118
+ <div style={{
119
+ display: 'flex',
120
+ justifyContent: 'space-between',
121
+ alignItems: 'center',
122
+ marginBottom: '12px',
123
+ paddingBottom: '8px',
124
+ borderBottom: `2px solid ${color}`,
125
+ }}>
126
+ <div style={{ fontWeight: 'bold', fontSize: '14px' }}>
127
+ Node Information
128
+ </div>
129
+ <button
130
+ onClick={onClose}
131
+ style={{
132
+ border: 'none',
133
+ background: 'none',
134
+ cursor: 'pointer',
135
+ fontSize: '18px',
136
+ color: '#666',
137
+ padding: '0',
138
+ width: '24px',
139
+ height: '24px',
140
+ display: 'flex',
141
+ alignItems: 'center',
142
+ justifyContent: 'center',
143
+ }}
144
+ >
145
+ ×
146
+ </button>
147
+ </div>
148
+
149
+ {/* Icon Selector */}
150
+ <div style={{ marginBottom: '12px' }}>
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 && (
271
+ <div style={{ marginBottom: '12px' }}>
272
+ <div style={{ fontSize: '10px', color: '#666', marginBottom: '4px' }}>
273
+ State
274
+ </div>
275
+ <div style={{
276
+ fontSize: '12px',
277
+ padding: '4px 8px',
278
+ backgroundColor: typeDefinition?.states?.[node.state]?.color || '#888',
279
+ color: 'white',
280
+ borderRadius: '4px',
281
+ display: 'inline-block',
282
+ }}>
283
+ {typeDefinition?.states?.[node.state]?.label || node.state}
284
+ </div>
285
+ </div>
286
+ )}
287
+
288
+ {/* Editable Name Field */}
289
+ {nameField && (
290
+ <div style={{ marginBottom: '12px' }}>
291
+ <div style={{ fontSize: '10px', color: '#666', marginBottom: '4px' }}>
292
+ Name
293
+ </div>
294
+ {canEdit && editingName ? (
295
+ <div style={{ display: 'flex', gap: '4px' }}>
296
+ <input
297
+ type="text"
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
305
+ style={{
306
+ flex: 1,
307
+ fontSize: '12px',
308
+ padding: '4px 8px',
309
+ borderRadius: '4px',
310
+ border: '1px solid #4A90E2',
311
+ outline: 'none',
312
+ }}
313
+ />
314
+ <button
315
+ onClick={handleNameSave}
316
+ style={{
317
+ padding: '4px 8px',
318
+ backgroundColor: '#4A90E2',
319
+ color: 'white',
320
+ border: 'none',
321
+ borderRadius: '4px',
322
+ cursor: 'pointer',
323
+ fontSize: '11px',
324
+ }}
325
+ >
326
+ Save
327
+ </button>
328
+ </div>
329
+ ) : (
330
+ <div
331
+ onClick={() => canEdit && setEditingName(true)}
332
+ style={{
333
+ fontSize: '12px',
334
+ padding: '4px 8px',
335
+ backgroundColor: '#f5f5f5',
336
+ borderRadius: '4px',
337
+ cursor: canEdit ? 'pointer' : 'default',
338
+ border: canEdit ? '1px dashed #ccc' : 'none',
339
+ }}
340
+ title={canEdit ? 'Click to edit' : undefined}
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
354
+ </div>
355
+ {displayFields.filter(f => f.field !== nameField).map(({ field, label, value }) => (
356
+ <div key={field} style={{ marginBottom: '8px' }}>
357
+ <div style={{ fontSize: '10px', color: '#666', marginBottom: '2px' }}>
358
+ {label}
359
+ </div>
360
+ <div style={{ fontSize: '12px', color: '#333' }}>
361
+ {value !== undefined && value !== null
362
+ ? typeof value === 'object'
363
+ ? JSON.stringify(value, null, 2)
364
+ : String(value)
365
+ : '-'}
366
+ </div>
367
+ </div>
368
+ ))}
369
+ </div>
370
+ )}
371
+
372
+ {/* Show all node data if no schema is defined */}
373
+ {!hasSchemaFields && nodeDataEntries.length > 0 && (
374
+ <div style={{ marginBottom: '12px' }}>
375
+ <div style={{ fontSize: '10px', color: '#666', marginBottom: '8px', fontWeight: 'bold' }}>
376
+ Data
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}
382
+ </div>
383
+ <div style={{ fontSize: '12px', color: '#333', wordBreak: 'break-word' }}>
384
+ {value !== undefined && value !== null
385
+ ? typeof value === 'object'
386
+ ? JSON.stringify(value, null, 2)
387
+ : String(value)
388
+ : '-'}
389
+ </div>
390
+ </div>
391
+ ))}
392
+ </div>
393
+ )}
394
+
395
+ {/* Metadata */}
396
+ <div style={{ fontSize: '10px', color: '#999', marginTop: '12px', paddingTop: '8px', borderTop: '1px solid #eee' }}>
397
+ ID: {node.id}
398
+ </div>
399
+
400
+ {/* Delete Button */}
401
+ {onDelete && (
402
+ <button
403
+ onClick={() => {
404
+ onDelete(node.id);
405
+ onClose();
406
+ }}
407
+ style={{
408
+ marginTop: '12px',
409
+ width: '100%',
410
+ padding: '8px 12px',
411
+ backgroundColor: '#dc3545',
412
+ color: 'white',
413
+ border: 'none',
414
+ borderRadius: '4px',
415
+ cursor: 'pointer',
416
+ fontSize: '12px',
417
+ fontWeight: 'bold',
418
+ }}
419
+ >
420
+ Delete Node
421
+ </button>
422
+ )}
423
+ </div>
424
+ );
425
+ };