@memberjunction/ng-skip-chat 2.69.1 → 2.70.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.
@@ -1,2012 +0,0 @@
1
- import { LogError } from '@memberjunction/core';
2
- import { BaseSingleton } from '@memberjunction/global';
3
- /**
4
- * CDN URLs for external dependencies
5
- * These can be configured via environment variables in the future
6
- */
7
- const CDN_URLS = {
8
- // Core React dependencies
9
- BABEL_STANDALONE: 'https://unpkg.com/@babel/standalone@7/babel.min.js',
10
- REACT: 'https://unpkg.com/react@18/umd/react.development.js',
11
- REACT_DOM: 'https://unpkg.com/react-dom@18/umd/react-dom.development.js',
12
- // Ant Design dependencies
13
- DAYJS: 'https://unpkg.com/dayjs@1.11.10/dayjs.min.js',
14
- // UI Libraries - Using UMD builds that work with global React
15
- ANTD_JS: 'https://unpkg.com/antd@5.12.8/dist/antd.js',
16
- ANTD_CSS: 'https://unpkg.com/antd@5.12.8/dist/reset.css',
17
- REACT_BOOTSTRAP_JS: 'https://unpkg.com/react-bootstrap@2.9.1/dist/react-bootstrap.js',
18
- BOOTSTRAP_CSS: 'https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css',
19
- // Data Visualization
20
- D3_JS: 'https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js',
21
- CHART_JS: 'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.0/chart.umd.js',
22
- // Utilities
23
- LODASH_JS: 'https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js'
24
- };
25
- /**
26
- * Global component registry service for managing reusable React components
27
- * Extends BaseSingleton to ensure a truly global singleton instance across
28
- * the entire application, even if this code is loaded multiple times.
29
- */
30
- export class GlobalComponentRegistry extends BaseSingleton {
31
- components = new Map();
32
- constructor() {
33
- super(); // Call parent constructor to register in global store
34
- }
35
- /**
36
- * Get the singleton instance
37
- */
38
- static get Instance() {
39
- return super.getInstance();
40
- }
41
- /**
42
- * Register a component with a simple key
43
- */
44
- register(key, component) {
45
- this.components.set(key, { component });
46
- }
47
- /**
48
- * Get a component by key
49
- */
50
- get(key) {
51
- const entry = this.components.get(key);
52
- return entry?.component;
53
- }
54
- /**
55
- * Register a component with metadata for versioning and context
56
- */
57
- registerWithMetadata(name, context, version, component, description) {
58
- const key = this.createKey(name, context, version);
59
- this.components.set(key, {
60
- component,
61
- metadata: { context, version, description }
62
- });
63
- // Also register without version for backwards compatibility
64
- const contextKey = `${name}_${context}`;
65
- if (!this.components.has(contextKey)) {
66
- this.register(contextKey, component);
67
- }
68
- }
69
- /**
70
- * Create a standardized key from component metadata
71
- */
72
- createKey(name, context, version) {
73
- return `${name}_${context}_${version}`;
74
- }
75
- /**
76
- * Get all registered component keys (useful for debugging)
77
- */
78
- getRegisteredKeys() {
79
- return Array.from(this.components.keys());
80
- }
81
- /**
82
- * Clear all registered components
83
- */
84
- clear() {
85
- this.components.clear();
86
- }
87
- /**
88
- * Check if a component is registered
89
- */
90
- has(key) {
91
- return this.components.has(key);
92
- }
93
- /**
94
- * Get component with fallback options
95
- */
96
- getWithFallback(name, context, version) {
97
- // Try exact match first
98
- let key = this.createKey(name, context, version);
99
- if (this.has(key)) {
100
- return this.get(key);
101
- }
102
- // Try without version
103
- key = `${name}_${context}`;
104
- if (this.has(key)) {
105
- return this.get(key);
106
- }
107
- // Try global version
108
- key = `${name}_Global`;
109
- if (this.has(key)) {
110
- return this.get(key);
111
- }
112
- // Try just the name
113
- if (this.has(name)) {
114
- return this.get(name);
115
- }
116
- return null;
117
- }
118
- /**
119
- * Remove a component from the registry
120
- */
121
- remove(key) {
122
- this.components.delete(key);
123
- }
124
- }
125
- /**
126
- * Default styles that match the Skip design system
127
- */
128
- const DEFAULT_STYLES = {
129
- colors: {
130
- // Primary colors - modern purple/blue gradient feel
131
- primary: '#5B4FE9',
132
- primaryHover: '#4940D4',
133
- primaryLight: '#E8E6FF',
134
- // Secondary colors - sophisticated gray
135
- secondary: '#64748B',
136
- secondaryHover: '#475569',
137
- // Status colors
138
- success: '#10B981',
139
- successLight: '#D1FAE5',
140
- warning: '#F59E0B',
141
- warningLight: '#FEF3C7',
142
- error: '#EF4444',
143
- errorLight: '#FEE2E2',
144
- info: '#3B82F6',
145
- infoLight: '#DBEAFE',
146
- // Base colors
147
- background: '#FFFFFF',
148
- surface: '#F8FAFC',
149
- surfaceHover: '#F1F5F9',
150
- // Text colors with better contrast
151
- text: '#1E293B',
152
- textSecondary: '#64748B',
153
- textTertiary: '#94A3B8',
154
- textInverse: '#FFFFFF',
155
- // Border colors
156
- border: '#E2E8F0',
157
- borderLight: '#F1F5F9',
158
- borderFocus: '#5B4FE9',
159
- // Shadows (as color strings for easy use)
160
- shadow: 'rgba(0, 0, 0, 0.05)',
161
- shadowMedium: 'rgba(0, 0, 0, 0.1)',
162
- shadowLarge: 'rgba(0, 0, 0, 0.15)',
163
- },
164
- spacing: {
165
- xs: '4px',
166
- sm: '8px',
167
- md: '16px',
168
- lg: '24px',
169
- xl: '32px',
170
- xxl: '48px',
171
- xxxl: '64px',
172
- },
173
- typography: {
174
- fontFamily: '-apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", Roboto, sans-serif',
175
- fontSize: {
176
- xs: '11px',
177
- sm: '12px',
178
- md: '14px',
179
- lg: '16px',
180
- xl: '20px',
181
- xxl: '24px',
182
- xxxl: '32px',
183
- },
184
- fontWeight: {
185
- light: '300',
186
- regular: '400',
187
- medium: '500',
188
- semibold: '600',
189
- bold: '700',
190
- },
191
- lineHeight: {
192
- tight: '1.25',
193
- normal: '1.5',
194
- relaxed: '1.75',
195
- },
196
- },
197
- borders: {
198
- radius: {
199
- sm: '6px',
200
- md: '8px',
201
- lg: '12px',
202
- xl: '16px',
203
- full: '9999px',
204
- },
205
- width: {
206
- thin: '1px',
207
- medium: '2px',
208
- thick: '3px',
209
- },
210
- },
211
- shadows: {
212
- sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
213
- md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
214
- lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
215
- xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',
216
- inner: 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)',
217
- },
218
- transitions: {
219
- fast: '150ms ease-in-out',
220
- normal: '250ms ease-in-out',
221
- slow: '350ms ease-in-out',
222
- },
223
- overflow: 'auto'
224
- };
225
- /**
226
- * Host class for integrating Skip-generated React components into Angular applications.
227
- * This class handles the lifecycle management, state synchronization, and communication
228
- * between React components and their Angular host containers.
229
- */
230
- export class SkipReactComponentHost {
231
- config;
232
- componentResult = null;
233
- reactRoot = null;
234
- componentContainer = null;
235
- destroyed = false;
236
- currentState = {};
237
- // React and ReactDOM references (will be loaded dynamically)
238
- React;
239
- ReactDOM;
240
- // Static style system that's created once and reused
241
- static cachedStyleSystem = null;
242
- // Static cached libraries
243
- static cachedLibraries = null;
244
- static libraryLoadPromise = null;
245
- constructor(config) {
246
- this.config = config;
247
- // Auto-populate metadata if not provided
248
- if (!this.config.metadata) {
249
- const childComponentNames = (this.config.component.childComponents || []).map((child) => child.componentName);
250
- this.config.metadata = {
251
- requiredChildComponents: childComponentNames,
252
- componentContext: 'Global',
253
- version: 'v1'
254
- };
255
- }
256
- }
257
- /**
258
- * Register all components in the hierarchy before initialization
259
- * This ensures all child components are available in the registry
260
- */
261
- async registerComponentHierarchy() {
262
- const errors = [];
263
- const registeredComponents = [];
264
- try {
265
- // Get React context for registration
266
- const reactContext = {
267
- React: this.React,
268
- ReactDOM: this.ReactDOM,
269
- libraries: {} // Libraries will be loaded later
270
- };
271
- // Register root component
272
- const rootComponentName = this.config.component.componentName;
273
- const success = await compileAndRegisterComponent(rootComponentName, this.config.component.componentCode, this.config.metadata?.componentContext || 'Global', this.config.metadata?.version || 'v1', reactContext);
274
- if (success) {
275
- registeredComponents.push(rootComponentName);
276
- }
277
- else {
278
- errors.push(`Failed to register root component: ${rootComponentName}`);
279
- }
280
- // Recursively register child components if they exist
281
- if (this.config.component.childComponents && Array.isArray(this.config.component.childComponents)) {
282
- for (const child of this.config.component.childComponents) {
283
- await this.registerChildComponent(child, registeredComponents, errors, reactContext);
284
- }
285
- }
286
- return {
287
- success: errors.length === 0,
288
- registeredComponents,
289
- errors
290
- };
291
- }
292
- catch (error) {
293
- errors.push(`Error processing component hierarchy: ${error.message}`);
294
- return {
295
- success: false,
296
- registeredComponents,
297
- errors
298
- };
299
- }
300
- }
301
- /**
302
- * Recursively register child components
303
- */
304
- async registerChildComponent(spec, registeredComponents, errors, reactContext) {
305
- try {
306
- if (spec.componentCode) {
307
- const success = await compileAndRegisterComponent(spec.componentName, spec.componentCode, this.config.metadata?.componentContext || 'Global', this.config.metadata?.version || 'v1', reactContext);
308
- if (success) {
309
- registeredComponents.push(spec.componentName);
310
- }
311
- else {
312
- errors.push(`Failed to register component: ${spec.componentName}`);
313
- }
314
- }
315
- // Process nested children
316
- const childArray = spec.components || [];
317
- for (const child of childArray) {
318
- await this.registerChildComponent(child, registeredComponents, errors, reactContext);
319
- }
320
- }
321
- catch (error) {
322
- errors.push(`Error registering ${spec.componentName}: ${error.message}`);
323
- }
324
- }
325
- /**
326
- * Create a plain JavaScript object containing only the components needed by the generated component
327
- */
328
- createComponentsObject() {
329
- if (!this.config.metadata?.requiredChildComponents) {
330
- return {}; // No child components required
331
- }
332
- const registry = GlobalComponentRegistry.Instance;
333
- const components = {};
334
- const missingComponents = [];
335
- console.log('Creating components object. Required:', this.config.metadata.requiredChildComponents);
336
- console.log('Available components in registry:', registry.getRegisteredKeys());
337
- for (const childName of this.config.metadata.requiredChildComponents) {
338
- // Try to resolve the component with metadata context
339
- const component = registry.getWithFallback(childName, this.config.metadata.componentContext, this.config.metadata.version);
340
- if (component) {
341
- components[childName] = component;
342
- console.log(`Found component "${childName}"`);
343
- }
344
- else {
345
- console.warn(`Component "${childName}" not found in registry. Tried contexts: ${this.config.metadata.componentContext}, Global`);
346
- missingComponents.push(childName);
347
- }
348
- }
349
- // If any required components are missing, throw a descriptive error
350
- if (missingComponents.length > 0) {
351
- const errorMessage = `Missing required child components: ${missingComponents.join(', ')}. ` +
352
- `This usually means the component specification is incomplete or child components failed to compile.`;
353
- throw new Error(errorMessage);
354
- }
355
- return components;
356
- }
357
- /**
358
- * Load React and ReactDOM dynamically
359
- */
360
- async loadReactLibraries() {
361
- // Check if React is already loaded globally
362
- if (window.React && window.ReactDOM) {
363
- this.React = window.React;
364
- this.ReactDOM = window.ReactDOM;
365
- return;
366
- }
367
- // Load React from CDN to ensure it's available globally for other libraries
368
- try {
369
- // Load React first
370
- await this.loadScriptFromCDN(CDN_URLS.REACT, 'React');
371
- // Then load ReactDOM (it depends on React being available)
372
- await this.loadScriptFromCDN(CDN_URLS.REACT_DOM, 'ReactDOM');
373
- this.React = window.React;
374
- this.ReactDOM = window.ReactDOM;
375
- // Verify they loaded correctly
376
- if (!this.React || !this.ReactDOM) {
377
- throw new Error('React and ReactDOM failed to load from CDN');
378
- }
379
- }
380
- catch (error) {
381
- // Try ES modules as fallback
382
- try {
383
- const [ReactModule, ReactDOMModule] = await Promise.all([
384
- import('react'),
385
- import('react-dom/client')
386
- ]);
387
- this.React = ReactModule;
388
- this.ReactDOM = ReactDOMModule;
389
- // Also set them globally for other libraries
390
- window.React = ReactModule;
391
- window.ReactDOM = ReactDOMModule;
392
- }
393
- catch (moduleError) {
394
- throw new Error('Failed to load React and ReactDOM from any source');
395
- }
396
- }
397
- }
398
- /**
399
- * Generic method to load a script from CDN
400
- */
401
- loadScriptFromCDN(url, globalName) {
402
- return new Promise((resolve, reject) => {
403
- // Check if already loaded
404
- if (window[globalName]) {
405
- resolve(window[globalName]);
406
- return;
407
- }
408
- // Check if script is already in DOM
409
- const existingScript = document.querySelector(`script[src="${url}"]`);
410
- if (existingScript) {
411
- // Wait for it to load
412
- existingScript.addEventListener('load', () => {
413
- if (window[globalName]) {
414
- resolve(window[globalName]);
415
- }
416
- else {
417
- reject(new Error(`${globalName} not found after script load`));
418
- }
419
- });
420
- return;
421
- }
422
- // Load new script
423
- const script = document.createElement('script');
424
- script.src = url;
425
- script.onload = () => {
426
- if (window[globalName]) {
427
- resolve(window[globalName]);
428
- }
429
- else {
430
- reject(new Error(`${globalName} not found after script load`));
431
- }
432
- };
433
- script.onerror = () => reject(new Error(`Failed to load script: ${url}`));
434
- document.head.appendChild(script);
435
- });
436
- }
437
- /**
438
- * Load Babel standalone for JSX transpilation
439
- */
440
- async loadBabel() {
441
- return this.loadScriptFromCDN(CDN_URLS.BABEL_STANDALONE, 'Babel');
442
- }
443
- /**
444
- * Load a CSS file from CDN
445
- */
446
- loadCSS(url) {
447
- // Check if CSS is already loaded
448
- const existingLink = document.querySelector(`link[href="${url}"]`);
449
- if (existingLink) {
450
- return;
451
- }
452
- const link = document.createElement('link');
453
- link.rel = 'stylesheet';
454
- link.href = url;
455
- document.head.appendChild(link);
456
- }
457
- /**
458
- * Load a script from CDN with promise
459
- */
460
- loadScript(url, globalName) {
461
- return this.loadScriptFromCDN(url, globalName);
462
- }
463
- /**
464
- * Load common UI and utility libraries
465
- */
466
- async loadCommonLibraries() {
467
- // If already cached, return immediately
468
- if (SkipReactComponentHost.cachedLibraries) {
469
- return SkipReactComponentHost.cachedLibraries;
470
- }
471
- // If currently loading, wait for the existing promise
472
- if (SkipReactComponentHost.libraryLoadPromise) {
473
- return SkipReactComponentHost.libraryLoadPromise;
474
- }
475
- // Start loading libraries
476
- SkipReactComponentHost.libraryLoadPromise = this.doLoadLibraries();
477
- try {
478
- SkipReactComponentHost.cachedLibraries = await SkipReactComponentHost.libraryLoadPromise;
479
- return SkipReactComponentHost.cachedLibraries;
480
- }
481
- finally {
482
- SkipReactComponentHost.libraryLoadPromise = null;
483
- }
484
- }
485
- /**
486
- * Actually load the libraries
487
- */
488
- async doLoadLibraries() {
489
- // Load CSS files first (these don't need to be awaited)
490
- this.loadCSS(CDN_URLS.ANTD_CSS);
491
- this.loadCSS(CDN_URLS.BOOTSTRAP_CSS);
492
- // Load base dependencies first
493
- const [dayjs, _, d3, Chart] = await Promise.all([
494
- this.loadScript(CDN_URLS.DAYJS, 'dayjs'),
495
- this.loadScript(CDN_URLS.LODASH_JS, '_'),
496
- this.loadScript(CDN_URLS.D3_JS, 'd3'),
497
- this.loadScript(CDN_URLS.CHART_JS, 'Chart')
498
- ]);
499
- // Then load UI libraries that depend on React
500
- const [antd, ReactBootstrap] = await Promise.all([
501
- this.loadScript(CDN_URLS.ANTD_JS, 'antd'),
502
- this.loadScript(CDN_URLS.REACT_BOOTSTRAP_JS, 'ReactBootstrap')
503
- ]);
504
- return {
505
- antd,
506
- ReactBootstrap,
507
- d3,
508
- Chart,
509
- _,
510
- dayjs
511
- };
512
- }
513
- /**
514
- * Initialize the React component
515
- */
516
- async initialize() {
517
- try {
518
- // Step 1: Load React and ReactDOM first
519
- await this.loadReactLibraries();
520
- // Step 2: Load Babel (needed for JSX transpilation during component registration)
521
- const Babel = await this.loadBabel();
522
- // Step 3: Now we can register components (React and Babel are both loaded)
523
- const registrationResult = await this.registerComponentHierarchy();
524
- if (!registrationResult.success) {
525
- const errorMessage = `Failed to register components: ${registrationResult.errors.join(', ')}`;
526
- LogError(errorMessage);
527
- if (this.config.callbacks?.NotifyEvent) {
528
- this.config.callbacks.NotifyEvent('componentError', {
529
- error: errorMessage,
530
- source: 'Component Registration'
531
- });
532
- }
533
- throw new Error(errorMessage);
534
- }
535
- console.log('Successfully registered components:', registrationResult.registeredComponents);
536
- // Step 4: Load common libraries
537
- const libraries = await this.loadCommonLibraries();
538
- // Register example components if needed (for testing)
539
- if (this.config.metadata?.requiredChildComponents?.length) {
540
- // Check if we need to register example components
541
- const registry = GlobalComponentRegistry.Instance;
542
- const hasComponents = this.config.metadata.requiredChildComponents.every(name => registry.getWithFallback(name, this.config.metadata.componentContext, this.config.metadata.version));
543
- if (!hasComponents && typeof window.registerExampleComponents === 'function') {
544
- // Try to register example components
545
- window.registerExampleComponents(this.React, libraries.Chart);
546
- }
547
- }
548
- // Create utility functions
549
- const createStateUpdater = this.createStateUpdaterFunction();
550
- const createStandardEventHandler = this.createStandardEventHandlerFunction();
551
- // Get or create the style system
552
- const styles = this.getOrCreateStyleSystem();
553
- // Transpile the JSX code to JavaScript
554
- let transpiledCode;
555
- try {
556
- const wrappedCode = generateComponentWrapper(this.config.component.componentCode, this.config.component.componentName);
557
- const result = Babel.transform(wrappedCode, {
558
- presets: ['react'],
559
- filename: 'component.jsx'
560
- });
561
- transpiledCode = result.code;
562
- }
563
- catch (transpileError) {
564
- LogError(`Failed to transpile JSX: ${transpileError}`);
565
- if (this.config.callbacks?.NotifyEvent) {
566
- this.config.callbacks.NotifyEvent('componentError', {
567
- error: `JSX transpilation failed: ${transpileError}`,
568
- source: 'JSX Transpilation'
569
- });
570
- }
571
- throw new Error(`JSX transpilation failed: ${transpileError}`);
572
- }
573
- // Create the component factory function from the transpiled code
574
- // Always pass all libraries - unused ones will just be undefined in older components
575
- let createComponent;
576
- try {
577
- createComponent = new Function('React', 'styles', 'console', 'antd', 'ReactBootstrap', 'd3', 'Chart', '_', 'dayjs', `${transpiledCode}; return createComponent;`)(this.React, styles, console, libraries.antd, libraries.ReactBootstrap, libraries.d3, libraries.Chart, libraries._, libraries.dayjs);
578
- }
579
- catch (evalError) {
580
- LogError(`Failed to evaluate component code: ${evalError}`);
581
- console.error('Component code evaluation error:', evalError);
582
- console.error('Transpiled code:', transpiledCode);
583
- if (this.config.callbacks?.NotifyEvent) {
584
- this.config.callbacks.NotifyEvent('componentError', {
585
- error: `Component code evaluation failed: ${evalError}`,
586
- source: 'Code Evaluation'
587
- });
588
- }
589
- throw new Error(`Component code evaluation failed: ${evalError}`);
590
- }
591
- // Debug: Check if React hooks are available
592
- if (!this.React.useState) {
593
- console.error('React.useState is not available. React object:', this.React);
594
- if (this.config.callbacks?.NotifyEvent) {
595
- this.config.callbacks.NotifyEvent('componentError', {
596
- error: 'React hooks are not available. Make sure React is loaded correctly.',
597
- source: 'React Initialization'
598
- });
599
- }
600
- throw new Error('React hooks are not available. Make sure React is loaded correctly.');
601
- }
602
- // Call createComponent with all parameters - older components will just ignore the extra libraries parameter
603
- try {
604
- this.componentResult = createComponent(this.React, this.ReactDOM, this.React.useState, this.React.useEffect, this.React.useCallback, createStateUpdater, createStandardEventHandler, libraries);
605
- }
606
- catch (factoryError) {
607
- LogError(`Component factory failed: ${factoryError}`);
608
- if (this.config.callbacks?.NotifyEvent) {
609
- this.config.callbacks.NotifyEvent('componentError', {
610
- error: `Component factory failed: ${factoryError}`,
611
- source: 'Component Factory'
612
- });
613
- }
614
- throw factoryError;
615
- }
616
- // Create container if it doesn't exist
617
- if (!this.componentContainer) {
618
- this.componentContainer = document.createElement('div');
619
- this.componentContainer.className = 'react-component-container';
620
- this.componentContainer.style.width = '100%';
621
- this.componentContainer.style.height = '100%';
622
- this.config.container.appendChild(this.componentContainer);
623
- }
624
- // Store initial state
625
- this.currentState = this.config.initialState || {};
626
- // Render the component
627
- this.render();
628
- }
629
- catch (error) {
630
- LogError(error);
631
- if (this.config.callbacks?.NotifyEvent) {
632
- this.config.callbacks.NotifyEvent('componentError', {
633
- error: String(error),
634
- source: 'Component Initialization'
635
- });
636
- }
637
- }
638
- }
639
- /**
640
- * Create an error boundary component
641
- */
642
- createErrorBoundary() {
643
- const React = this.React;
644
- class ErrorBoundary extends React.Component {
645
- constructor(props) {
646
- super(props);
647
- this.state = {
648
- hasError: false,
649
- error: null,
650
- errorInfo: null
651
- };
652
- }
653
- static getDerivedStateFromError(_error) {
654
- return { hasError: true };
655
- }
656
- componentDidCatch(error, errorInfo) {
657
- console.error('React Error Boundary caught:', error, errorInfo);
658
- // Bubble error up to Angular
659
- const host = this.props.host;
660
- if (host?.config?.callbacks?.NotifyEvent) {
661
- host.config.callbacks.NotifyEvent('componentError', {
662
- error: error?.toString() || 'Unknown error',
663
- errorInfo: errorInfo,
664
- stackTrace: errorInfo?.componentStack,
665
- source: 'React Error Boundary'
666
- });
667
- }
668
- // Set state to prevent re-rendering the broken component
669
- this.setState({
670
- error: error,
671
- errorInfo: errorInfo
672
- });
673
- }
674
- render() {
675
- if (this.state.hasError) {
676
- // Just return an empty div - Angular will show the error
677
- return React.createElement('div', {
678
- style: {
679
- display: 'none'
680
- }
681
- });
682
- }
683
- return this.props.children;
684
- }
685
- }
686
- return ErrorBoundary;
687
- }
688
- /**
689
- * Render or re-render the React component with new props
690
- */
691
- render() {
692
- if (!this.componentResult || !this.componentResult.component || this.destroyed) {
693
- return;
694
- }
695
- const Component = this.componentResult.component;
696
- const ErrorBoundary = this.createErrorBoundary();
697
- // Ensure utilities and callbacks are available
698
- const utilities = this.config.utilities || {};
699
- const callbacks = this.createCallbacks();
700
- const styles = this.getOrCreateStyleSystem();
701
- const componentProps = {
702
- data: this.config.data || {},
703
- utilities: utilities,
704
- userState: this.currentState,
705
- callbacks: callbacks,
706
- styles: styles,
707
- components: this.createComponentsObject() // Add the filtered components object
708
- };
709
- // Debug: Log the data being passed to the component
710
- console.log('=== SkipReactComponentHost: Rendering component ===');
711
- console.log('Data:', componentProps.data);
712
- console.log('User state:', componentProps.userState);
713
- console.log('Components:', Object.keys(componentProps.components));
714
- console.log('=== End component props debug ===');
715
- if (!this.reactRoot && this.componentContainer) {
716
- this.reactRoot = this.ReactDOM.createRoot(this.componentContainer);
717
- }
718
- if (this.reactRoot) {
719
- try {
720
- // Wrap component in error boundary, passing host reference
721
- const wrappedElement = this.React.createElement(ErrorBoundary, { host: this }, this.React.createElement(Component, componentProps));
722
- // Set a timeout to prevent infinite loops from freezing the browser
723
- const renderTimeout = setTimeout(() => {
724
- console.error('Component render timeout - possible infinite loop detected');
725
- if (this.config.callbacks?.NotifyEvent) {
726
- this.config.callbacks.NotifyEvent('componentError', {
727
- error: 'Component render timeout - possible infinite loop or heavy computation detected',
728
- source: 'Render Timeout'
729
- });
730
- }
731
- }, 5000); // 5 second timeout
732
- this.reactRoot.render(wrappedElement);
733
- // Clear timeout if render completes
734
- clearTimeout(renderTimeout);
735
- }
736
- catch (renderError) {
737
- console.error('Failed to render React component:', renderError);
738
- console.error('Component:', Component);
739
- console.error('Props:', componentProps);
740
- if (this.config.callbacks?.NotifyEvent) {
741
- this.config.callbacks.NotifyEvent('componentError', {
742
- error: `Component render failed: ${renderError}`,
743
- source: 'React Render'
744
- });
745
- }
746
- }
747
- }
748
- }
749
- /**
750
- * Update the component state
751
- */
752
- updateState(path, value) {
753
- // Update the current state
754
- this.currentState = {
755
- ...this.currentState,
756
- [path]: value
757
- };
758
- // Re-render with new state
759
- this.render();
760
- }
761
- /**
762
- * Update the component data
763
- */
764
- updateData(newData) {
765
- this.config.data = {
766
- ...this.config.data,
767
- ...newData
768
- };
769
- // Re-render with new data
770
- this.render();
771
- }
772
- /**
773
- * Refresh the component with optional new data
774
- */
775
- refresh(newData) {
776
- if (newData) {
777
- this.updateData(newData);
778
- }
779
- if (this.componentResult && this.componentResult.refresh) {
780
- this.componentResult.refresh(this.config.data);
781
- }
782
- else {
783
- // Re-render the component if no refresh method is available
784
- this.render();
785
- }
786
- }
787
- /**
788
- * Print the component
789
- */
790
- print() {
791
- if (this.componentResult && this.componentResult.print) {
792
- this.componentResult.print();
793
- }
794
- else {
795
- window.print();
796
- }
797
- }
798
- /**
799
- * Clean up resources
800
- */
801
- destroy() {
802
- this.destroyed = true;
803
- if (this.reactRoot) {
804
- this.reactRoot.unmount();
805
- this.reactRoot = null;
806
- }
807
- if (this.componentContainer && this.componentContainer.parentNode) {
808
- this.componentContainer.parentNode.removeChild(this.componentContainer);
809
- }
810
- this.componentContainer = null;
811
- this.componentResult = null;
812
- }
813
- /**
814
- * Create the callbacks object to pass to the React component
815
- */
816
- createCallbacks() {
817
- return this.config.callbacks || {
818
- RefreshData: () => { },
819
- OpenEntityRecord: () => { },
820
- UpdateUserState: () => { },
821
- NotifyEvent: () => { }
822
- };
823
- }
824
- /**
825
- * Get or create the cached style system
826
- */
827
- getOrCreateStyleSystem() {
828
- // If we already have a cached style system, return it
829
- if (SkipReactComponentHost.cachedStyleSystem) {
830
- return SkipReactComponentHost.cachedStyleSystem;
831
- }
832
- // Create the style system by merging defaults with config
833
- SkipReactComponentHost.cachedStyleSystem = this.createStyleSystem(this.config.styles);
834
- return SkipReactComponentHost.cachedStyleSystem;
835
- }
836
- /**
837
- * Create a unified style system for the component
838
- */
839
- createStyleSystem(baseStyles) {
840
- return {
841
- ...DEFAULT_STYLES,
842
- ...baseStyles
843
- };
844
- }
845
- /**
846
- * Create the state updater utility function for the React component
847
- */
848
- createStateUpdaterFunction() {
849
- return function createStateUpdater(statePath, parentStateUpdater) {
850
- return (componentStateUpdate) => {
851
- if (!statePath) {
852
- // Root component - call container callback directly
853
- parentStateUpdater(componentStateUpdate);
854
- }
855
- else {
856
- // Sub-component - bubble up with path context
857
- const pathParts = statePath.split('.');
858
- const componentKey = pathParts[pathParts.length - 1];
859
- parentStateUpdater({
860
- [componentKey]: {
861
- ...componentStateUpdate
862
- }
863
- });
864
- }
865
- };
866
- };
867
- }
868
- /**
869
- * Create the standard event handler utility function for the React component
870
- */
871
- createStandardEventHandlerFunction() {
872
- return function createStandardEventHandler(updateUserState, callbacksParam) {
873
- return (event) => {
874
- switch (event.type) {
875
- case 'stateChanged':
876
- if (event.payload?.statePath && event.payload?.newState) {
877
- const update = {};
878
- update[event.payload.statePath] = event.payload.newState;
879
- updateUserState(update);
880
- }
881
- break;
882
- case 'navigate':
883
- if (callbacksParam?.OpenEntityRecord && event.payload) {
884
- callbacksParam.OpenEntityRecord(event.payload.entityName, event.payload.key);
885
- }
886
- break;
887
- default:
888
- if (callbacksParam?.NotifyEvent) {
889
- callbacksParam.NotifyEvent(event.type, event.payload);
890
- }
891
- }
892
- };
893
- };
894
- }
895
- }
896
- /**
897
- * Example child components for testing the component registry system
898
- * These would normally be defined in separate files and imported
899
- */
900
- // Example SearchBox component
901
- export const createSearchBoxComponent = (React) => {
902
- return function SearchBox({ data, config, state, onEvent, styles, statePath }) {
903
- const [searchValue, setSearchValue] = React.useState(state?.searchValue || '');
904
- const handleSearch = (value) => {
905
- setSearchValue(value);
906
- if (onEvent) {
907
- onEvent({
908
- type: 'stateChanged',
909
- payload: {
910
- statePath: statePath,
911
- newState: { searchValue: value }
912
- }
913
- });
914
- }
915
- };
916
- return React.createElement('div', {
917
- style: {
918
- padding: styles?.spacing?.md || '16px',
919
- backgroundColor: styles?.colors?.surface || '#f8f9fa',
920
- borderRadius: styles?.borders?.radius?.md || '8px',
921
- marginBottom: styles?.spacing?.md || '16px'
922
- }
923
- }, [
924
- React.createElement('input', {
925
- key: 'search-input',
926
- type: 'text',
927
- value: searchValue,
928
- onChange: (e) => handleSearch(e.target.value),
929
- placeholder: config?.placeholder || 'Search...',
930
- style: {
931
- width: '100%',
932
- padding: styles?.spacing?.sm || '8px',
933
- border: `1px solid ${styles?.colors?.border || '#dee2e6'}`,
934
- borderRadius: styles?.borders?.radius?.sm || '4px',
935
- fontSize: styles?.typography?.fontSize?.md || '14px',
936
- fontFamily: styles?.typography?.fontFamily || 'inherit'
937
- }
938
- })
939
- ]);
940
- };
941
- };
942
- // Example OrderList component
943
- export const createOrderListComponent = (React) => {
944
- return function OrderList({ data, config, state, onEvent, styles, statePath }) {
945
- const [sortBy, setSortBy] = React.useState(state?.sortBy || 'date');
946
- const [currentPage, setCurrentPage] = React.useState(state?.currentPage || 1);
947
- const orders = data || [];
948
- const pageSize = config?.pageSize || 10;
949
- const totalPages = Math.ceil(orders.length / pageSize);
950
- const updateState = (newState) => {
951
- if (onEvent) {
952
- onEvent({
953
- type: 'stateChanged',
954
- payload: {
955
- statePath: statePath,
956
- newState: { ...state, ...newState }
957
- }
958
- });
959
- }
960
- };
961
- const handleSort = (field) => {
962
- setSortBy(field);
963
- updateState({ sortBy: field });
964
- };
965
- const handlePageChange = (page) => {
966
- setCurrentPage(page);
967
- updateState({ currentPage: page });
968
- };
969
- // Simple pagination
970
- const startIndex = (currentPage - 1) * pageSize;
971
- const endIndex = startIndex + pageSize;
972
- const displayedOrders = orders.slice(startIndex, endIndex);
973
- return React.createElement('div', {
974
- style: {
975
- backgroundColor: styles?.colors?.background || '#ffffff',
976
- border: `1px solid ${styles?.colors?.border || '#dee2e6'}`,
977
- borderRadius: styles?.borders?.radius?.md || '8px',
978
- padding: styles?.spacing?.lg || '24px'
979
- }
980
- }, [
981
- // Header
982
- React.createElement('div', {
983
- key: 'header',
984
- style: {
985
- display: 'flex',
986
- justifyContent: 'space-between',
987
- alignItems: 'center',
988
- marginBottom: styles?.spacing?.md || '16px'
989
- }
990
- }, [
991
- React.createElement('h3', {
992
- key: 'title',
993
- style: {
994
- margin: 0,
995
- fontSize: styles?.typography?.fontSize?.lg || '16px',
996
- fontWeight: styles?.typography?.fontWeight?.semibold || '600'
997
- }
998
- }, 'Orders'),
999
- config?.sortable && React.createElement('select', {
1000
- key: 'sort',
1001
- value: sortBy,
1002
- onChange: (e) => handleSort(e.target.value),
1003
- style: {
1004
- padding: styles?.spacing?.sm || '8px',
1005
- border: `1px solid ${styles?.colors?.border || '#dee2e6'}`,
1006
- borderRadius: styles?.borders?.radius?.sm || '4px'
1007
- }
1008
- }, [
1009
- React.createElement('option', { key: 'date', value: 'date' }, 'Sort by Date'),
1010
- React.createElement('option', { key: 'amount', value: 'amount' }, 'Sort by Amount'),
1011
- React.createElement('option', { key: 'status', value: 'status' }, 'Sort by Status')
1012
- ])
1013
- ]),
1014
- // List
1015
- React.createElement('div', {
1016
- key: 'list',
1017
- style: { marginBottom: styles?.spacing?.md || '16px' }
1018
- }, displayedOrders.length > 0 ? displayedOrders.map((order, index) => React.createElement('div', {
1019
- key: order.id || index,
1020
- style: {
1021
- padding: styles?.spacing?.md || '16px',
1022
- borderBottom: `1px solid ${styles?.colors?.borderLight || '#f1f5f9'}`,
1023
- cursor: 'pointer'
1024
- },
1025
- onClick: () => onEvent && onEvent({
1026
- type: 'navigate',
1027
- payload: { entityName: 'Orders', key: order.id }
1028
- })
1029
- }, [
1030
- React.createElement('div', {
1031
- key: 'order-number',
1032
- style: { fontWeight: styles?.typography?.fontWeight?.medium || '500' }
1033
- }, `Order #${order.orderNumber || order.id}`),
1034
- React.createElement('div', {
1035
- key: 'order-details',
1036
- style: {
1037
- fontSize: styles?.typography?.fontSize?.sm || '12px',
1038
- color: styles?.colors?.textSecondary || '#6c757d'
1039
- }
1040
- }, `$${order.amount || 0} - ${order.status || 'Pending'}`)
1041
- ])) : React.createElement('div', {
1042
- style: {
1043
- textAlign: 'center',
1044
- padding: styles?.spacing?.xl || '32px',
1045
- color: styles?.colors?.textSecondary || '#6c757d'
1046
- }
1047
- }, 'No orders found')),
1048
- // Pagination
1049
- totalPages > 1 && React.createElement('div', {
1050
- key: 'pagination',
1051
- style: {
1052
- display: 'flex',
1053
- justifyContent: 'center',
1054
- gap: styles?.spacing?.sm || '8px'
1055
- }
1056
- }, Array.from({ length: totalPages }, (_, i) => i + 1).map(page => React.createElement('button', {
1057
- key: page,
1058
- onClick: () => handlePageChange(page),
1059
- style: {
1060
- padding: `${styles?.spacing?.xs || '4px'} ${styles?.spacing?.sm || '8px'}`,
1061
- border: `1px solid ${styles?.colors?.border || '#dee2e6'}`,
1062
- borderRadius: styles?.borders?.radius?.sm || '4px',
1063
- backgroundColor: page === currentPage ? styles?.colors?.primary || '#5B4FE9' : 'transparent',
1064
- color: page === currentPage ? styles?.colors?.textInverse || '#ffffff' : styles?.colors?.text || '#212529',
1065
- cursor: 'pointer'
1066
- }
1067
- }, page)))
1068
- ]);
1069
- };
1070
- };
1071
- // Example CategoryChart component
1072
- export const createCategoryChartComponent = (React, Chart) => {
1073
- return function CategoryChart({ data, config, state, onEvent, styles, statePath }) {
1074
- const chartRef = React.useRef(null);
1075
- const chartInstanceRef = React.useRef(null);
1076
- React.useEffect(() => {
1077
- if (!chartRef.current || !Chart)
1078
- return;
1079
- // Destroy existing chart
1080
- if (chartInstanceRef.current) {
1081
- chartInstanceRef.current.destroy();
1082
- }
1083
- // Create new chart
1084
- const ctx = chartRef.current.getContext('2d');
1085
- chartInstanceRef.current = new Chart(ctx, {
1086
- type: 'bar',
1087
- data: {
1088
- labels: data?.map((item) => item.category) || [],
1089
- datasets: [{
1090
- label: 'Sales by Category',
1091
- data: data?.map((item) => item.value) || [],
1092
- backgroundColor: styles?.colors?.primary || '#5B4FE9',
1093
- borderColor: styles?.colors?.primaryHover || '#4940D4',
1094
- borderWidth: 1
1095
- }]
1096
- },
1097
- options: {
1098
- responsive: true,
1099
- maintainAspectRatio: false,
1100
- plugins: {
1101
- legend: {
1102
- display: config?.showLegend !== false
1103
- },
1104
- tooltip: {
1105
- callbacks: {
1106
- label: (context) => {
1107
- return `$${context.parsed.y.toLocaleString()}`;
1108
- }
1109
- }
1110
- }
1111
- },
1112
- scales: {
1113
- y: {
1114
- beginAtZero: true,
1115
- ticks: {
1116
- callback: (value) => `$${value.toLocaleString()}`
1117
- }
1118
- }
1119
- },
1120
- onClick: (event, elements) => {
1121
- if (elements.length > 0 && onEvent) {
1122
- const index = elements[0].index;
1123
- const category = data[index];
1124
- onEvent({
1125
- type: 'chartClick',
1126
- payload: { category: category.category, value: category.value }
1127
- });
1128
- }
1129
- }
1130
- }
1131
- });
1132
- return () => {
1133
- if (chartInstanceRef.current) {
1134
- chartInstanceRef.current.destroy();
1135
- }
1136
- };
1137
- }, [data, config, styles]);
1138
- return React.createElement('div', {
1139
- style: {
1140
- backgroundColor: styles?.colors?.background || '#ffffff',
1141
- border: `1px solid ${styles?.colors?.border || '#dee2e6'}`,
1142
- borderRadius: styles?.borders?.radius?.md || '8px',
1143
- padding: styles?.spacing?.lg || '24px',
1144
- height: '400px'
1145
- }
1146
- }, [
1147
- React.createElement('h3', {
1148
- key: 'title',
1149
- style: {
1150
- margin: `0 0 ${styles?.spacing?.md || '16px'} 0`,
1151
- fontSize: styles?.typography?.fontSize?.lg || '16px',
1152
- fontWeight: styles?.typography?.fontWeight?.semibold || '600'
1153
- }
1154
- }, 'Sales by Category'),
1155
- React.createElement('canvas', {
1156
- key: 'chart',
1157
- ref: chartRef,
1158
- style: { maxHeight: '320px' }
1159
- })
1160
- ]);
1161
- };
1162
- };
1163
- // Example ActionCategoryList component string - simulates AI-generated code
1164
- export const getActionCategoryListComponentString = () => {
1165
- return String.raw `
1166
- function createComponent(React, ReactDOM, useState, useEffect, useCallback, createStateUpdater, createStandardEventHandler) {
1167
- function ActionCategoryList({ data, config, state, onEvent, styles, statePath, utilities, selectedCategoryID }) {
1168
- const [categories, setCategories] = useState([]);
1169
- const [expandedCategories, setExpandedCategories] = useState(new Set());
1170
- const [loading, setLoading] = useState(true);
1171
- const [error, setError] = useState(null);
1172
-
1173
- useEffect(() => {
1174
- loadCategories();
1175
- }, []);
1176
-
1177
- const loadCategories = async () => {
1178
- if (!utilities?.rv) {
1179
- setError('RunView utility not available');
1180
- setLoading(false);
1181
- return;
1182
- }
1183
-
1184
- try {
1185
- setLoading(true);
1186
- setError(null);
1187
-
1188
- const result = await utilities.rv.RunView({
1189
- EntityName: 'Action Categories',
1190
- ExtraFilter: '',
1191
- OrderBy: 'Name',
1192
- MaxRows: 1000,
1193
- ResultType: 'entity_object'
1194
- });
1195
-
1196
- if (result.Success && result.Results) {
1197
- setCategories(result.Results);
1198
- } else {
1199
- setError(result.ErrorMessage || 'Failed to load categories');
1200
- }
1201
- } catch (err) {
1202
- setError('Error loading categories: ' + err);
1203
- } finally {
1204
- setLoading(false);
1205
- }
1206
- };
1207
-
1208
- const handleCategoryClick = (category) => {
1209
- if (onEvent) {
1210
- onEvent({
1211
- type: 'categorySelected',
1212
- source: 'ActionCategoryList',
1213
- payload: {
1214
- categoryID: category.ID,
1215
- categoryName: category.Name
1216
- }
1217
- });
1218
- }
1219
- };
1220
-
1221
- if (loading) {
1222
- return React.createElement('div', {
1223
- style: {
1224
- padding: styles?.spacing?.lg || '24px',
1225
- textAlign: 'center',
1226
- color: styles?.colors?.textSecondary || '#6c757d'
1227
- }
1228
- }, 'Loading categories...');
1229
- }
1230
-
1231
- if (error) {
1232
- return React.createElement('div', {
1233
- style: {
1234
- padding: styles?.spacing?.lg || '24px',
1235
- color: styles?.colors?.error || '#dc3545'
1236
- }
1237
- }, error);
1238
- }
1239
-
1240
- return React.createElement('div', {
1241
- style: {
1242
- height: '100%',
1243
- overflow: 'auto'
1244
- }
1245
- }, [
1246
- React.createElement('h3', {
1247
- key: 'title',
1248
- style: {
1249
- margin: '0 0 ' + (styles?.spacing?.md || '16px') + ' 0',
1250
- padding: '0 ' + (styles?.spacing?.md || '16px'),
1251
- fontSize: styles?.typography?.fontSize?.lg || '16px',
1252
- fontWeight: styles?.typography?.fontWeight?.semibold || '600'
1253
- }
1254
- }, 'Action Categories'),
1255
-
1256
- React.createElement('div', {
1257
- key: 'list',
1258
- style: {
1259
- display: 'flex',
1260
- flexDirection: 'column',
1261
- gap: styles?.spacing?.xs || '4px'
1262
- }
1263
- }, categories.map((category) =>
1264
- React.createElement('div', {
1265
- key: category.ID,
1266
- onClick: () => handleCategoryClick(category),
1267
- style: {
1268
- padding: styles?.spacing?.md || '16px',
1269
- cursor: 'pointer',
1270
- backgroundColor: selectedCategoryID === category.ID
1271
- ? styles?.colors?.primaryLight || '#e8e6ff'
1272
- : 'transparent',
1273
- borderLeft: selectedCategoryID === category.ID
1274
- ? '3px solid ' + (styles?.colors?.primary || '#5B4FE9')
1275
- : '3px solid transparent',
1276
- transition: styles?.transitions?.fast || '150ms ease-in-out'
1277
- },
1278
- onMouseEnter: (e) => {
1279
- if (selectedCategoryID !== category.ID) {
1280
- e.currentTarget.style.backgroundColor = styles?.colors?.surfaceHover || '#f1f5f9';
1281
- }
1282
- },
1283
- onMouseLeave: (e) => {
1284
- if (selectedCategoryID !== category.ID) {
1285
- e.currentTarget.style.backgroundColor = 'transparent';
1286
- }
1287
- }
1288
- }, [
1289
- React.createElement('div', {
1290
- key: 'name',
1291
- style: {
1292
- fontSize: styles?.typography?.fontSize?.md || '14px',
1293
- fontWeight: selectedCategoryID === category.ID
1294
- ? styles?.typography?.fontWeight?.medium || '500'
1295
- : styles?.typography?.fontWeight?.regular || '400',
1296
- color: styles?.colors?.text || '#212529',
1297
- marginBottom: styles?.spacing?.xs || '4px'
1298
- }
1299
- }, category.Name),
1300
-
1301
- category.Description && React.createElement('div', {
1302
- key: 'description',
1303
- style: {
1304
- fontSize: styles?.typography?.fontSize?.sm || '12px',
1305
- color: styles?.colors?.textSecondary || '#6c757d',
1306
- lineHeight: styles?.typography?.lineHeight?.normal || '1.5'
1307
- }
1308
- }, category.Description)
1309
- ])
1310
- ))
1311
- ]);
1312
- }
1313
-
1314
- return { component: ActionCategoryList };
1315
- }
1316
- `;
1317
- };
1318
- // Example ActionList component string - simulates AI-generated code
1319
- export const getActionListComponentString = () => {
1320
- return String.raw `
1321
- function createComponent(React, ReactDOM, useState, useEffect, useCallback, createStateUpdater, createStandardEventHandler) {
1322
- function ActionList({ data, config, state, onEvent, styles, statePath, utilities, selectedCategoryID }) {
1323
- const [actions, setActions] = useState([]);
1324
- const [expandedActions, setExpandedActions] = useState(new Set());
1325
- const [actionDetails, setActionDetails] = useState({});
1326
- const [loading, setLoading] = useState(false);
1327
- const [error, setError] = useState(null);
1328
-
1329
- useEffect(() => {
1330
- if (selectedCategoryID) {
1331
- loadActions(selectedCategoryID);
1332
- } else {
1333
- setActions([]);
1334
- }
1335
- }, [selectedCategoryID]);
1336
-
1337
- const loadActions = async (categoryID) => {
1338
- if (!utilities?.rv) {
1339
- setError('RunView utility not available');
1340
- return;
1341
- }
1342
-
1343
- try {
1344
- setLoading(true);
1345
- setError(null);
1346
-
1347
- const result = await utilities.rv.RunView({
1348
- EntityName: 'Actions',
1349
- ExtraFilter: 'CategoryID = \'' + categoryID + '\'',
1350
- OrderBy: 'Name',
1351
- MaxRows: 1000,
1352
- ResultType: 'entity_object'
1353
- });
1354
-
1355
- if (result.Success && result.Results) {
1356
- setActions(result.Results);
1357
- } else {
1358
- setError(result.ErrorMessage || 'Failed to load actions');
1359
- }
1360
- } catch (err) {
1361
- setError('Error loading actions: ' + err);
1362
- } finally {
1363
- setLoading(false);
1364
- }
1365
- };
1366
-
1367
- const handleActionClick = async (action) => {
1368
- // Toggle expanded state
1369
- const newExpanded = new Set(expandedActions);
1370
- if (newExpanded.has(action.ID)) {
1371
- newExpanded.delete(action.ID);
1372
- } else {
1373
- newExpanded.add(action.ID);
1374
- // Load details if not already loaded
1375
- if (!actionDetails[action.ID]) {
1376
- await loadActionDetails(action.ID);
1377
- }
1378
- }
1379
- setExpandedActions(newExpanded);
1380
-
1381
- if (onEvent) {
1382
- onEvent({
1383
- type: 'actionSelected',
1384
- source: 'ActionList',
1385
- payload: {
1386
- actionID: action.ID,
1387
- actionName: action.Name
1388
- }
1389
- });
1390
- }
1391
- };
1392
-
1393
- const loadActionDetails = async (actionID) => {
1394
- if (!utilities?.rv) return;
1395
-
1396
- try {
1397
- // Load params and result codes in parallel
1398
- const [paramsResult, resultCodesResult] = await Promise.all([
1399
- utilities.rv.RunView({
1400
- EntityName: 'Action Params',
1401
- ExtraFilter: 'ActionID = \'' + actionID + '\'',
1402
- OrderBy: 'Name',
1403
- ResultType: 'entity_object'
1404
- }),
1405
- utilities.rv.RunView({
1406
- EntityName: 'Action Result Codes',
1407
- ExtraFilter: 'ActionID = \'' + actionID + '\'',
1408
- OrderBy: 'ResultCode',
1409
- ResultType: 'entity_object'
1410
- })
1411
- ]);
1412
-
1413
- const details = {
1414
- params: paramsResult.Success ? paramsResult.Results : [],
1415
- resultCodes: resultCodesResult.Success ? resultCodesResult.Results : []
1416
- };
1417
-
1418
- setActionDetails(prev => ({ ...prev, [actionID]: details }));
1419
- } catch (err) {
1420
- console.error('Error loading action details:', err);
1421
- }
1422
- };
1423
-
1424
- if (!selectedCategoryID) {
1425
- return React.createElement('div', {
1426
- style: {
1427
- padding: styles?.spacing?.xl || '32px',
1428
- textAlign: 'center',
1429
- color: styles?.colors?.textSecondary || '#6c757d'
1430
- }
1431
- }, 'Select a category to view actions');
1432
- }
1433
-
1434
- if (loading) {
1435
- return React.createElement('div', {
1436
- style: {
1437
- padding: styles?.spacing?.xl || '32px',
1438
- textAlign: 'center',
1439
- color: styles?.colors?.textSecondary || '#6c757d'
1440
- }
1441
- }, 'Loading actions...');
1442
- }
1443
-
1444
- if (error) {
1445
- return React.createElement('div', {
1446
- style: {
1447
- padding: styles?.spacing?.lg || '24px',
1448
- color: styles?.colors?.error || '#dc3545'
1449
- }
1450
- }, error);
1451
- }
1452
-
1453
- if (actions.length === 0) {
1454
- return React.createElement('div', {
1455
- style: {
1456
- padding: styles?.spacing?.xl || '32px',
1457
- textAlign: 'center',
1458
- color: styles?.colors?.textSecondary || '#6c757d'
1459
- }
1460
- }, 'No actions found in this category');
1461
- }
1462
-
1463
- return React.createElement('div', {
1464
- style: {
1465
- padding: styles?.spacing?.lg || '24px'
1466
- }
1467
- }, [
1468
- React.createElement('h3', {
1469
- key: 'title',
1470
- style: {
1471
- margin: '0 0 ' + (styles?.spacing?.lg || '24px') + ' 0',
1472
- fontSize: styles?.typography?.fontSize?.lg || '16px',
1473
- fontWeight: styles?.typography?.fontWeight?.semibold || '600'
1474
- }
1475
- }, 'Actions (' + actions.length + ')'),
1476
-
1477
- React.createElement('div', {
1478
- key: 'grid',
1479
- style: {
1480
- display: 'grid',
1481
- gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))',
1482
- gap: styles?.spacing?.md || '16px'
1483
- }
1484
- }, actions.map((action) => {
1485
- const isExpanded = expandedActions.has(action.ID);
1486
- const details = actionDetails[action.ID] || { params: [], resultCodes: [] };
1487
-
1488
- return React.createElement('div', {
1489
- key: action.ID,
1490
- style: {
1491
- backgroundColor: styles?.colors?.surface || '#f8f9fa',
1492
- border: '1px solid ' + (styles?.colors?.border || '#dee2e6'),
1493
- borderRadius: styles?.borders?.radius?.md || '8px',
1494
- overflow: 'hidden',
1495
- transition: styles?.transitions?.fast || '150ms ease-in-out'
1496
- }
1497
- }, [
1498
- React.createElement('div', {
1499
- key: 'header',
1500
- onClick: () => handleActionClick(action),
1501
- style: {
1502
- padding: styles?.spacing?.md || '16px',
1503
- cursor: 'pointer'
1504
- },
1505
- onMouseEnter: (e) => {
1506
- e.currentTarget.style.backgroundColor = styles?.colors?.surfaceHover || '#f1f5f9';
1507
- },
1508
- onMouseLeave: (e) => {
1509
- e.currentTarget.style.backgroundColor = 'transparent';
1510
- }
1511
- }, [
1512
- React.createElement('div', {
1513
- key: 'header-content',
1514
- style: {
1515
- display: 'flex',
1516
- alignItems: 'center',
1517
- justifyContent: 'space-between'
1518
- }
1519
- }, [
1520
- React.createElement('div', { key: 'main-content' }, [
1521
- React.createElement('div', {
1522
- key: 'name',
1523
- style: {
1524
- fontSize: styles?.typography?.fontSize?.md || '14px',
1525
- fontWeight: styles?.typography?.fontWeight?.medium || '500',
1526
- color: styles?.colors?.text || '#212529',
1527
- marginBottom: styles?.spacing?.xs || '4px'
1528
- }
1529
- }, action.Name),
1530
-
1531
- action.Description && React.createElement('div', {
1532
- key: 'description',
1533
- style: {
1534
- fontSize: styles?.typography?.fontSize?.sm || '12px',
1535
- color: styles?.colors?.textSecondary || '#6c757d',
1536
- lineHeight: styles?.typography?.lineHeight?.normal || '1.5',
1537
- marginBottom: styles?.spacing?.xs || '4px'
1538
- }
1539
- }, action.Description),
1540
-
1541
- React.createElement('div', {
1542
- key: 'metadata',
1543
- style: {
1544
- display: 'flex',
1545
- gap: styles?.spacing?.md || '16px',
1546
- fontSize: styles?.typography?.fontSize?.xs || '11px',
1547
- color: styles?.colors?.textTertiary || '#94a3b8'
1548
- }
1549
- }, [
1550
- action.Type && React.createElement('span', { key: 'type' }, 'Type: ' + action.Type),
1551
- action.Status && React.createElement('span', { key: 'status' }, 'Status: ' + action.Status)
1552
- ])
1553
- ]),
1554
-
1555
- React.createElement('span', {
1556
- key: 'expand-icon',
1557
- style: {
1558
- fontSize: '12px',
1559
- color: styles?.colors?.textSecondary || '#6c757d',
1560
- marginLeft: '8px'
1561
- }
1562
- }, isExpanded ? '▼' : '▶')
1563
- ])
1564
- ]),
1565
-
1566
- isExpanded && React.createElement('div', {
1567
- key: 'details',
1568
- style: {
1569
- borderTop: '1px solid ' + (styles?.colors?.border || '#dee2e6'),
1570
- backgroundColor: styles?.colors?.background || '#ffffff'
1571
- }
1572
- }, [
1573
- // Parameters section
1574
- details.params.length > 0 && React.createElement('div', {
1575
- key: 'params',
1576
- style: {
1577
- padding: styles?.spacing?.md || '16px',
1578
- borderBottom: '1px solid ' + (styles?.colors?.borderLight || '#f1f5f9')
1579
- }
1580
- }, [
1581
- React.createElement('h4', {
1582
- key: 'params-title',
1583
- style: {
1584
- margin: '0 0 ' + (styles?.spacing?.sm || '8px') + ' 0',
1585
- fontSize: styles?.typography?.fontSize?.sm || '13px',
1586
- fontWeight: styles?.typography?.fontWeight?.semibold || '600',
1587
- color: styles?.colors?.text || '#212529'
1588
- }
1589
- }, 'Parameters'),
1590
-
1591
- React.createElement('div', {
1592
- key: 'params-list',
1593
- style: {
1594
- display: 'flex',
1595
- flexDirection: 'column',
1596
- gap: styles?.spacing?.xs || '4px'
1597
- }
1598
- }, details.params.map(param =>
1599
- React.createElement('div', {
1600
- key: param.ID,
1601
- style: {
1602
- display: 'flex',
1603
- alignItems: 'center',
1604
- fontSize: styles?.typography?.fontSize?.xs || '12px',
1605
- padding: '4px 0'
1606
- }
1607
- }, [
1608
- React.createElement('span', {
1609
- key: 'name',
1610
- style: {
1611
- fontWeight: styles?.typography?.fontWeight?.medium || '500',
1612
- color: styles?.colors?.text || '#212529',
1613
- marginRight: '8px'
1614
- }
1615
- }, param.Name),
1616
-
1617
- React.createElement('span', {
1618
- key: 'type',
1619
- style: {
1620
- color: styles?.colors?.textSecondary || '#6c757d',
1621
- fontSize: '11px',
1622
- backgroundColor: styles?.colors?.surfaceHover || '#f1f5f9',
1623
- padding: '2px 6px',
1624
- borderRadius: '3px',
1625
- marginRight: '8px'
1626
- }
1627
- }, param.Type),
1628
-
1629
- param.IsRequired && React.createElement('span', {
1630
- key: 'required',
1631
- style: {
1632
- color: styles?.colors?.error || '#dc3545',
1633
- fontSize: '10px',
1634
- fontWeight: styles?.typography?.fontWeight?.semibold || '600'
1635
- }
1636
- }, 'REQUIRED'),
1637
-
1638
- param.Description && React.createElement('span', {
1639
- key: 'desc',
1640
- style: {
1641
- color: styles?.colors?.textTertiary || '#94a3b8',
1642
- marginLeft: 'auto',
1643
- fontSize: '11px'
1644
- }
1645
- }, param.Description)
1646
- ])
1647
- ))
1648
- ]),
1649
-
1650
- // Result codes section
1651
- details.resultCodes.length > 0 && React.createElement('div', {
1652
- key: 'result-codes',
1653
- style: {
1654
- padding: styles?.spacing?.md || '16px'
1655
- }
1656
- }, [
1657
- React.createElement('h4', {
1658
- key: 'codes-title',
1659
- style: {
1660
- margin: '0 0 ' + (styles?.spacing?.sm || '8px') + ' 0',
1661
- fontSize: styles?.typography?.fontSize?.sm || '13px',
1662
- fontWeight: styles?.typography?.fontWeight?.semibold || '600',
1663
- color: styles?.colors?.text || '#212529'
1664
- }
1665
- }, 'Result Codes'),
1666
-
1667
- React.createElement('div', {
1668
- key: 'codes-list',
1669
- style: {
1670
- display: 'flex',
1671
- flexDirection: 'column',
1672
- gap: styles?.spacing?.xs || '4px'
1673
- }
1674
- }, details.resultCodes.map(code =>
1675
- React.createElement('div', {
1676
- key: code.ID,
1677
- style: {
1678
- display: 'flex',
1679
- alignItems: 'center',
1680
- fontSize: styles?.typography?.fontSize?.xs || '12px',
1681
- padding: '4px 0'
1682
- }
1683
- }, [
1684
- React.createElement('span', {
1685
- key: 'code',
1686
- style: {
1687
- fontFamily: 'monospace',
1688
- fontWeight: styles?.typography?.fontWeight?.medium || '500',
1689
- color: code.IsSuccess ? styles?.colors?.success || '#10b981' : styles?.colors?.error || '#dc3545',
1690
- marginRight: '8px'
1691
- }
1692
- }, code.ResultCode),
1693
-
1694
- code.Description && React.createElement('span', {
1695
- key: 'desc',
1696
- style: {
1697
- color: styles?.colors?.textSecondary || '#6c757d'
1698
- }
1699
- }, code.Description)
1700
- ])
1701
- ))
1702
- ])
1703
- ])
1704
- ]);
1705
- }))
1706
- ]);
1707
- }
1708
-
1709
- return { component: ActionList };
1710
- }
1711
- `;
1712
- };
1713
- // Example composite ActionBrowser component string - simulates AI-generated code
1714
- export const getActionBrowserComponentString = () => {
1715
- return String.raw `
1716
- function createComponent(React, ReactDOM, useState, useEffect, useCallback, createStateUpdater, createStandardEventHandler) {
1717
- function ActionBrowser({ data, utilities, userState, callbacks, styles, components }) {
1718
- const [fullUserState, setFullUserState] = useState({
1719
- selectedCategoryID: null,
1720
- selectedActionID: null,
1721
- categoryList: {},
1722
- actionList: {},
1723
- ...userState
1724
- });
1725
-
1726
- // Destructure child components from registry
1727
- const { ActionCategoryList, ActionList } = components;
1728
-
1729
- const updateUserState = (stateUpdate) => {
1730
- const newState = { ...fullUserState, ...stateUpdate };
1731
- setFullUserState(newState);
1732
- if (callbacks?.UpdateUserState) {
1733
- callbacks.UpdateUserState(newState);
1734
- }
1735
- };
1736
-
1737
- const handleComponentEvent = (event) => {
1738
- if (event.type === 'categorySelected' && event.source === 'ActionCategoryList') {
1739
- updateUserState({
1740
- selectedCategoryID: event.payload.categoryID,
1741
- selectedActionID: null // Reset action selection
1742
- });
1743
- return;
1744
- }
1745
-
1746
- if (event.type === 'actionSelected' && event.source === 'ActionList') {
1747
- updateUserState({
1748
- selectedActionID: event.payload.actionID
1749
- });
1750
- if (callbacks?.NotifyEvent) {
1751
- callbacks.NotifyEvent('actionSelected', event.payload);
1752
- }
1753
- return;
1754
- }
1755
-
1756
- // Handle standard state changes
1757
- if (event.type === 'stateChanged') {
1758
- const update = {};
1759
- update[event.payload.statePath] = event.payload.newState;
1760
- updateUserState(update);
1761
- }
1762
- };
1763
-
1764
- return React.createElement('div', {
1765
- style: {
1766
- display: 'flex',
1767
- height: '100%',
1768
- minHeight: '600px',
1769
- backgroundColor: styles.colors.background,
1770
- fontFamily: styles.typography.fontFamily
1771
- }
1772
- }, [
1773
- // Left sidebar with categories
1774
- React.createElement('div', {
1775
- key: 'sidebar',
1776
- style: {
1777
- width: '300px',
1778
- backgroundColor: styles.colors.surface,
1779
- borderRight: '1px solid ' + styles.colors.border,
1780
- overflow: 'hidden',
1781
- display: 'flex',
1782
- flexDirection: 'column'
1783
- }
1784
- }, [
1785
- ActionCategoryList && React.createElement(ActionCategoryList, {
1786
- key: 'categories',
1787
- data: [],
1788
- config: {},
1789
- state: fullUserState.categoryList || {},
1790
- onEvent: handleComponentEvent,
1791
- styles: styles,
1792
- utilities: utilities,
1793
- statePath: 'categoryList',
1794
- selectedCategoryID: fullUserState.selectedCategoryID
1795
- })
1796
- ]),
1797
-
1798
- // Main content area with actions
1799
- React.createElement('div', {
1800
- key: 'main',
1801
- style: {
1802
- flex: 1,
1803
- overflow: 'auto'
1804
- }
1805
- }, [
1806
- ActionList && React.createElement(ActionList, {
1807
- key: 'actions',
1808
- data: [],
1809
- config: {},
1810
- state: fullUserState.actionList || {},
1811
- onEvent: handleComponentEvent,
1812
- styles: styles,
1813
- utilities: utilities,
1814
- statePath: 'actionList',
1815
- selectedCategoryID: fullUserState.selectedCategoryID
1816
- })
1817
- ])
1818
- ]);
1819
- }
1820
-
1821
- return { component: ActionBrowser };
1822
- }
1823
- `;
1824
- };
1825
- /**
1826
- * Unit tests for GlobalComponentRegistry
1827
- * These would normally be in a separate .spec.ts file
1828
- * Run these tests to ensure the registry works correctly
1829
- */
1830
- export function testGlobalComponentRegistry() {
1831
- const registry = GlobalComponentRegistry.Instance;
1832
- const testResults = [];
1833
- const assert = (condition, testName, error) => {
1834
- testResults.push({ test: testName, passed: condition, error: condition ? undefined : error });
1835
- if (!condition) {
1836
- console.error(`Test failed: ${testName}`, error);
1837
- }
1838
- else {
1839
- console.log(`Test passed: ${testName}`);
1840
- }
1841
- };
1842
- // Test 1: Singleton pattern
1843
- const registry2 = GlobalComponentRegistry.Instance;
1844
- assert(registry === registry2, 'Singleton pattern', 'Multiple instances created');
1845
- // Test 2: Basic registration and retrieval
1846
- registry.clear(); // Start fresh
1847
- const mockComponent = { name: 'MockComponent' };
1848
- registry.register('TestComponent', mockComponent);
1849
- assert(registry.get('TestComponent') === mockComponent, 'Basic register/get', 'Component not retrieved correctly');
1850
- // Test 3: Has method
1851
- assert(registry.has('TestComponent') === true, 'Has method - existing', 'Should return true for existing component');
1852
- assert(registry.has('NonExistent') === false, 'Has method - non-existing', 'Should return false for non-existing component');
1853
- // Test 4: Register with metadata
1854
- const mockSearchBox = { name: 'SearchBox' };
1855
- registry.registerWithMetadata('SearchBox', 'CRM', 'v1', mockSearchBox, 'CRM-specific search');
1856
- assert(registry.get('SearchBox_CRM_v1') === mockSearchBox, 'Register with metadata', 'Component not found with metadata key');
1857
- assert(registry.get('SearchBox_CRM') === mockSearchBox, 'Backwards compatibility key', 'Component not found with context-only key');
1858
- // Test 5: Multiple versions
1859
- const mockSearchBoxV2 = { name: 'SearchBoxV2' };
1860
- registry.registerWithMetadata('SearchBox', 'CRM', 'v2', mockSearchBoxV2);
1861
- assert(registry.get('SearchBox_CRM_v1') === mockSearchBox, 'Version v1 still accessible', 'v1 component overwritten');
1862
- assert(registry.get('SearchBox_CRM_v2') === mockSearchBoxV2, 'Version v2 accessible', 'v2 component not found');
1863
- // Test 6: GetWithFallback - exact match
1864
- const found1 = registry.getWithFallback('SearchBox', 'CRM', 'v2');
1865
- assert(found1 === mockSearchBoxV2, 'GetWithFallback - exact match', 'Should find exact version match');
1866
- // Test 7: GetWithFallback - context fallback
1867
- const found2 = registry.getWithFallback('SearchBox', 'CRM', 'v3'); // v3 doesn't exist
1868
- assert(found2 === mockSearchBoxV2, 'GetWithFallback - context fallback', 'Should fall back to context match');
1869
- // Test 8: GetWithFallback - global fallback
1870
- const globalComponent = { name: 'GlobalSearch' };
1871
- registry.register('SearchBox_Global', globalComponent);
1872
- const found3 = registry.getWithFallback('SearchBox', 'Finance', 'v1'); // Finance context doesn't exist
1873
- assert(found3 === globalComponent, 'GetWithFallback - global fallback', 'Should fall back to global component');
1874
- // Test 9: GetWithFallback - name only fallback
1875
- const nameOnlyComponent = { name: 'NameOnly' };
1876
- registry.register('UniqueComponent', nameOnlyComponent);
1877
- const found4 = registry.getWithFallback('UniqueComponent', 'Any', 'v1');
1878
- assert(found4 === nameOnlyComponent, 'GetWithFallback - name only fallback', 'Should fall back to name-only registration');
1879
- // Test 10: GetWithFallback - not found
1880
- const found5 = registry.getWithFallback('NotRegistered', 'Any', 'v1');
1881
- assert(found5 === null, 'GetWithFallback - not found', 'Should return null when component not found');
1882
- // Test 11: Get registered keys
1883
- const keys = registry.getRegisteredKeys();
1884
- assert(keys.includes('SearchBox_CRM_v1'), 'Get registered keys', 'Should include registered components');
1885
- assert(keys.length > 5, 'Multiple registrations', `Should have multiple keys registered, found ${keys.length}`);
1886
- // Test 12: Clear registry
1887
- registry.clear();
1888
- assert(registry.getRegisteredKeys().length === 0, 'Clear registry', 'Registry should be empty after clear');
1889
- // Summary
1890
- const passed = testResults.filter(r => r.passed).length;
1891
- const failed = testResults.filter(r => !r.passed).length;
1892
- console.log(`\nTest Summary: ${passed} passed, ${failed} failed out of ${testResults.length} total tests`);
1893
- // Important: Clear the registry at the end of tests so it's ready for actual use
1894
- registry.clear();
1895
- return testResults;
1896
- }
1897
- /**
1898
- * Generate a createComponent wrapper around component code
1899
- * This wrapper provides React context and expected factory interface
1900
- */
1901
- function generateComponentWrapper(componentCode, componentName) {
1902
- return `function createComponent(React, ReactDOM, useState, useEffect, useCallback, createStateUpdater, createStandardEventHandler, libraries) {
1903
- ${componentCode}
1904
-
1905
- return {
1906
- component: ${componentName},
1907
- print: function() { window.print(); },
1908
- refresh: function() { /* Managed by parent */ }
1909
- };
1910
- }`;
1911
- }
1912
- /**
1913
- * Transpile JSX code to JavaScript using Babel
1914
- */
1915
- function transpileJSX(code, filename) {
1916
- const Babel = window.Babel;
1917
- if (!Babel) {
1918
- throw new Error('Babel not loaded - cannot transpile JSX');
1919
- }
1920
- const result = Babel.transform(code, {
1921
- presets: ['react'],
1922
- filename: filename
1923
- });
1924
- return result.code;
1925
- }
1926
- /**
1927
- * Get React context and libraries for component compilation
1928
- */
1929
- function getReactContext() {
1930
- const React = window.React;
1931
- const ReactDOM = window.ReactDOM;
1932
- if (!React || !ReactDOM) {
1933
- throw new Error('React and ReactDOM must be loaded before compiling components');
1934
- }
1935
- const libraries = {
1936
- antd: window.antd,
1937
- ReactBootstrap: window.ReactBootstrap,
1938
- d3: window.d3,
1939
- Chart: window.Chart,
1940
- _: window._,
1941
- dayjs: window.dayjs
1942
- };
1943
- return { React, ReactDOM, libraries };
1944
- }
1945
- /**
1946
- * Compile and register a component from string code
1947
- * Always wraps plain function components with createComponent factory
1948
- */
1949
- export async function compileAndRegisterComponent(componentName, componentCode, context = 'Global', version = 'v1', reactContext) {
1950
- const registry = GlobalComponentRegistry.Instance;
1951
- try {
1952
- const { React, ReactDOM, libraries } = reactContext || getReactContext();
1953
- // Auto-generate wrapper around the component code
1954
- const wrappedCode = generateComponentWrapper(componentCode, componentName);
1955
- // Transpile the wrapped code
1956
- const transpiledCode = transpileJSX(wrappedCode, `${componentName}.jsx`);
1957
- // Create the component factory
1958
- const createComponent = new Function('React', 'ReactDOM', 'useState', 'useEffect', 'useCallback', 'createStateUpdater', 'createStandardEventHandler', 'libraries', `${transpiledCode}; return createComponent;`)(React, ReactDOM, React.useState, React.useEffect, React.useCallback, () => { }, // createStateUpdater placeholder
1959
- () => { }, // createStandardEventHandler placeholder
1960
- libraries);
1961
- // Get the component from the factory
1962
- const componentResult = createComponent(React, ReactDOM, React.useState, React.useEffect, React.useCallback, () => { }, // createStateUpdater
1963
- () => { } // createStandardEventHandler
1964
- );
1965
- // Register the component
1966
- registry.registerWithMetadata(componentName, context, version, componentResult.component);
1967
- console.log(`Compiled and registered component: ${componentName}`);
1968
- return true;
1969
- }
1970
- catch (error) {
1971
- console.error(`Failed to compile component ${componentName}:`, error);
1972
- return false;
1973
- }
1974
- }
1975
- /**
1976
- * Helper function to register example components for testing
1977
- * Call this during application initialization
1978
- */
1979
- export async function registerExampleComponents(React, Chart) {
1980
- const registry = GlobalComponentRegistry.Instance;
1981
- // Get React reference - either passed in or from window
1982
- React = React || window.React;
1983
- Chart = Chart || window.Chart;
1984
- // Also make this function available globally for debugging
1985
- window.registerExampleComponents = registerExampleComponents;
1986
- window.compileAndRegisterComponent = compileAndRegisterComponent;
1987
- if (React) {
1988
- // Register simple test components (these use the real React components directly)
1989
- registry.registerWithMetadata('SearchBox', 'CRM', 'v1', createSearchBoxComponent(React));
1990
- registry.registerWithMetadata('SearchBox', 'Global', 'v1', createSearchBoxComponent(React));
1991
- // Register OrderList variants
1992
- registry.registerWithMetadata('OrderList', 'Standard', 'v1', createOrderListComponent(React));
1993
- registry.registerWithMetadata('OrderList', 'Advanced', 'v1', createOrderListComponent(React));
1994
- // Register chart components
1995
- if (Chart) {
1996
- registry.registerWithMetadata('CategoryChart', 'Global', 'v1', createCategoryChartComponent(React, Chart));
1997
- }
1998
- // Compile and register Action browser components from strings
1999
- // This simulates how AI-generated components are processed
2000
- await compileAndRegisterComponent('ActionCategoryList', getActionCategoryListComponentString(), 'Global', 'v1');
2001
- await compileAndRegisterComponent('ActionList', getActionListComponentString(), 'Global', 'v1');
2002
- await compileAndRegisterComponent('ActionBrowser', getActionBrowserComponentString(), 'Global', 'v1');
2003
- console.log('Example components registered successfully');
2004
- console.log('Registered components:', registry.getRegisteredKeys());
2005
- return true;
2006
- }
2007
- else {
2008
- console.warn('React not found - cannot register example components');
2009
- return false;
2010
- }
2011
- }
2012
- //# sourceMappingURL=skip-react-component-host.js.map