@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 +1 -1
- package/src/cli/commands/generate.ts +26 -2
- package/src/components.tsx +30 -0
- package/src/lib/fetcher.ts +85 -87
package/package.json
CHANGED
|
@@ -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
|
-
|
|
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);
|
package/src/components.tsx
CHANGED
|
@@ -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
|
package/src/lib/fetcher.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
/^\/devices\/apps\/([^/]+)\/node\/([^/?#]+)$/,
|
|
35
|
+
throw new Error(
|
|
36
|
+
`Local app.json not found for ${appId} (tried ${tryUrls.join(", ")})`
|
|
47
37
|
);
|
|
48
|
-
|
|
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
|
-
|
|
94
|
-
|
|
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
|
-
|
|
98
|
-
|
|
89
|
+
console.log(
|
|
90
|
+
"[Fetcher] Requesting:",
|
|
91
|
+
url,
|
|
92
|
+
"BaseURL:",
|
|
93
|
+
appsServiceUrl,
|
|
94
|
+
"Headers:",
|
|
95
|
+
{ "x-app-clientrendered": "disabled" }
|
|
96
|
+
);
|
|
99
97
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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;
|