@principal-ai/principal-view-react 0.13.9 → 0.13.11

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.
@@ -0,0 +1,329 @@
1
+ import React from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import { MultiCanvasRenderer } from '../components/MultiCanvasRenderer';
4
+ import type { MultiCanvasLayout } from '../components/MultiCanvasRenderer';
5
+ import type { ExtendedCanvas } from '@principal-ai/principal-view-core';
6
+ import { ThemeProvider, defaultEditorTheme } from '@principal-ade/industry-theme';
7
+
8
+ const meta = {
9
+ title: 'Components/MultiCanvasRenderer',
10
+ component: MultiCanvasRenderer,
11
+ parameters: {
12
+ layout: 'fullscreen',
13
+ },
14
+ tags: ['autodocs'],
15
+ decorators: [
16
+ (Story) => (
17
+ <ThemeProvider theme={defaultEditorTheme}>
18
+ <div style={{ width: '100vw', height: '100vh' }}>
19
+ <Story />
20
+ </div>
21
+ </ThemeProvider>
22
+ ),
23
+ ],
24
+ } satisfies Meta<typeof MultiCanvasRenderer>;
25
+
26
+ export default meta;
27
+ type Story = StoryObj<typeof meta>;
28
+
29
+ // ============================================================================
30
+ // Sample Canvases
31
+ // ============================================================================
32
+
33
+ const authFlowCanvas: ExtendedCanvas = {
34
+ nodes: [
35
+ {
36
+ id: 'login',
37
+ type: 'text',
38
+ x: 0,
39
+ y: 0,
40
+ width: 120,
41
+ height: 60,
42
+ text: 'Login',
43
+ color: '#4A90E2',
44
+ pv: { nodeType: 'process', shape: 'rectangle', icon: 'User' },
45
+ },
46
+ {
47
+ id: 'validate',
48
+ type: 'text',
49
+ x: 170,
50
+ y: 0,
51
+ width: 120,
52
+ height: 60,
53
+ text: 'Validate',
54
+ color: '#F5A623',
55
+ pv: { nodeType: 'process', shape: 'rectangle', icon: 'Shield' },
56
+ },
57
+ {
58
+ id: 'session',
59
+ type: 'text',
60
+ x: 340,
61
+ y: 0,
62
+ width: 120,
63
+ height: 60,
64
+ text: 'Session',
65
+ color: '#7ED321',
66
+ pv: { nodeType: 'data', shape: 'rectangle', icon: 'Key' },
67
+ },
68
+ ],
69
+ edges: [
70
+ { id: 'e1', fromNode: 'login', toNode: 'validate', pv: { edgeType: 'flow' } },
71
+ { id: 'e2', fromNode: 'validate', toNode: 'session', pv: { edgeType: 'flow' } },
72
+ ],
73
+ pv: {
74
+ version: '1.0.0',
75
+ name: 'Authentication Flow',
76
+ },
77
+ };
78
+
79
+ const dataProcessingCanvas: ExtendedCanvas = {
80
+ nodes: [
81
+ {
82
+ id: 'input',
83
+ type: 'text',
84
+ x: 0,
85
+ y: 0,
86
+ width: 100,
87
+ height: 100,
88
+ text: 'Input',
89
+ color: '#7B68EE',
90
+ pv: { nodeType: 'data', shape: 'circle', icon: 'Download' },
91
+ },
92
+ {
93
+ id: 'transform',
94
+ type: 'text',
95
+ x: 150,
96
+ y: 15,
97
+ width: 140,
98
+ height: 70,
99
+ text: 'Transform',
100
+ color: '#4A90E2',
101
+ pv: { nodeType: 'process', shape: 'rectangle', icon: 'Settings' },
102
+ },
103
+ {
104
+ id: 'output',
105
+ type: 'text',
106
+ x: 340,
107
+ y: 0,
108
+ width: 100,
109
+ height: 100,
110
+ text: 'Output',
111
+ color: '#7B68EE',
112
+ pv: { nodeType: 'data', shape: 'circle', icon: 'Upload' },
113
+ },
114
+ ],
115
+ edges: [
116
+ { id: 'e1', fromNode: 'input', toNode: 'transform', pv: { edgeType: 'dataflow' } },
117
+ { id: 'e2', fromNode: 'transform', toNode: 'output', pv: { edgeType: 'dataflow' } },
118
+ ],
119
+ pv: {
120
+ version: '1.0.0',
121
+ name: 'Data Processing Pipeline',
122
+ },
123
+ };
124
+
125
+ const notificationCanvas: ExtendedCanvas = {
126
+ nodes: [
127
+ {
128
+ id: 'event',
129
+ type: 'text',
130
+ x: 0,
131
+ y: 30,
132
+ width: 100,
133
+ height: 60,
134
+ text: 'Event',
135
+ color: '#F5A623',
136
+ pv: { nodeType: 'trigger', shape: 'rectangle', icon: 'Zap' },
137
+ },
138
+ {
139
+ id: 'router',
140
+ type: 'text',
141
+ x: 150,
142
+ y: 0,
143
+ width: 100,
144
+ height: 100,
145
+ text: 'Router',
146
+ color: '#4A90E2',
147
+ pv: { nodeType: 'process', shape: 'diamond', icon: 'GitBranch' },
148
+ },
149
+ {
150
+ id: 'email',
151
+ type: 'text',
152
+ x: 300,
153
+ y: -30,
154
+ width: 100,
155
+ height: 50,
156
+ text: 'Email',
157
+ color: '#7ED321',
158
+ pv: { nodeType: 'action', shape: 'rectangle', icon: 'Mail' },
159
+ },
160
+ {
161
+ id: 'sms',
162
+ type: 'text',
163
+ x: 300,
164
+ y: 40,
165
+ width: 100,
166
+ height: 50,
167
+ text: 'SMS',
168
+ color: '#7ED321',
169
+ pv: { nodeType: 'action', shape: 'rectangle', icon: 'MessageSquare' },
170
+ },
171
+ {
172
+ id: 'push',
173
+ type: 'text',
174
+ x: 300,
175
+ y: 110,
176
+ width: 100,
177
+ height: 50,
178
+ text: 'Push',
179
+ color: '#7ED321',
180
+ pv: { nodeType: 'action', shape: 'rectangle', icon: 'Bell' },
181
+ },
182
+ ],
183
+ edges: [
184
+ { id: 'e1', fromNode: 'event', toNode: 'router', pv: { edgeType: 'flow' } },
185
+ { id: 'e2', fromNode: 'router', toNode: 'email', pv: { edgeType: 'flow' } },
186
+ { id: 'e3', fromNode: 'router', toNode: 'sms', pv: { edgeType: 'flow' } },
187
+ { id: 'e4', fromNode: 'router', toNode: 'push', pv: { edgeType: 'flow' } },
188
+ ],
189
+ pv: {
190
+ version: '1.0.0',
191
+ name: 'Notification System',
192
+ },
193
+ };
194
+
195
+ const monitoringCanvas: ExtendedCanvas = {
196
+ nodes: [
197
+ {
198
+ id: 'metrics',
199
+ type: 'text',
200
+ x: 0,
201
+ y: 0,
202
+ width: 100,
203
+ height: 60,
204
+ text: 'Metrics',
205
+ color: '#4A90E2',
206
+ pv: { nodeType: 'data', shape: 'rectangle', icon: 'BarChart2' },
207
+ },
208
+ {
209
+ id: 'alert',
210
+ type: 'text',
211
+ x: 150,
212
+ y: 0,
213
+ width: 100,
214
+ height: 60,
215
+ text: 'Alert',
216
+ color: '#D0021B',
217
+ pv: { nodeType: 'process', shape: 'rectangle', icon: 'AlertTriangle' },
218
+ },
219
+ ],
220
+ edges: [
221
+ { id: 'e1', fromNode: 'metrics', toNode: 'alert', pv: { edgeType: 'flow' } },
222
+ ],
223
+ pv: {
224
+ version: '1.0.0',
225
+ name: 'Monitoring',
226
+ },
227
+ };
228
+
229
+ // ============================================================================
230
+ // Stories
231
+ // ============================================================================
232
+
233
+ /**
234
+ * Two canvases side by side on the same graph
235
+ */
236
+ const twoCanvasLayout: MultiCanvasLayout = {
237
+ placements: [
238
+ { canvasId: 'auth', canvas: authFlowCanvas, position: { x: 0, y: 0 } },
239
+ { canvasId: 'data', canvas: dataProcessingCanvas, position: { x: 600, y: 0 } },
240
+ ],
241
+ };
242
+
243
+ export const TwoCanvasesSideBySide: Story = {
244
+ args: {
245
+ layout: twoCanvasLayout,
246
+ showControls: true,
247
+ showBackground: true,
248
+ },
249
+ };
250
+
251
+ /**
252
+ * Four canvases arranged in a 2x2 grid pattern
253
+ */
254
+ const fourCanvasLayout: MultiCanvasLayout = {
255
+ placements: [
256
+ { canvasId: 'auth', canvas: authFlowCanvas, position: { x: 0, y: 0 } },
257
+ { canvasId: 'data', canvas: dataProcessingCanvas, position: { x: 600, y: 0 } },
258
+ { canvasId: 'notify', canvas: notificationCanvas, position: { x: 0, y: 200 } },
259
+ { canvasId: 'monitor', canvas: monitoringCanvas, position: { x: 600, y: 200 } },
260
+ ],
261
+ };
262
+
263
+ export const FourCanvasesGrid: Story = {
264
+ args: {
265
+ layout: fourCanvasLayout,
266
+ showControls: true,
267
+ showBackground: true,
268
+ },
269
+ };
270
+
271
+ /**
272
+ * Canvases stacked vertically
273
+ */
274
+ const verticalLayout: MultiCanvasLayout = {
275
+ placements: [
276
+ { canvasId: 'auth', canvas: authFlowCanvas, position: { x: 0, y: 0 } },
277
+ { canvasId: 'data', canvas: dataProcessingCanvas, position: { x: 0, y: 150 } },
278
+ { canvasId: 'notify', canvas: notificationCanvas, position: { x: 0, y: 300 } },
279
+ ],
280
+ };
281
+
282
+ export const VerticalStack: Story = {
283
+ args: {
284
+ layout: verticalLayout,
285
+ showControls: true,
286
+ showBackground: true,
287
+ },
288
+ };
289
+
290
+ /**
291
+ * Single canvas (baseline comparison)
292
+ */
293
+ const singleCanvasLayout: MultiCanvasLayout = {
294
+ placements: [
295
+ { canvasId: 'notify', canvas: notificationCanvas, position: { x: 0, y: 0 } },
296
+ ],
297
+ };
298
+
299
+ export const SingleCanvas: Story = {
300
+ args: {
301
+ layout: singleCanvasLayout,
302
+ showControls: true,
303
+ showBackground: true,
304
+ },
305
+ };
306
+
307
+ /**
308
+ * With minimap enabled to navigate the combined view
309
+ */
310
+ export const WithMinimap: Story = {
311
+ args: {
312
+ layout: fourCanvasLayout,
313
+ showControls: true,
314
+ showMinimap: true,
315
+ showBackground: true,
316
+ },
317
+ };
318
+
319
+ /**
320
+ * Without group borders - just the nodes merged together
321
+ */
322
+ export const WithoutGroups: Story = {
323
+ args: {
324
+ layout: fourCanvasLayout,
325
+ showControls: true,
326
+ showBackground: true,
327
+ showGroups: false,
328
+ },
329
+ };
@@ -0,0 +1,91 @@
1
+ import type { ExtendedCanvas } from '@principal-ai/principal-view-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
+ /**
59
+ * Calculate a recommended display size for a canvas cell.
60
+ * Adds padding and enforces minimum dimensions.
61
+ */
62
+ export function getCanvasDisplaySize(
63
+ canvas: ExtendedCanvas,
64
+ options: {
65
+ padding?: number;
66
+ minWidth?: number;
67
+ minHeight?: number;
68
+ maxWidth?: number;
69
+ maxHeight?: number;
70
+ } = {}
71
+ ): { width: number; height: number } {
72
+ const {
73
+ padding = 100,
74
+ minWidth = 300,
75
+ minHeight = 200,
76
+ maxWidth = 1200,
77
+ maxHeight = 800,
78
+ } = options;
79
+
80
+ const bounds = getCanvasBounds(canvas);
81
+
82
+ // Add padding to content dimensions
83
+ const contentWidth = bounds.width + padding * 2;
84
+ const contentHeight = bounds.height + padding * 2;
85
+
86
+ // Clamp to min/max
87
+ const width = Math.min(maxWidth, Math.max(minWidth, contentWidth));
88
+ const height = Math.min(maxHeight, Math.max(minHeight, contentHeight));
89
+
90
+ return { width, height };
91
+ }