@bravostudioai/react 0.1.15 → 0.1.17

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.
@@ -85,12 +85,14 @@ async function generateWrapper({
85
85
  outputPath,
86
86
  cachedAppData,
87
87
  isProduction,
88
+ usedNames,
88
89
  }: {
89
90
  appId: string;
90
91
  pageId: string;
91
92
  outputPath: string;
92
93
  cachedAppData?: any;
93
94
  isProduction?: boolean;
95
+ usedNames?: Set<string>;
94
96
  }) {
95
97
  console.log(`Generating wrapper for app: ${appId}, page: ${pageId}`);
96
98
 
@@ -249,7 +251,27 @@ async function generateWrapper({
249
251
  pageName = pageData.name || pageData.id || pageName;
250
252
 
251
253
  // Generate component name and directory name
252
- const { directoryPath, componentName } = generateNames(appName, pageName);
254
+ let { directoryPath, componentName } = generateNames(appName, pageName);
255
+
256
+ // Ensure unique component name
257
+ if (usedNames) {
258
+ let uniqueName = componentName;
259
+ let counter = 2;
260
+ while (usedNames.has(uniqueName)) {
261
+ uniqueName = `${componentName}${counter}`;
262
+ counter++;
263
+ }
264
+ if (uniqueName !== componentName) {
265
+ componentName = uniqueName;
266
+ // Reconstitute directory path with new component name
267
+ // generateNames uses: join(appPascal, pagePascal) where pagePascal is componentName
268
+ const appCamel = sanitizePropName(appName);
269
+ const appPascal = appCamel.charAt(0).toUpperCase() + appCamel.slice(1);
270
+ directoryPath = join(appPascal, componentName);
271
+ }
272
+ usedNames.add(componentName);
273
+ }
274
+
253
275
  const directoryName = directoryPath;
254
276
 
255
277
  // Update output path
@@ -271,7 +293,15 @@ async function generateWrapper({
271
293
  forms,
272
294
  selectInputs,
273
295
  actionButtons,
274
- !!isProduction
296
+ !!isProduction,
297
+ {
298
+ width: pageData.style?.width,
299
+ height: pageData.style?.height,
300
+ aspectRatio:
301
+ pageData.style?.width && pageData.style?.height
302
+ ? pageData.style.width / pageData.style.height
303
+ : undefined,
304
+ }
275
305
  );
276
306
 
277
307
  const readmeContent = generateReadme(
@@ -373,6 +403,7 @@ export async function runGenerate(args: string[]) {
373
403
  return;
374
404
  }
375
405
 
406
+ const usedNames = new Set<string>();
376
407
  for (const page of pages) {
377
408
  if (!page.id) continue;
378
409
  try {
@@ -382,6 +413,7 @@ export async function runGenerate(args: string[]) {
382
413
  outputPath,
383
414
  cachedAppData: appData,
384
415
  isProduction,
416
+ usedNames,
385
417
  });
386
418
  } catch (error) {
387
419
  console.warn(
@@ -400,7 +432,7 @@ export async function runGenerate(args: string[]) {
400
432
  const [appId, pageId, outputPath] = cleanArgs;
401
433
 
402
434
  try {
403
- await generateWrapper({ appId, pageId, outputPath, isProduction });
435
+ await generateWrapper({ appId, pageId, outputPath, isProduction }); // Single page generation, no collision context needed unless we wanted global uniqueness but usually used for one-off
404
436
  } catch (error) {
405
437
  console.error("\nError:", error instanceof Error ? error.message : error);
406
438
  process.exit(1);
@@ -28,7 +28,12 @@ export function generateComponentCode(
28
28
  forms: FormInfo[],
29
29
  selectInputs: SelectInputInfo[],
30
30
  actionButtons: ActionButtonInfo[],
31
- isProduction: boolean = false
31
+ isProduction: boolean = false,
32
+ pageMeta?: {
33
+ width?: number;
34
+ height?: number;
35
+ aspectRatio?: number;
36
+ }
32
37
  ): string {
33
38
  // Generate prop types
34
39
  const propTypes: string[] = [];
@@ -439,6 +444,12 @@ ${dataMapping.join("\n")}
439
444
  }
440
445
 
441
446
  export default ${componentName};
447
+
448
+ export const PageMeta = {
449
+ width: ${pageMeta?.width ?? "undefined"},
450
+ height: ${pageMeta?.height ?? "undefined"},
451
+ aspectRatio: ${pageMeta?.aspectRatio ?? "undefined"},
452
+ };
442
453
  `;
443
454
  }
444
455
 
@@ -828,7 +828,15 @@ const EncoreApp = ({
828
828
  overflow: "hidden",
829
829
  }}
830
830
  >
831
- <div ref={contentWrapperRef} style={{ width: "100%", minHeight: "100%" }}>
831
+ <div
832
+ ref={contentWrapperRef}
833
+ style={{
834
+ width: "100%",
835
+ height: "100%",
836
+ display: "flex",
837
+ flexDirection: "column",
838
+ }}
839
+ >
832
840
  <Suspense fallback={fallback || <div />}>
833
841
  <EncoreComponentIdContext.Provider value={{ componentId }}>
834
842
  <EncoreActionContext.Provider value={{ onAction }}>
@@ -4,104 +4,102 @@ import useEncoreState from "../stores/useEncoreState";
4
4
 
5
5
  // Get baseURL from store at runtime instead of build time
6
6
  const getAppsServiceUrl = () => {
7
- return useEncoreState.getState().baseURL;
7
+ return useEncoreState.getState().baseURL;
8
8
  };
9
9
 
10
10
  const bundledResponses: Record<string, string> = {};
11
11
 
12
12
  const fetcher = (url: string) => {
13
- // Local mode: map Encore service URLs to files in /flex-layout
14
- const forceLocal = /\buseLocal=1\b/.test(url);
15
- if (forceLocal || isLocalMode()) {
16
- const pathOnly = url.split("?")[0];
17
- const absBase = null;
18
- // /devices/apps/:appId
19
- const appMatch = pathOnly.match(/^\/devices\/apps\/([^/]+)$/);
20
- if (appMatch) {
21
- const appId = appMatch[1];
22
- const tryUrls = [
23
- `/flex-layout/${appId}/${appId}.json`,
24
- absBase
25
- ? `/@fs/${absBase}/${appId}/${appId}.json`
26
- : undefined,
27
- ].filter(Boolean) as string[];
28
- return (async () => {
29
- for (const u of tryUrls) {
30
- try {
31
- const r = await fetch(u);
32
- if (r.ok) return r.json();
33
- } catch {
34
- // try next
35
- }
36
- }
37
- throw new Error(
38
- `Local app.json not found for ${appId} (tried ${tryUrls.join(
39
- ", ",
40
- )})`,
41
- );
42
- })();
13
+ // Local mode: map Encore service URLs to files in /flex-layout
14
+ const forceLocal = /\buseLocal=1\b/.test(url);
15
+ if (forceLocal || isLocalMode()) {
16
+ const pathOnly = url.split("?")[0];
17
+ const absBase = null;
18
+ // /devices/apps/:appId
19
+ const appMatch = pathOnly.match(/^\/devices\/apps\/([^/]+)$/);
20
+ if (appMatch) {
21
+ const appId = appMatch[1];
22
+ const tryUrls = [
23
+ `/flex-layout/${appId}/${appId}.json`,
24
+ absBase ? `/@fs/${absBase}/${appId}/${appId}.json` : undefined,
25
+ ].filter(Boolean) as string[];
26
+ return (async () => {
27
+ for (const u of tryUrls) {
28
+ try {
29
+ const r = await fetch(u);
30
+ if (r.ok) return r.json();
31
+ } catch {
32
+ // try next
33
+ }
43
34
  }
44
- // /devices/apps/:appId/node/:pageId
45
- const pageMatch = pathOnly.match(
46
- /^\/devices\/apps\/([^/]+)\/node\/([^/?#]+)$/,
35
+ throw new Error(
36
+ `Local app.json not found for ${appId} (tried ${tryUrls.join(", ")})`
47
37
  );
48
- if (pageMatch) {
49
- const appId = pageMatch[1];
50
- const pageId = pageMatch[2];
51
- // Try a dedicated page JSON first (if present)
52
- const tryUrls = [
53
- `/flex-layout/${appId}/${pageId}.json`,
54
- absBase
55
- ? `/@fs/${absBase}/${appId}/${pageId}.json`
56
- : undefined,
57
- ].filter(Boolean) as string[];
58
- return fetch(tryUrls[0])
59
- .then(async (r) => {
60
- if (r.ok) return r.json();
61
- // try secondary url if defined
62
- if (tryUrls[1]) {
63
- const r2 = await fetch(tryUrls[1]).catch(() => null);
64
- if (r2 && r2.ok) return r2.json();
65
- }
66
- // Fallback: derive minimal shape from app.json
67
- const appJson =
68
- (await fetch(`/flex-layout/${appId}/${appId}.json`)
69
- .then((a) => (a.ok ? a.json() : null))
70
- .catch(() => null)) ||
71
- (absBase
72
- ? await fetch(
73
- `/@fs/${absBase}/${appId}/${appId}.json`,
74
- )
75
- .then((a) => (a.ok ? a.json() : null))
76
- .catch(() => null)
77
- : null);
78
- // Keep shape compatible with current consumers
79
- const pages =
80
- appJson?.app?.data?.pages ||
81
- appJson?.data?.pages ||
82
- [];
83
- const page = pages.find((p: any) => p?.id === pageId) || {};
84
- return { clientData: page };
85
- })
86
- .catch(async () => {
87
- // Final fallback: minimal object
88
- return { clientData: null };
89
- });
90
- }
38
+ })();
91
39
  }
92
-
93
- if (bundledResponses?.[url]) {
94
- return JSON.parse(bundledResponses?.[url]);
40
+ // /devices/apps/:appId/node/:pageId
41
+ const pageMatch = pathOnly.match(
42
+ /^\/devices\/apps\/([^/]+)\/node\/([^/?#]+)$/
43
+ );
44
+ if (pageMatch) {
45
+ const appId = pageMatch[1];
46
+ const pageId = pageMatch[2];
47
+ // Try a dedicated page JSON first (if present)
48
+ const tryUrls = [
49
+ `/flex-layout/${appId}/${pageId}.json`,
50
+ absBase ? `/@fs/${absBase}/${appId}/${pageId}.json` : undefined,
51
+ ].filter(Boolean) as string[];
52
+ return fetch(tryUrls[0])
53
+ .then(async (r) => {
54
+ if (r.ok) return r.json();
55
+ // try secondary url if defined
56
+ if (tryUrls[1]) {
57
+ const r2 = await fetch(tryUrls[1]).catch(() => null);
58
+ if (r2 && r2.ok) return r2.json();
59
+ }
60
+ // Fallback: derive minimal shape from app.json
61
+ const appJson =
62
+ (await fetch(`/flex-layout/${appId}/${appId}.json`)
63
+ .then((a) => (a.ok ? a.json() : null))
64
+ .catch(() => null)) ||
65
+ (absBase
66
+ ? await fetch(`/@fs/${absBase}/${appId}/${appId}.json`)
67
+ .then((a) => (a.ok ? a.json() : null))
68
+ .catch(() => null)
69
+ : null);
70
+ // Keep shape compatible with current consumers
71
+ const pages = appJson?.app?.data?.pages || appJson?.data?.pages || [];
72
+ const page = pages.find((p: any) => p?.id === pageId) || {};
73
+ return { clientData: page };
74
+ })
75
+ .catch(async () => {
76
+ // Final fallback: minimal object
77
+ return { clientData: null };
78
+ });
95
79
  }
80
+ }
81
+
82
+ if (bundledResponses?.[url]) {
83
+ return JSON.parse(bundledResponses?.[url]);
84
+ }
85
+
86
+ // Get baseURL at runtime from store
87
+ const appsServiceUrl = getAppsServiceUrl();
96
88
 
97
- // Get baseURL at runtime from store
98
- const appsServiceUrl = getAppsServiceUrl();
89
+ console.log(
90
+ "[Fetcher] Requesting:",
91
+ url,
92
+ "BaseURL:",
93
+ appsServiceUrl,
94
+ "Headers:",
95
+ { "x-app-clientrendered": "disabled" }
96
+ );
99
97
 
100
- return axios({
101
- baseURL: appsServiceUrl,
102
- url,
103
- headers: { "x-app-clientrendered": "true" },
104
- }).then((res) => res.data);
98
+ return axios({
99
+ baseURL: appsServiceUrl,
100
+ url,
101
+ // headers: { "x-app-clientrendered": "true" },
102
+ }).then((res) => res.data);
105
103
  };
106
104
 
107
105
  export default fetcher;