@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.
- package/dist/lib/dynamic-report/dynamic-ui-component.d.ts +42 -21
- package/dist/lib/dynamic-report/dynamic-ui-component.d.ts.map +1 -1
- package/dist/lib/dynamic-report/dynamic-ui-component.js +284 -245
- package/dist/lib/dynamic-report/dynamic-ui-component.js.map +1 -1
- package/dist/lib/module.d.ts +3 -2
- package/dist/lib/module.d.ts.map +1 -1
- package/dist/lib/module.js +5 -1
- package/dist/lib/module.js.map +1 -1
- package/dist/lib/skip-chat/skip-chat.component.d.ts +0 -15
- package/dist/lib/skip-chat/skip-chat.component.d.ts.map +1 -1
- package/dist/lib/skip-chat/skip-chat.component.js +27 -691
- package/dist/lib/skip-chat/skip-chat.component.js.map +1 -1
- package/package.json +14 -17
- package/dist/lib/dynamic-report/skip-react-component-host.d.ts +0 -237
- package/dist/lib/dynamic-report/skip-react-component-host.d.ts.map +0 -1
- package/dist/lib/dynamic-report/skip-react-component-host.js +0 -2012
- package/dist/lib/dynamic-report/skip-react-component-host.js.map +0 -1
|
@@ -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
|