@bravostudioai/react 0.1.16 → 0.1.18

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bravostudioai/react",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/src/index.d.ts",
@@ -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
@@ -381,6 +403,7 @@ export async function runGenerate(args: string[]) {
381
403
  return;
382
404
  }
383
405
 
406
+ const usedNames = new Set<string>();
384
407
  for (const page of pages) {
385
408
  if (!page.id) continue;
386
409
  try {
@@ -390,6 +413,7 @@ export async function runGenerate(args: string[]) {
390
413
  outputPath,
391
414
  cachedAppData: appData,
392
415
  isProduction,
416
+ usedNames,
393
417
  });
394
418
  } catch (error) {
395
419
  console.warn(
@@ -408,7 +432,7 @@ export async function runGenerate(args: string[]) {
408
432
  const [appId, pageId, outputPath] = cleanArgs;
409
433
 
410
434
  try {
411
- 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
412
436
  } catch (error) {
413
437
  console.error("\nError:", error instanceof Error ? error.message : error);
414
438
  process.exit(1);
@@ -1553,6 +1553,36 @@ const SvgComponent: React.FC<ComponentProps> = ({ id, name, nodeData }) => {
1553
1553
  });
1554
1554
  const assetsById = useEncoreState((state) => state.assetsById);
1555
1555
 
1556
+ // Fix for headless rendering: Explicitly calculate width/height from positioning
1557
+ // img tags with both left+right or top+bottom might not render correctly in all browsers
1558
+ const pos = nodeData.style?.positioning;
1559
+ if (pos) {
1560
+ const hasLR = typeof pos.left === "number" && typeof pos.right === "number";
1561
+ const hasTB = typeof pos.top === "number" && typeof pos.bottom === "number";
1562
+
1563
+ // Explicitly set width if left+right are present
1564
+ if (hasLR) {
1565
+ const widthPct = 100 - pos.left - pos.right;
1566
+ style.width = `${widthPct}%`;
1567
+ // Ensure left/top are set (useEncoreStyle sets them, but we want to be sure)
1568
+ style.left = `${pos.left}%`;
1569
+ delete style.right;
1570
+ }
1571
+
1572
+ // Explicitly set height if top+bottom are present
1573
+ if (hasTB) {
1574
+ const heightPct = 100 - pos.top - pos.bottom;
1575
+ style.height = `${heightPct}%`;
1576
+ style.top = `${pos.top}%`;
1577
+ delete style.bottom;
1578
+ }
1579
+ }
1580
+
1581
+ // Ensure object fills the box
1582
+ if (!style.objectFit) {
1583
+ style.objectFit = "fill"; // Default to fill for SVGs/Groups to match Figma bounds
1584
+ }
1585
+
1556
1586
  return (
1557
1587
  <EncoreLinkActionWrapper id={id} nodeData={nodeData}>
1558
1588
  <img
@@ -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;