dynim-react 1.0.7 → 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.
|
@@ -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,18 +28,8 @@ 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 */
|
|
@@ -66,7 +57,7 @@ export interface BuilderContextValue {
|
|
|
66
57
|
resetCodeMessage: () => void;
|
|
67
58
|
/** @deprecated Use codeMessage.edits instead */
|
|
68
59
|
codeEdits: CodeEdit[];
|
|
69
|
-
/** Load a bundle
|
|
60
|
+
/** Load a bundle by URL */
|
|
70
61
|
loadBundle: (bundleUrl: string) => Promise<void>;
|
|
71
62
|
/** Load bundle for a project (uses getBundleUrl from config) */
|
|
72
63
|
loadProjectBundle: (projectId: string) => Promise<void>;
|
|
@@ -74,19 +65,11 @@ export interface BuilderContextValue {
|
|
|
74
65
|
loadSavedBundle: (projectId: string) => Promise<void>;
|
|
75
66
|
/** Load temp bundle for a project (AI edits preview) */
|
|
76
67
|
loadTempBundle: (projectId: string) => Promise<void>;
|
|
77
|
-
/** Apply styles directly to preview DOM */
|
|
78
|
-
applyStyles: (selector: string, styles: Record<string, string>) => void;
|
|
79
|
-
/** Apply className to preview DOM */
|
|
80
|
-
applyClassName: (selector: string, className: string) => void;
|
|
81
|
-
/** Toggle CSS classes on preview DOM */
|
|
82
|
-
toggleClasses: (selector: string, add: string[], remove: string[]) => void;
|
|
83
|
-
/** Check if bundle manager is available */
|
|
84
|
-
hasBundleManager: boolean;
|
|
85
68
|
/** Check if a bundle is currently loaded */
|
|
86
69
|
isBundleLoaded: boolean;
|
|
87
70
|
/** Check if bundle is loading */
|
|
88
71
|
isBundleLoading: boolean;
|
|
89
|
-
/** Unload current bundle */
|
|
72
|
+
/** Unload current bundle (show children again) */
|
|
90
73
|
unloadBundle: () => void;
|
|
91
74
|
}
|
|
92
75
|
export interface BuilderProviderProps {
|
|
@@ -96,6 +79,9 @@ export interface BuilderProviderProps {
|
|
|
96
79
|
}
|
|
97
80
|
/**
|
|
98
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.
|
|
99
85
|
*/
|
|
100
86
|
export declare function BuilderProvider({ children, config, onBuilderReady, }: BuilderProviderProps): JSX.Element;
|
|
101
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,29 +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
9
|
import React, { createContext, useContext, useEffect, useRef, useCallback, useMemo, useState, } from 'react';
|
|
9
10
|
import ReactDOM from 'react-dom';
|
|
10
|
-
import { createBuilderClient, createBuilder, createCodeClient,
|
|
11
|
+
import { createBuilderClient, createBuilder, createCodeClient, } from 'dynim-core';
|
|
11
12
|
import { createSharedContext } from '../inference/sharedContext';
|
|
12
13
|
const BuilderContext = createContext(null);
|
|
13
14
|
/**
|
|
14
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.
|
|
15
19
|
*/
|
|
16
20
|
export function BuilderProvider({ children, config = {}, onBuilderReady, }) {
|
|
17
21
|
const builderClientRef = useRef(null);
|
|
18
22
|
const builderRef = useRef(null);
|
|
19
23
|
const codeClientRef = useRef(null);
|
|
20
|
-
const bundleManagerRef = useRef(null);
|
|
21
|
-
const styleApplierRef = useRef(null);
|
|
22
|
-
const [internalContainer, setInternalContainer] = useState(null);
|
|
23
24
|
const [isBuilderActive, setIsBuilderActive] = useState(false);
|
|
24
25
|
const [isBundleLoaded, setIsBundleLoaded] = useState(false);
|
|
25
26
|
const [isBundleLoading, setIsBundleLoading] = useState(false);
|
|
26
|
-
const [hasBundleManager, setHasBundleManager] = useState(false);
|
|
27
27
|
const [codeMessage, setCodeMessage] = useState({
|
|
28
28
|
thinking: '',
|
|
29
29
|
text: '',
|
|
@@ -32,22 +32,35 @@ export function BuilderProvider({ children, config = {}, onBuilderReady, }) {
|
|
|
32
32
|
bundleReady: false,
|
|
33
33
|
bundleError: undefined,
|
|
34
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);
|
|
35
39
|
// Set up shared context for bundles (exposes React, ReactDOM, packages, hooks, contexts)
|
|
36
40
|
useEffect(() => {
|
|
37
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
|
+
};
|
|
38
48
|
createSharedContext({
|
|
39
49
|
React,
|
|
40
50
|
ReactDOM,
|
|
41
|
-
packages
|
|
51
|
+
packages: {
|
|
52
|
+
'dynim-react': sdkExports,
|
|
53
|
+
...packages,
|
|
54
|
+
},
|
|
42
55
|
hooks,
|
|
43
56
|
contexts,
|
|
44
57
|
});
|
|
45
|
-
console.log('[BuilderProvider] Shared context initialized with packages:', Object.keys(packages));
|
|
58
|
+
console.log('[BuilderProvider] Shared context initialized with packages:', ['dynim-react', ...Object.keys(packages)]);
|
|
46
59
|
}, [config]);
|
|
47
60
|
// Initialize code client FIRST (before builder, so we can share it)
|
|
48
61
|
useEffect(() => {
|
|
49
62
|
const { apiBase = 'http://localhost:8080', sessionToken, refreshToken, getSession, onCodeMessageUpdate, onCodeMessage, // deprecated
|
|
50
|
-
onCodeEdit, onError,
|
|
63
|
+
onCodeEdit, onError, } = config;
|
|
51
64
|
codeClientRef.current = createCodeClient({
|
|
52
65
|
apiBase,
|
|
53
66
|
sessionToken,
|
|
@@ -65,11 +78,6 @@ export function BuilderProvider({ children, config = {}, onBuilderReady, }) {
|
|
|
65
78
|
},
|
|
66
79
|
onEdit: (edit) => {
|
|
67
80
|
console.log('[BuilderProvider] Code edit:', edit);
|
|
68
|
-
// Auto-apply style changes if enabled and style applier is available
|
|
69
|
-
if (autoApplyStyles && styleApplierRef.current && hasStyleChanges(edit)) {
|
|
70
|
-
const result = styleApplierRef.current.processEdit(edit);
|
|
71
|
-
console.log('[BuilderProvider] Style changes:', result.changes.length, 'applied:', result.applied);
|
|
72
|
-
}
|
|
73
81
|
onCodeEdit?.(edit);
|
|
74
82
|
},
|
|
75
83
|
onError: (error) => {
|
|
@@ -121,80 +129,6 @@ export function BuilderProvider({ children, config = {}, onBuilderReady, }) {
|
|
|
121
129
|
},
|
|
122
130
|
});
|
|
123
131
|
}, [config]);
|
|
124
|
-
// Initialize bundle manager
|
|
125
|
-
useEffect(() => {
|
|
126
|
-
const { previewContainer, inPlacePreview = true, previewTransitionDuration = 300, sessionToken, getSession, onStyleApplied, onBundleLoadStart, onBundleLoadComplete, onError, } = config;
|
|
127
|
-
// Determine which container to use
|
|
128
|
-
const container = inPlacePreview ? internalContainer : previewContainer;
|
|
129
|
-
console.log('[BuilderProvider] Bundle manager init:', { inPlacePreview, hasInternalContainer: !!internalContainer, hasPreviewContainer: !!previewContainer });
|
|
130
|
-
if (!container) {
|
|
131
|
-
// For inPlacePreview, container may not be ready on first render
|
|
132
|
-
console.log('[BuilderProvider] No container yet, skipping bundle manager init');
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
// Create auth token getter for bundle requests
|
|
136
|
-
const getAuthToken = async () => {
|
|
137
|
-
// First try config sessionToken
|
|
138
|
-
if (sessionToken) {
|
|
139
|
-
return sessionToken;
|
|
140
|
-
}
|
|
141
|
-
// Then try getSession callback
|
|
142
|
-
if (getSession) {
|
|
143
|
-
try {
|
|
144
|
-
const session = await getSession();
|
|
145
|
-
return session.token;
|
|
146
|
-
}
|
|
147
|
-
catch (e) {
|
|
148
|
-
console.warn('[BuilderProvider] Failed to get session for bundle auth:', e);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
return null;
|
|
152
|
-
};
|
|
153
|
-
bundleManagerRef.current = createBundleManager({
|
|
154
|
-
parent: container,
|
|
155
|
-
transitionDuration: previewTransitionDuration,
|
|
156
|
-
getAuthToken,
|
|
157
|
-
onLoadStart: (url) => {
|
|
158
|
-
setIsBundleLoading(true);
|
|
159
|
-
onBundleLoadStart?.(url);
|
|
160
|
-
},
|
|
161
|
-
onLoadComplete: (url) => {
|
|
162
|
-
setIsBundleLoading(false);
|
|
163
|
-
setIsBundleLoaded(true);
|
|
164
|
-
onBundleLoadComplete?.(url);
|
|
165
|
-
},
|
|
166
|
-
onError: (error) => {
|
|
167
|
-
setIsBundleLoading(false);
|
|
168
|
-
console.error('[BuilderProvider] Bundle load error:', error);
|
|
169
|
-
onError?.(error);
|
|
170
|
-
},
|
|
171
|
-
onUnload: () => {
|
|
172
|
-
setIsBundleLoaded(false);
|
|
173
|
-
},
|
|
174
|
-
});
|
|
175
|
-
setHasBundleManager(true);
|
|
176
|
-
console.log('[BuilderProvider] Bundle manager created');
|
|
177
|
-
// Create style applier that targets the bundle manager's container
|
|
178
|
-
styleApplierRef.current = createStyleApplier({
|
|
179
|
-
queryElements: (selector) => {
|
|
180
|
-
const container = bundleManagerRef.current?.getActiveContainer();
|
|
181
|
-
return container?.querySelectorAll(selector) ?? document.querySelectorAll(selector);
|
|
182
|
-
},
|
|
183
|
-
onApply: (change, elements) => {
|
|
184
|
-
console.log('[BuilderProvider] Style applied:', change, elements.length, 'elements');
|
|
185
|
-
onStyleApplied?.(change, elements);
|
|
186
|
-
},
|
|
187
|
-
onError: (change, error) => {
|
|
188
|
-
console.error('[BuilderProvider] Style apply error:', error, change);
|
|
189
|
-
},
|
|
190
|
-
});
|
|
191
|
-
return () => {
|
|
192
|
-
bundleManagerRef.current?.destroy();
|
|
193
|
-
bundleManagerRef.current = null;
|
|
194
|
-
styleApplierRef.current = null;
|
|
195
|
-
setHasBundleManager(false);
|
|
196
|
-
};
|
|
197
|
-
}, [config, internalContainer]);
|
|
198
132
|
// Enter builder mode - activates the visual builder UI
|
|
199
133
|
const enterBuilder = useCallback(() => {
|
|
200
134
|
if (builderRef.current && !builderRef.current.isActive()) {
|
|
@@ -213,14 +147,94 @@ export function BuilderProvider({ children, config = {}, onBuilderReady, }) {
|
|
|
213
147
|
await builderRef.current.save();
|
|
214
148
|
}
|
|
215
149
|
}, []);
|
|
216
|
-
|
|
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
|
+
*/
|
|
217
175
|
const loadBundle = useCallback(async (bundleUrl) => {
|
|
218
|
-
|
|
219
|
-
|
|
176
|
+
const { onBundleLoadStart, onBundleLoadComplete, onError } = config;
|
|
177
|
+
if (isBundleLoadingRef.current) {
|
|
178
|
+
console.warn('[BuilderProvider] Already loading a bundle');
|
|
220
179
|
return;
|
|
221
180
|
}
|
|
222
|
-
|
|
223
|
-
|
|
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]);
|
|
224
238
|
const loadProjectBundle = useCallback(async (projectId, forceReload = false) => {
|
|
225
239
|
const { getBundleUrl, apiBase = 'http://localhost:8080' } = config;
|
|
226
240
|
let bundleUrl;
|
|
@@ -230,74 +244,63 @@ export function BuilderProvider({ children, config = {}, onBuilderReady, }) {
|
|
|
230
244
|
else {
|
|
231
245
|
bundleUrl = `${apiBase.replace(':8080', ':8081')}/bundles/preview?project_id=${projectId}`;
|
|
232
246
|
}
|
|
233
|
-
// Cache bust to force reload
|
|
247
|
+
// Cache bust to force reload
|
|
234
248
|
if (forceReload) {
|
|
235
|
-
bundleUrl +=
|
|
249
|
+
bundleUrl += (bundleUrl.includes('?') ? '&' : '?') + `_t=${Date.now()}`;
|
|
236
250
|
}
|
|
237
251
|
await loadBundle(bundleUrl);
|
|
238
252
|
}, [config, loadBundle]);
|
|
239
253
|
// Load saved (permanent) bundle - used for initial load, after save, and after abandon
|
|
240
|
-
//
|
|
241
|
-
const loadSavedBundle = useCallback(async (
|
|
254
|
+
// Note: projectId is for API consistency; actual project comes from JWT
|
|
255
|
+
const loadSavedBundle = useCallback(async (_projectId) => {
|
|
242
256
|
const { apiBase = 'http://localhost:8080' } = config;
|
|
243
|
-
// Use build-server's authenticated bundle endpoint
|
|
244
257
|
const bundleUrl = `${apiBase}/api/code/bundle`;
|
|
245
258
|
console.log('[BuilderProvider] Loading saved bundle via:', bundleUrl);
|
|
246
259
|
await loadBundle(bundleUrl);
|
|
247
|
-
console.log('[BuilderProvider] Saved bundle loaded');
|
|
248
260
|
}, [config, loadBundle]);
|
|
249
261
|
// Load temp bundle - used when AI finishes editing to preview changes
|
|
250
|
-
//
|
|
251
|
-
const loadTempBundle = useCallback(async (
|
|
262
|
+
// Note: projectId is for API consistency; actual project comes from JWT
|
|
263
|
+
const loadTempBundle = useCallback(async (_projectId) => {
|
|
252
264
|
const { apiBase = 'http://localhost:8080' } = config;
|
|
253
|
-
//
|
|
265
|
+
// Cache bust to force reload
|
|
254
266
|
const bundleUrl = `${apiBase}/api/code/bundle?_t=${Date.now()}`;
|
|
255
267
|
console.log('[BuilderProvider] Loading temp bundle via:', bundleUrl);
|
|
256
268
|
await loadBundle(bundleUrl);
|
|
257
|
-
console.log('[BuilderProvider] Temp bundle loaded');
|
|
258
269
|
}, [config, loadBundle]);
|
|
259
|
-
// Auto-
|
|
260
|
-
// Uses hasBundleManager as dependency so it re-runs when manager initializes
|
|
270
|
+
// Auto-load temp bundle when bundle is ready
|
|
261
271
|
useEffect(() => {
|
|
262
272
|
console.log('[BuilderProvider] Auto-load check:', {
|
|
263
273
|
bundleReady: codeMessage.bundleReady,
|
|
264
274
|
projectId: codeMessage.projectId,
|
|
265
|
-
hasBundleManager,
|
|
266
275
|
});
|
|
267
|
-
if (codeMessage.bundleReady && codeMessage.projectId
|
|
276
|
+
if (codeMessage.bundleReady && codeMessage.projectId) {
|
|
268
277
|
console.log('[BuilderProvider] Bundle ready - loading temp bundle:', codeMessage.projectId);
|
|
269
278
|
loadTempBundle(codeMessage.projectId);
|
|
270
279
|
}
|
|
271
|
-
}, [codeMessage.bundleReady, codeMessage.projectId,
|
|
272
|
-
|
|
273
|
-
bundleManagerRef.current?.applyStyles(selector, styles);
|
|
274
|
-
}, []);
|
|
275
|
-
const applyClassName = useCallback((selector, className) => {
|
|
276
|
-
bundleManagerRef.current?.applyClassName(selector, className);
|
|
277
|
-
}, []);
|
|
278
|
-
const toggleClasses = useCallback((selector, add, remove) => {
|
|
279
|
-
bundleManagerRef.current?.toggleClasses(selector, add, remove);
|
|
280
|
-
}, []);
|
|
280
|
+
}, [codeMessage.bundleReady, codeMessage.projectId, loadTempBundle]);
|
|
281
|
+
// Unload bundle - show children again
|
|
281
282
|
const unloadBundle = useCallback(() => {
|
|
282
|
-
|
|
283
|
+
setTenantApp(null);
|
|
284
|
+
setIsBundleLoaded(false);
|
|
285
|
+
console.log('[BuilderProvider] Bundle unloaded');
|
|
283
286
|
}, []);
|
|
284
|
-
// Code client methods
|
|
287
|
+
// Code client methods
|
|
285
288
|
const sendCode = useCallback(async (query) => {
|
|
286
289
|
await codeClientRef.current?.sendCode(query);
|
|
287
290
|
}, []);
|
|
288
291
|
const saveCode = useCallback(async () => {
|
|
289
292
|
// 1. Persist changes to backend (promotes temp to saved)
|
|
290
293
|
await codeClientRef.current?.saveCode();
|
|
291
|
-
// 2. Load the newly saved bundle
|
|
292
|
-
if (
|
|
294
|
+
// 2. Load the newly saved bundle
|
|
295
|
+
if (codeMessage.projectId) {
|
|
293
296
|
await loadSavedBundle(codeMessage.projectId);
|
|
294
297
|
}
|
|
295
298
|
}, [loadSavedBundle, codeMessage.projectId]);
|
|
296
299
|
const abandonCode = useCallback(async () => {
|
|
297
300
|
// 1. Discard temp changes on server
|
|
298
301
|
await codeClientRef.current?.abandonCode();
|
|
299
|
-
// 2. Revert to saved bundle
|
|
300
|
-
if (
|
|
302
|
+
// 2. Revert to saved bundle
|
|
303
|
+
if (codeMessage.projectId) {
|
|
301
304
|
await loadSavedBundle(codeMessage.projectId);
|
|
302
305
|
}
|
|
303
306
|
}, [loadSavedBundle, codeMessage.projectId]);
|
|
@@ -313,24 +316,19 @@ export function BuilderProvider({ children, config = {}, onBuilderReady, }) {
|
|
|
313
316
|
saveBuilder,
|
|
314
317
|
isBuilderActive,
|
|
315
318
|
getBuilder: () => builderRef.current,
|
|
316
|
-
// Code client methods
|
|
319
|
+
// Code client methods
|
|
317
320
|
sendCode,
|
|
318
321
|
saveCode,
|
|
319
322
|
abandonCode,
|
|
320
323
|
warmCache,
|
|
321
324
|
codeMessage,
|
|
322
325
|
resetCodeMessage,
|
|
323
|
-
// Deprecated - use codeMessage.edits instead
|
|
324
326
|
codeEdits: codeMessage.edits,
|
|
325
|
-
// Bundle
|
|
327
|
+
// Bundle methods
|
|
326
328
|
loadBundle,
|
|
327
329
|
loadProjectBundle,
|
|
328
330
|
loadSavedBundle,
|
|
329
331
|
loadTempBundle,
|
|
330
|
-
applyStyles,
|
|
331
|
-
applyClassName,
|
|
332
|
-
toggleClasses,
|
|
333
|
-
hasBundleManager,
|
|
334
332
|
isBundleLoaded,
|
|
335
333
|
isBundleLoading,
|
|
336
334
|
unloadBundle,
|
|
@@ -349,10 +347,6 @@ export function BuilderProvider({ children, config = {}, onBuilderReady, }) {
|
|
|
349
347
|
loadProjectBundle,
|
|
350
348
|
loadSavedBundle,
|
|
351
349
|
loadTempBundle,
|
|
352
|
-
applyStyles,
|
|
353
|
-
applyClassName,
|
|
354
|
-
toggleClasses,
|
|
355
|
-
hasBundleManager,
|
|
356
350
|
isBundleLoaded,
|
|
357
351
|
isBundleLoading,
|
|
358
352
|
unloadBundle,
|
|
@@ -365,14 +359,30 @@ export function BuilderProvider({ children, config = {}, onBuilderReady, }) {
|
|
|
365
359
|
onBuilderReady(contextValue);
|
|
366
360
|
}
|
|
367
361
|
}, [contextValue, onBuilderReady]);
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
if (
|
|
372
|
-
|
|
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)
|
|
373
367
|
}
|
|
374
|
-
|
|
375
|
-
|
|
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() }));
|
|
376
386
|
}
|
|
377
387
|
/**
|
|
378
388
|
* Hook to access builder functionality
|