dynim-react 1.0.6 → 1.0.8
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/builder/BuilderProvider.d.ts +14 -22
- package/dist/builder/BuilderProvider.d.ts.map +1 -1
- package/dist/builder/BuilderProvider.js +165 -141
- package/dist/inference/sharedContext.d.ts.map +1 -1
- package/dist/inference/sharedContext.js +10 -2
- package/dist/inference/types.d.ts +3 -0
- package/dist/inference/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
* BuilderProvider - React integration for the visual builder
|
|
3
3
|
*
|
|
4
4
|
* Integrates the visual builder UI (floating bar, drag engine, overlays)
|
|
5
|
-
* from dynim-core.
|
|
5
|
+
* from dynim-core. Loads tenant bundles via dynamic import() and renders
|
|
6
|
+
* them as React components.
|
|
6
7
|
*/
|
|
7
8
|
import { type ReactNode } from 'react';
|
|
8
|
-
import type { BuilderInstance, CodeEdit, CodeEvent, CodeMessage
|
|
9
|
+
import type { BuilderInstance, CodeEdit, CodeEvent, CodeMessage } from 'dynim-core';
|
|
9
10
|
export interface BuilderConfig {
|
|
10
11
|
apiBase?: string;
|
|
11
12
|
pageId?: string;
|
|
@@ -27,22 +28,18 @@ export interface BuilderConfig {
|
|
|
27
28
|
onCodeEdit?: (edit: CodeEdit) => void;
|
|
28
29
|
/** @deprecated Use onCodeMessageUpdate instead */
|
|
29
30
|
onCodeMessage?: (event: CodeEvent) => void;
|
|
30
|
-
/** @deprecated No longer needed - BuilderProvider creates internal container */
|
|
31
|
-
previewContainer?: HTMLElement;
|
|
32
31
|
/** Function to resolve bundle URL from project ID */
|
|
33
32
|
getBundleUrl?: (projectId: string) => string | Promise<string>;
|
|
34
|
-
/** If true, bundles replace children when loaded. If false, uses external previewContainer (default: true) */
|
|
35
|
-
inPlacePreview?: boolean;
|
|
36
|
-
/** Whether to auto-apply style changes from code edits (default: true) */
|
|
37
|
-
autoApplyStyles?: boolean;
|
|
38
|
-
/** Transition duration for bundle crossfade in ms (default: 300) */
|
|
39
|
-
previewTransitionDuration?: number;
|
|
40
|
-
/** Called when a style change is applied */
|
|
41
|
-
onStyleApplied?: (change: StyleChange, elements: Element[]) => void;
|
|
42
33
|
/** Called when bundle starts loading */
|
|
43
34
|
onBundleLoadStart?: (bundleUrl: string) => void;
|
|
44
35
|
/** Called when bundle finishes loading */
|
|
45
36
|
onBundleLoadComplete?: (bundleUrl: string) => void;
|
|
37
|
+
/** NPM packages to expose to bundles (e.g., { 'react-router-dom': ReactRouterDOM }) */
|
|
38
|
+
packages?: Record<string, unknown>;
|
|
39
|
+
/** Custom hooks to expose to bundles */
|
|
40
|
+
hooks?: Record<string, unknown>;
|
|
41
|
+
/** React contexts to expose to bundles */
|
|
42
|
+
contexts?: Record<string, unknown>;
|
|
46
43
|
}
|
|
47
44
|
export interface BuilderContextValue {
|
|
48
45
|
enterBuilder: () => void;
|
|
@@ -60,7 +57,7 @@ export interface BuilderContextValue {
|
|
|
60
57
|
resetCodeMessage: () => void;
|
|
61
58
|
/** @deprecated Use codeMessage.edits instead */
|
|
62
59
|
codeEdits: CodeEdit[];
|
|
63
|
-
/** Load a bundle
|
|
60
|
+
/** Load a bundle by URL */
|
|
64
61
|
loadBundle: (bundleUrl: string) => Promise<void>;
|
|
65
62
|
/** Load bundle for a project (uses getBundleUrl from config) */
|
|
66
63
|
loadProjectBundle: (projectId: string) => Promise<void>;
|
|
@@ -68,19 +65,11 @@ export interface BuilderContextValue {
|
|
|
68
65
|
loadSavedBundle: (projectId: string) => Promise<void>;
|
|
69
66
|
/** Load temp bundle for a project (AI edits preview) */
|
|
70
67
|
loadTempBundle: (projectId: string) => Promise<void>;
|
|
71
|
-
/** Apply styles directly to preview DOM */
|
|
72
|
-
applyStyles: (selector: string, styles: Record<string, string>) => void;
|
|
73
|
-
/** Apply className to preview DOM */
|
|
74
|
-
applyClassName: (selector: string, className: string) => void;
|
|
75
|
-
/** Toggle CSS classes on preview DOM */
|
|
76
|
-
toggleClasses: (selector: string, add: string[], remove: string[]) => void;
|
|
77
|
-
/** Check if bundle manager is available */
|
|
78
|
-
hasBundleManager: boolean;
|
|
79
68
|
/** Check if a bundle is currently loaded */
|
|
80
69
|
isBundleLoaded: boolean;
|
|
81
70
|
/** Check if bundle is loading */
|
|
82
71
|
isBundleLoading: boolean;
|
|
83
|
-
/** Unload current bundle */
|
|
72
|
+
/** Unload current bundle (show children again) */
|
|
84
73
|
unloadBundle: () => void;
|
|
85
74
|
}
|
|
86
75
|
export interface BuilderProviderProps {
|
|
@@ -90,6 +79,9 @@ export interface BuilderProviderProps {
|
|
|
90
79
|
}
|
|
91
80
|
/**
|
|
92
81
|
* BuilderProvider - Wraps your app with builder support
|
|
82
|
+
*
|
|
83
|
+
* When a bundle is loaded, it replaces children with the bundle's App component.
|
|
84
|
+
* Bundles are loaded via dynamic import() and rendered as React components.
|
|
93
85
|
*/
|
|
94
86
|
export declare function BuilderProvider({ children, config, onBuilderReady, }: BuilderProviderProps): JSX.Element;
|
|
95
87
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BuilderProvider.d.ts","sourceRoot":"","sources":["../../src/builder/BuilderProvider.tsx"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"BuilderProvider.d.ts","sourceRoot":"","sources":["../../src/builder/BuilderProvider.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1,27 +1,29 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsxs as _jsxs, Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
3
|
* BuilderProvider - React integration for the visual builder
|
|
4
4
|
*
|
|
5
5
|
* Integrates the visual builder UI (floating bar, drag engine, overlays)
|
|
6
|
-
* from dynim-core.
|
|
6
|
+
* from dynim-core. Loads tenant bundles via dynamic import() and renders
|
|
7
|
+
* them as React components.
|
|
7
8
|
*/
|
|
8
|
-
import { createContext, useContext, useEffect, useRef, useCallback, useMemo, useState, } from 'react';
|
|
9
|
-
import
|
|
9
|
+
import React, { createContext, useContext, useEffect, useRef, useCallback, useMemo, useState, } from 'react';
|
|
10
|
+
import ReactDOM from 'react-dom';
|
|
11
|
+
import { createBuilderClient, createBuilder, createCodeClient, } from 'dynim-core';
|
|
12
|
+
import { createSharedContext } from '../inference/sharedContext';
|
|
10
13
|
const BuilderContext = createContext(null);
|
|
11
14
|
/**
|
|
12
15
|
* BuilderProvider - Wraps your app with builder support
|
|
16
|
+
*
|
|
17
|
+
* When a bundle is loaded, it replaces children with the bundle's App component.
|
|
18
|
+
* Bundles are loaded via dynamic import() and rendered as React components.
|
|
13
19
|
*/
|
|
14
20
|
export function BuilderProvider({ children, config = {}, onBuilderReady, }) {
|
|
15
21
|
const builderClientRef = useRef(null);
|
|
16
22
|
const builderRef = useRef(null);
|
|
17
23
|
const codeClientRef = useRef(null);
|
|
18
|
-
const bundleManagerRef = useRef(null);
|
|
19
|
-
const styleApplierRef = useRef(null);
|
|
20
|
-
const [internalContainer, setInternalContainer] = useState(null);
|
|
21
24
|
const [isBuilderActive, setIsBuilderActive] = useState(false);
|
|
22
25
|
const [isBundleLoaded, setIsBundleLoaded] = useState(false);
|
|
23
26
|
const [isBundleLoading, setIsBundleLoading] = useState(false);
|
|
24
|
-
const [hasBundleManager, setHasBundleManager] = useState(false);
|
|
25
27
|
const [codeMessage, setCodeMessage] = useState({
|
|
26
28
|
thinking: '',
|
|
27
29
|
text: '',
|
|
@@ -30,10 +32,35 @@ export function BuilderProvider({ children, config = {}, onBuilderReady, }) {
|
|
|
30
32
|
bundleReady: false,
|
|
31
33
|
bundleError: undefined,
|
|
32
34
|
});
|
|
35
|
+
// The loaded tenant app component
|
|
36
|
+
const [TenantApp, setTenantApp] = useState(null);
|
|
37
|
+
// Error state for bundle rendering failures
|
|
38
|
+
const [bundleError, setBundleError] = useState(null);
|
|
39
|
+
// Set up shared context for bundles (exposes React, ReactDOM, packages, hooks, contexts)
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
const { packages = {}, hooks = {}, contexts = {} } = config;
|
|
42
|
+
// Import useBuilder dynamically to register in packages (avoids circular dep at module level)
|
|
43
|
+
const sdkExports = {
|
|
44
|
+
useBuilder,
|
|
45
|
+
BuilderProvider,
|
|
46
|
+
// Add other commonly used exports bundles might need
|
|
47
|
+
};
|
|
48
|
+
createSharedContext({
|
|
49
|
+
React,
|
|
50
|
+
ReactDOM,
|
|
51
|
+
packages: {
|
|
52
|
+
'dynim-react': sdkExports,
|
|
53
|
+
...packages,
|
|
54
|
+
},
|
|
55
|
+
hooks,
|
|
56
|
+
contexts,
|
|
57
|
+
});
|
|
58
|
+
console.log('[BuilderProvider] Shared context initialized with packages:', ['dynim-react', ...Object.keys(packages)]);
|
|
59
|
+
}, [config]);
|
|
33
60
|
// Initialize code client FIRST (before builder, so we can share it)
|
|
34
61
|
useEffect(() => {
|
|
35
62
|
const { apiBase = 'http://localhost:8080', sessionToken, refreshToken, getSession, onCodeMessageUpdate, onCodeMessage, // deprecated
|
|
36
|
-
onCodeEdit, onError,
|
|
63
|
+
onCodeEdit, onError, } = config;
|
|
37
64
|
codeClientRef.current = createCodeClient({
|
|
38
65
|
apiBase,
|
|
39
66
|
sessionToken,
|
|
@@ -51,11 +78,6 @@ export function BuilderProvider({ children, config = {}, onBuilderReady, }) {
|
|
|
51
78
|
},
|
|
52
79
|
onEdit: (edit) => {
|
|
53
80
|
console.log('[BuilderProvider] Code edit:', edit);
|
|
54
|
-
// Auto-apply style changes if enabled and style applier is available
|
|
55
|
-
if (autoApplyStyles && styleApplierRef.current && hasStyleChanges(edit)) {
|
|
56
|
-
const result = styleApplierRef.current.processEdit(edit);
|
|
57
|
-
console.log('[BuilderProvider] Style changes:', result.changes.length, 'applied:', result.applied);
|
|
58
|
-
}
|
|
59
81
|
onCodeEdit?.(edit);
|
|
60
82
|
},
|
|
61
83
|
onError: (error) => {
|
|
@@ -107,80 +129,6 @@ export function BuilderProvider({ children, config = {}, onBuilderReady, }) {
|
|
|
107
129
|
},
|
|
108
130
|
});
|
|
109
131
|
}, [config]);
|
|
110
|
-
// Initialize bundle manager
|
|
111
|
-
useEffect(() => {
|
|
112
|
-
const { previewContainer, inPlacePreview = true, previewTransitionDuration = 300, sessionToken, getSession, onStyleApplied, onBundleLoadStart, onBundleLoadComplete, onError, } = config;
|
|
113
|
-
// Determine which container to use
|
|
114
|
-
const container = inPlacePreview ? internalContainer : previewContainer;
|
|
115
|
-
console.log('[BuilderProvider] Bundle manager init:', { inPlacePreview, hasInternalContainer: !!internalContainer, hasPreviewContainer: !!previewContainer });
|
|
116
|
-
if (!container) {
|
|
117
|
-
// For inPlacePreview, container may not be ready on first render
|
|
118
|
-
console.log('[BuilderProvider] No container yet, skipping bundle manager init');
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
// Create auth token getter for bundle requests
|
|
122
|
-
const getAuthToken = async () => {
|
|
123
|
-
// First try config sessionToken
|
|
124
|
-
if (sessionToken) {
|
|
125
|
-
return sessionToken;
|
|
126
|
-
}
|
|
127
|
-
// Then try getSession callback
|
|
128
|
-
if (getSession) {
|
|
129
|
-
try {
|
|
130
|
-
const session = await getSession();
|
|
131
|
-
return session.token;
|
|
132
|
-
}
|
|
133
|
-
catch (e) {
|
|
134
|
-
console.warn('[BuilderProvider] Failed to get session for bundle auth:', e);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
return null;
|
|
138
|
-
};
|
|
139
|
-
bundleManagerRef.current = createBundleManager({
|
|
140
|
-
parent: container,
|
|
141
|
-
transitionDuration: previewTransitionDuration,
|
|
142
|
-
getAuthToken,
|
|
143
|
-
onLoadStart: (url) => {
|
|
144
|
-
setIsBundleLoading(true);
|
|
145
|
-
onBundleLoadStart?.(url);
|
|
146
|
-
},
|
|
147
|
-
onLoadComplete: (url) => {
|
|
148
|
-
setIsBundleLoading(false);
|
|
149
|
-
setIsBundleLoaded(true);
|
|
150
|
-
onBundleLoadComplete?.(url);
|
|
151
|
-
},
|
|
152
|
-
onError: (error) => {
|
|
153
|
-
setIsBundleLoading(false);
|
|
154
|
-
console.error('[BuilderProvider] Bundle load error:', error);
|
|
155
|
-
onError?.(error);
|
|
156
|
-
},
|
|
157
|
-
onUnload: () => {
|
|
158
|
-
setIsBundleLoaded(false);
|
|
159
|
-
},
|
|
160
|
-
});
|
|
161
|
-
setHasBundleManager(true);
|
|
162
|
-
console.log('[BuilderProvider] Bundle manager created');
|
|
163
|
-
// Create style applier that targets the bundle manager's container
|
|
164
|
-
styleApplierRef.current = createStyleApplier({
|
|
165
|
-
queryElements: (selector) => {
|
|
166
|
-
const container = bundleManagerRef.current?.getActiveContainer();
|
|
167
|
-
return container?.querySelectorAll(selector) ?? document.querySelectorAll(selector);
|
|
168
|
-
},
|
|
169
|
-
onApply: (change, elements) => {
|
|
170
|
-
console.log('[BuilderProvider] Style applied:', change, elements.length, 'elements');
|
|
171
|
-
onStyleApplied?.(change, elements);
|
|
172
|
-
},
|
|
173
|
-
onError: (change, error) => {
|
|
174
|
-
console.error('[BuilderProvider] Style apply error:', error, change);
|
|
175
|
-
},
|
|
176
|
-
});
|
|
177
|
-
return () => {
|
|
178
|
-
bundleManagerRef.current?.destroy();
|
|
179
|
-
bundleManagerRef.current = null;
|
|
180
|
-
styleApplierRef.current = null;
|
|
181
|
-
setHasBundleManager(false);
|
|
182
|
-
};
|
|
183
|
-
}, [config, internalContainer]);
|
|
184
132
|
// Enter builder mode - activates the visual builder UI
|
|
185
133
|
const enterBuilder = useCallback(() => {
|
|
186
134
|
if (builderRef.current && !builderRef.current.isActive()) {
|
|
@@ -199,14 +147,94 @@ export function BuilderProvider({ children, config = {}, onBuilderReady, }) {
|
|
|
199
147
|
await builderRef.current.save();
|
|
200
148
|
}
|
|
201
149
|
}, []);
|
|
202
|
-
|
|
150
|
+
/**
|
|
151
|
+
* Get auth token for bundle requests
|
|
152
|
+
*/
|
|
153
|
+
const getAuthToken = useCallback(async () => {
|
|
154
|
+
const { sessionToken, getSession } = config;
|
|
155
|
+
if (sessionToken) {
|
|
156
|
+
return sessionToken;
|
|
157
|
+
}
|
|
158
|
+
if (getSession) {
|
|
159
|
+
try {
|
|
160
|
+
const session = await getSession();
|
|
161
|
+
return session.token;
|
|
162
|
+
}
|
|
163
|
+
catch (e) {
|
|
164
|
+
console.warn('[BuilderProvider] Failed to get session for bundle auth:', e);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return null;
|
|
168
|
+
}, [config]);
|
|
169
|
+
// Track loading state in ref to avoid stale closure issues
|
|
170
|
+
const isBundleLoadingRef = useRef(false);
|
|
171
|
+
/**
|
|
172
|
+
* Load a bundle via dynamic import()
|
|
173
|
+
* Fetches with auth, creates blob URL, imports as ES module
|
|
174
|
+
*/
|
|
203
175
|
const loadBundle = useCallback(async (bundleUrl) => {
|
|
204
|
-
|
|
205
|
-
|
|
176
|
+
const { onBundleLoadStart, onBundleLoadComplete, onError } = config;
|
|
177
|
+
if (isBundleLoadingRef.current) {
|
|
178
|
+
console.warn('[BuilderProvider] Already loading a bundle');
|
|
206
179
|
return;
|
|
207
180
|
}
|
|
208
|
-
|
|
209
|
-
|
|
181
|
+
isBundleLoadingRef.current = true;
|
|
182
|
+
setIsBundleLoading(true);
|
|
183
|
+
setBundleError(null); // Clear any previous error
|
|
184
|
+
onBundleLoadStart?.(bundleUrl);
|
|
185
|
+
console.log('[BuilderProvider] Loading bundle:', bundleUrl);
|
|
186
|
+
try {
|
|
187
|
+
// Fetch bundle with auth headers
|
|
188
|
+
const token = await getAuthToken();
|
|
189
|
+
const headers = {
|
|
190
|
+
'Accept': 'application/javascript',
|
|
191
|
+
};
|
|
192
|
+
if (token) {
|
|
193
|
+
headers['Authorization'] = `Bearer ${token}`;
|
|
194
|
+
}
|
|
195
|
+
const response = await fetch(bundleUrl, {
|
|
196
|
+
credentials: 'include',
|
|
197
|
+
headers,
|
|
198
|
+
});
|
|
199
|
+
if (!response.ok) {
|
|
200
|
+
throw new Error(`Failed to fetch bundle: ${response.status} ${response.statusText}`);
|
|
201
|
+
}
|
|
202
|
+
const code = await response.text();
|
|
203
|
+
console.log('[BuilderProvider] Bundle fetched, size:', code.length);
|
|
204
|
+
// Create blob URL for dynamic import
|
|
205
|
+
const blob = new Blob([code], { type: 'application/javascript' });
|
|
206
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
207
|
+
try {
|
|
208
|
+
// Dynamic import the bundle as ES module
|
|
209
|
+
const module = await import(/* @vite-ignore */ blobUrl);
|
|
210
|
+
console.log('[BuilderProvider] Bundle imported, exports:', Object.keys(module));
|
|
211
|
+
// Get the App component (try App, then default export)
|
|
212
|
+
const App = module.App || module.default;
|
|
213
|
+
if (!App) {
|
|
214
|
+
throw new Error('Bundle has no App or default export');
|
|
215
|
+
}
|
|
216
|
+
// Store the component
|
|
217
|
+
setTenantApp(() => App);
|
|
218
|
+
setIsBundleLoaded(true);
|
|
219
|
+
console.log('[BuilderProvider] Bundle loaded successfully');
|
|
220
|
+
onBundleLoadComplete?.(bundleUrl);
|
|
221
|
+
}
|
|
222
|
+
finally {
|
|
223
|
+
// Clean up blob URL
|
|
224
|
+
URL.revokeObjectURL(blobUrl);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
console.error('[BuilderProvider] Bundle load failed:', error);
|
|
229
|
+
setBundleError(error);
|
|
230
|
+
onError?.(error);
|
|
231
|
+
// Don't throw - let the app continue with children
|
|
232
|
+
}
|
|
233
|
+
finally {
|
|
234
|
+
isBundleLoadingRef.current = false;
|
|
235
|
+
setIsBundleLoading(false);
|
|
236
|
+
}
|
|
237
|
+
}, [config, getAuthToken]);
|
|
210
238
|
const loadProjectBundle = useCallback(async (projectId, forceReload = false) => {
|
|
211
239
|
const { getBundleUrl, apiBase = 'http://localhost:8080' } = config;
|
|
212
240
|
let bundleUrl;
|
|
@@ -216,74 +244,63 @@ export function BuilderProvider({ children, config = {}, onBuilderReady, }) {
|
|
|
216
244
|
else {
|
|
217
245
|
bundleUrl = `${apiBase.replace(':8080', ':8081')}/bundles/preview?project_id=${projectId}`;
|
|
218
246
|
}
|
|
219
|
-
// Cache bust to force reload
|
|
247
|
+
// Cache bust to force reload
|
|
220
248
|
if (forceReload) {
|
|
221
|
-
bundleUrl +=
|
|
249
|
+
bundleUrl += (bundleUrl.includes('?') ? '&' : '?') + `_t=${Date.now()}`;
|
|
222
250
|
}
|
|
223
251
|
await loadBundle(bundleUrl);
|
|
224
252
|
}, [config, loadBundle]);
|
|
225
253
|
// Load saved (permanent) bundle - used for initial load, after save, and after abandon
|
|
226
|
-
//
|
|
227
|
-
const loadSavedBundle = useCallback(async (
|
|
254
|
+
// Note: projectId is for API consistency; actual project comes from JWT
|
|
255
|
+
const loadSavedBundle = useCallback(async (_projectId) => {
|
|
228
256
|
const { apiBase = 'http://localhost:8080' } = config;
|
|
229
|
-
// Use build-server's authenticated bundle endpoint
|
|
230
257
|
const bundleUrl = `${apiBase}/api/code/bundle`;
|
|
231
258
|
console.log('[BuilderProvider] Loading saved bundle via:', bundleUrl);
|
|
232
259
|
await loadBundle(bundleUrl);
|
|
233
|
-
console.log('[BuilderProvider] Saved bundle loaded');
|
|
234
260
|
}, [config, loadBundle]);
|
|
235
261
|
// Load temp bundle - used when AI finishes editing to preview changes
|
|
236
|
-
//
|
|
237
|
-
const loadTempBundle = useCallback(async (
|
|
262
|
+
// Note: projectId is for API consistency; actual project comes from JWT
|
|
263
|
+
const loadTempBundle = useCallback(async (_projectId) => {
|
|
238
264
|
const { apiBase = 'http://localhost:8080' } = config;
|
|
239
|
-
//
|
|
265
|
+
// Cache bust to force reload
|
|
240
266
|
const bundleUrl = `${apiBase}/api/code/bundle?_t=${Date.now()}`;
|
|
241
267
|
console.log('[BuilderProvider] Loading temp bundle via:', bundleUrl);
|
|
242
268
|
await loadBundle(bundleUrl);
|
|
243
|
-
console.log('[BuilderProvider] Temp bundle loaded');
|
|
244
269
|
}, [config, loadBundle]);
|
|
245
|
-
// Auto-
|
|
246
|
-
// Uses hasBundleManager as dependency so it re-runs when manager initializes
|
|
270
|
+
// Auto-load temp bundle when bundle is ready
|
|
247
271
|
useEffect(() => {
|
|
248
272
|
console.log('[BuilderProvider] Auto-load check:', {
|
|
249
273
|
bundleReady: codeMessage.bundleReady,
|
|
250
274
|
projectId: codeMessage.projectId,
|
|
251
|
-
hasBundleManager,
|
|
252
275
|
});
|
|
253
|
-
if (codeMessage.bundleReady && codeMessage.projectId
|
|
276
|
+
if (codeMessage.bundleReady && codeMessage.projectId) {
|
|
254
277
|
console.log('[BuilderProvider] Bundle ready - loading temp bundle:', codeMessage.projectId);
|
|
255
278
|
loadTempBundle(codeMessage.projectId);
|
|
256
279
|
}
|
|
257
|
-
}, [codeMessage.bundleReady, codeMessage.projectId,
|
|
258
|
-
|
|
259
|
-
bundleManagerRef.current?.applyStyles(selector, styles);
|
|
260
|
-
}, []);
|
|
261
|
-
const applyClassName = useCallback((selector, className) => {
|
|
262
|
-
bundleManagerRef.current?.applyClassName(selector, className);
|
|
263
|
-
}, []);
|
|
264
|
-
const toggleClasses = useCallback((selector, add, remove) => {
|
|
265
|
-
bundleManagerRef.current?.toggleClasses(selector, add, remove);
|
|
266
|
-
}, []);
|
|
280
|
+
}, [codeMessage.bundleReady, codeMessage.projectId, loadTempBundle]);
|
|
281
|
+
// Unload bundle - show children again
|
|
267
282
|
const unloadBundle = useCallback(() => {
|
|
268
|
-
|
|
283
|
+
setTenantApp(null);
|
|
284
|
+
setIsBundleLoaded(false);
|
|
285
|
+
console.log('[BuilderProvider] Bundle unloaded');
|
|
269
286
|
}, []);
|
|
270
|
-
// Code client methods
|
|
287
|
+
// Code client methods
|
|
271
288
|
const sendCode = useCallback(async (query) => {
|
|
272
289
|
await codeClientRef.current?.sendCode(query);
|
|
273
290
|
}, []);
|
|
274
291
|
const saveCode = useCallback(async () => {
|
|
275
292
|
// 1. Persist changes to backend (promotes temp to saved)
|
|
276
293
|
await codeClientRef.current?.saveCode();
|
|
277
|
-
// 2. Load the newly saved bundle
|
|
278
|
-
if (
|
|
294
|
+
// 2. Load the newly saved bundle
|
|
295
|
+
if (codeMessage.projectId) {
|
|
279
296
|
await loadSavedBundle(codeMessage.projectId);
|
|
280
297
|
}
|
|
281
298
|
}, [loadSavedBundle, codeMessage.projectId]);
|
|
282
299
|
const abandonCode = useCallback(async () => {
|
|
283
300
|
// 1. Discard temp changes on server
|
|
284
301
|
await codeClientRef.current?.abandonCode();
|
|
285
|
-
// 2. Revert to saved bundle
|
|
286
|
-
if (
|
|
302
|
+
// 2. Revert to saved bundle
|
|
303
|
+
if (codeMessage.projectId) {
|
|
287
304
|
await loadSavedBundle(codeMessage.projectId);
|
|
288
305
|
}
|
|
289
306
|
}, [loadSavedBundle, codeMessage.projectId]);
|
|
@@ -299,24 +316,19 @@ export function BuilderProvider({ children, config = {}, onBuilderReady, }) {
|
|
|
299
316
|
saveBuilder,
|
|
300
317
|
isBuilderActive,
|
|
301
318
|
getBuilder: () => builderRef.current,
|
|
302
|
-
// Code client methods
|
|
319
|
+
// Code client methods
|
|
303
320
|
sendCode,
|
|
304
321
|
saveCode,
|
|
305
322
|
abandonCode,
|
|
306
323
|
warmCache,
|
|
307
324
|
codeMessage,
|
|
308
325
|
resetCodeMessage,
|
|
309
|
-
// Deprecated - use codeMessage.edits instead
|
|
310
326
|
codeEdits: codeMessage.edits,
|
|
311
|
-
// Bundle
|
|
327
|
+
// Bundle methods
|
|
312
328
|
loadBundle,
|
|
313
329
|
loadProjectBundle,
|
|
314
330
|
loadSavedBundle,
|
|
315
331
|
loadTempBundle,
|
|
316
|
-
applyStyles,
|
|
317
|
-
applyClassName,
|
|
318
|
-
toggleClasses,
|
|
319
|
-
hasBundleManager,
|
|
320
332
|
isBundleLoaded,
|
|
321
333
|
isBundleLoading,
|
|
322
334
|
unloadBundle,
|
|
@@ -335,10 +347,6 @@ export function BuilderProvider({ children, config = {}, onBuilderReady, }) {
|
|
|
335
347
|
loadProjectBundle,
|
|
336
348
|
loadSavedBundle,
|
|
337
349
|
loadTempBundle,
|
|
338
|
-
applyStyles,
|
|
339
|
-
applyClassName,
|
|
340
|
-
toggleClasses,
|
|
341
|
-
hasBundleManager,
|
|
342
350
|
isBundleLoaded,
|
|
343
351
|
isBundleLoading,
|
|
344
352
|
unloadBundle,
|
|
@@ -351,14 +359,30 @@ export function BuilderProvider({ children, config = {}, onBuilderReady, }) {
|
|
|
351
359
|
onBuilderReady(contextValue);
|
|
352
360
|
}
|
|
353
361
|
}, [contextValue, onBuilderReady]);
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
if (
|
|
358
|
-
|
|
362
|
+
// Render the appropriate content based on state
|
|
363
|
+
const renderContent = () => {
|
|
364
|
+
// Loading state
|
|
365
|
+
if (isBundleLoading) {
|
|
366
|
+
return children; // Show children while loading (no flash)
|
|
359
367
|
}
|
|
360
|
-
|
|
361
|
-
|
|
368
|
+
// Error state - show error banner with children
|
|
369
|
+
if (bundleError) {
|
|
370
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { style: {
|
|
371
|
+
padding: 12,
|
|
372
|
+
background: '#fef3c7',
|
|
373
|
+
borderBottom: '1px solid #fcd34d',
|
|
374
|
+
fontSize: 13,
|
|
375
|
+
color: '#92400e',
|
|
376
|
+
}, children: ["Bundle error: ", bundleError.message] }), children] }));
|
|
377
|
+
}
|
|
378
|
+
// Bundle loaded - render tenant app
|
|
379
|
+
if (TenantApp) {
|
|
380
|
+
return _jsx(TenantApp, {});
|
|
381
|
+
}
|
|
382
|
+
// Default - render children
|
|
383
|
+
return children;
|
|
384
|
+
};
|
|
385
|
+
return (_jsx(BuilderContext.Provider, { value: contextValue, children: renderContent() }));
|
|
362
386
|
}
|
|
363
387
|
/**
|
|
364
388
|
* Hook to access builder functionality
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sharedContext.d.ts","sourceRoot":"","sources":["../../src/inference/sharedContext.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3D;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,cAAc,GAAG,WAAW,
|
|
1
|
+
{"version":3,"file":"sharedContext.d.ts","sourceRoot":"","sources":["../../src/inference/sharedContext.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3D;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,cAAc,GAAG,WAAW,CA8BvE;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,IAAI,WAAW,GAAG,SAAS,CAK1D;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CAE9C"}
|
|
@@ -22,18 +22,26 @@
|
|
|
22
22
|
* ```
|
|
23
23
|
*/
|
|
24
24
|
export function createSharedContext(config) {
|
|
25
|
-
const { React, ReactDOM, hooks = {}, contexts = {}, globals = {} } = config;
|
|
25
|
+
const { React, ReactDOM, hooks = {}, contexts = {}, packages = {}, globals = {} } = config;
|
|
26
|
+
// Always include React and ReactDOM in packages for bundle access
|
|
27
|
+
const allPackages = {
|
|
28
|
+
'react': React,
|
|
29
|
+
'react-dom': ReactDOM,
|
|
30
|
+
'react-dom/client': ReactDOM,
|
|
31
|
+
...packages,
|
|
32
|
+
};
|
|
26
33
|
const sdk = {
|
|
27
34
|
React,
|
|
28
35
|
ReactDOM,
|
|
29
36
|
hooks,
|
|
30
37
|
contexts,
|
|
38
|
+
packages: allPackages,
|
|
31
39
|
...globals,
|
|
32
40
|
};
|
|
33
41
|
// Register on window for tenant bundles to access
|
|
34
42
|
if (typeof window !== 'undefined') {
|
|
35
43
|
window.__DYNIM__ = sdk;
|
|
36
|
-
// Also expose React and ReactDOM directly on window for bundles
|
|
44
|
+
// Also expose React and ReactDOM directly on window for legacy bundles
|
|
37
45
|
window.React = React;
|
|
38
46
|
window.ReactDOM = ReactDOM;
|
|
39
47
|
}
|
|
@@ -11,6 +11,8 @@ export interface DynimSDKConfig {
|
|
|
11
11
|
hooks?: Record<string, unknown>;
|
|
12
12
|
/** React contexts to expose to tenant bundles */
|
|
13
13
|
contexts?: Record<string, unknown>;
|
|
14
|
+
/** NPM packages to expose to tenant bundles (e.g., react-router-dom, axios) */
|
|
15
|
+
packages?: Record<string, unknown>;
|
|
14
16
|
/** Additional globals to expose */
|
|
15
17
|
globals?: Record<string, unknown>;
|
|
16
18
|
}
|
|
@@ -57,6 +59,7 @@ export interface DynimGlobal {
|
|
|
57
59
|
ReactDOM: typeof import('react-dom');
|
|
58
60
|
hooks: Record<string, unknown>;
|
|
59
61
|
contexts: Record<string, unknown>;
|
|
62
|
+
packages: Record<string, unknown>;
|
|
60
63
|
[key: string]: unknown;
|
|
61
64
|
}
|
|
62
65
|
declare global {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/inference/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,gDAAgD;IAChD,KAAK,EAAE,cAAc,OAAO,CAAC,CAAC;IAC9B,mDAAmD;IACnD,QAAQ,EAAE,cAAc,WAAW,CAAC,CAAC;IACrC,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,iDAAiD;IACjD,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,mCAAmC;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,2CAA2C;IAC3C,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,6DAA6D;IAC7D,SAAS,EAAE,MAAM,GAAG,CAAC,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAC,CAAC;IACnD;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mFAAmF;IACnF,mBAAmB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7D,+DAA+D;IAC/D,gBAAgB,CAAC,EAAE,SAAS,CAAC;IAC7B,4EAA4E;IAC5E,WAAW,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAClC,yCAAyC;IACzC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,8CAA8C;IAC9C,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,mEAAmE;IACnE,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,kBAAkB,CAAC;IAClF,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,cAAc,OAAO,CAAC,CAAC;IAC9B,QAAQ,EAAE,cAAc,WAAW,CAAC,CAAC;IACrC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,SAAS,CAAC,EAAE,WAAW,CAAC;KACzB;CACF"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/inference/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,gDAAgD;IAChD,KAAK,EAAE,cAAc,OAAO,CAAC,CAAC;IAC9B,mDAAmD;IACnD,QAAQ,EAAE,cAAc,WAAW,CAAC,CAAC;IACrC,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,iDAAiD;IACjD,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,mCAAmC;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,2CAA2C;IAC3C,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,6DAA6D;IAC7D,SAAS,EAAE,MAAM,GAAG,CAAC,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAC,CAAC;IACnD;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mFAAmF;IACnF,mBAAmB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7D,+DAA+D;IAC/D,gBAAgB,CAAC,EAAE,SAAS,CAAC;IAC7B,4EAA4E;IAC5E,WAAW,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAClC,yCAAyC;IACzC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,8CAA8C;IAC9C,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,mEAAmE;IACnE,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,kBAAkB,CAAC;IAClF,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,cAAc,OAAO,CAAC,CAAC;IAC9B,QAAQ,EAAE,cAAc,WAAW,CAAC,CAAC;IACrC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,SAAS,CAAC,EAAE,WAAW,CAAC;KACzB;CACF"}
|