@bravostudioai/react 0.1.47 → 0.1.48
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/components/EncoreApp.js +81 -79
- package/dist/components/EncoreApp.js.map +1 -1
- package/dist/src/components/EncoreApp.d.ts.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
- package/src/components/EncoreApp.tsx +26 -18
- package/src/version.ts +1 -1
|
@@ -1,52 +1,52 @@
|
|
|
1
1
|
import { jsx as n } from "react/jsx-runtime";
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import { useRef as
|
|
2
|
+
import Y from "../node_modules/swr/dist/index/index.js";
|
|
3
|
+
import q from "../lib/fetcher.js";
|
|
4
|
+
import l from "../stores/useEncoreState.js";
|
|
5
|
+
import { useRef as G, useEffect as c, useState as J, useCallback as ct, useMemo as at, Suspense as it } from "react";
|
|
6
6
|
import { setLocalModeOverride as A, isLocalMode as B } from "../lib/localMode.js";
|
|
7
|
-
import
|
|
8
|
-
import { useEncoreRouter as
|
|
9
|
-
import { usePusherUpdates as
|
|
10
|
-
import { useFontLoader as
|
|
11
|
-
import { useRepeatingContainers as
|
|
12
|
-
import { EncoreContextProviders as
|
|
13
|
-
import { patchPageData as
|
|
14
|
-
import { logger as
|
|
15
|
-
const
|
|
16
|
-
const { navigate:
|
|
7
|
+
import lt from "./DynamicComponent.js";
|
|
8
|
+
import { useEncoreRouter as dt } from "../contexts/EncoreRouterContext.js";
|
|
9
|
+
import { usePusherUpdates as ut } from "../hooks/usePusherUpdates.js";
|
|
10
|
+
import { useFontLoader as ft } from "../hooks/useFontLoader.js";
|
|
11
|
+
import { useRepeatingContainers as mt } from "../hooks/useRepeatingContainers.js";
|
|
12
|
+
import { EncoreContextProviders as ht } from "./EncoreContextProviders.js";
|
|
13
|
+
import { patchPageData as pt } from "../lib/dataPatching.js";
|
|
14
|
+
import { logger as d } from "../lib/logger.js";
|
|
15
|
+
const yt = ({ to: e, children: o, style: D, ...b }) => {
|
|
16
|
+
const { navigate: u } = dt();
|
|
17
17
|
return /* @__PURE__ */ n(
|
|
18
18
|
"a",
|
|
19
19
|
{
|
|
20
20
|
href: e,
|
|
21
21
|
onClick: (m) => {
|
|
22
|
-
m.preventDefault(),
|
|
22
|
+
m.preventDefault(), u(e);
|
|
23
23
|
},
|
|
24
24
|
style: { cursor: "pointer", ...D },
|
|
25
25
|
...b,
|
|
26
26
|
children: o
|
|
27
27
|
}
|
|
28
28
|
);
|
|
29
|
-
},
|
|
29
|
+
}, gt = (e) => e.setApp, vt = (e) => e.setAppId, bt = (e) => e.setPageId, wt = (e) => e.assetsById, jt = ({
|
|
30
30
|
appId: e,
|
|
31
31
|
pageId: o,
|
|
32
32
|
componentId: D,
|
|
33
33
|
fallback: b,
|
|
34
|
-
onSizeChange:
|
|
34
|
+
onSizeChange: u,
|
|
35
35
|
onContentSizeChange: m,
|
|
36
|
-
onAction:
|
|
36
|
+
onAction: Q,
|
|
37
37
|
data: w,
|
|
38
|
-
source:
|
|
39
|
-
repeatingContainerControls:
|
|
38
|
+
source: f,
|
|
39
|
+
repeatingContainerControls: X,
|
|
40
40
|
inputGroups: L,
|
|
41
41
|
baseURL: S,
|
|
42
42
|
appDefinition: h,
|
|
43
43
|
pageDefinition: R,
|
|
44
44
|
componentCode: O,
|
|
45
45
|
mode: P,
|
|
46
|
-
embeds:
|
|
46
|
+
embeds: M
|
|
47
47
|
}) => {
|
|
48
|
-
|
|
49
|
-
const
|
|
48
|
+
d.debug("EncoreApp render", { appId: e, pageId: o, mode: P }), S && l.getState().baseURL !== S && l.getState().setBaseURL(S), f && A(f === "local" ? "local" : "remote");
|
|
49
|
+
const Z = !1, C = l(gt), E = l(vt), U = l(bt), p = l(wt), F = G(null), j = G(null);
|
|
50
50
|
c(() => {
|
|
51
51
|
if (!m) return;
|
|
52
52
|
const t = j.current;
|
|
@@ -61,25 +61,25 @@ const pt = ({ to: e, children: o, style: D, ...b }) => {
|
|
|
61
61
|
});
|
|
62
62
|
return r(), s.observe(t), () => s.disconnect();
|
|
63
63
|
}, [m]);
|
|
64
|
-
const [
|
|
65
|
-
|
|
66
|
-
}, []);
|
|
67
|
-
|
|
64
|
+
const [W, z] = J(0), I = ct(() => {
|
|
65
|
+
z((t) => typeof t == "number" ? t + 1 : Date.now());
|
|
66
|
+
}, []), N = f === "local" || B(), y = P === "production", a = P === "optimistic", V = !y && !a;
|
|
67
|
+
ut({
|
|
68
68
|
appId: e,
|
|
69
69
|
pageId: o || void 0,
|
|
70
|
-
enabled: !B() && !h,
|
|
71
|
-
onUpdate:
|
|
70
|
+
enabled: a || !B() && !h,
|
|
71
|
+
onUpdate: I
|
|
72
72
|
});
|
|
73
|
-
const
|
|
74
|
-
suspense:
|
|
73
|
+
const K = !y && e ? `/devices/apps/${e}${N ? "?useLocal=1" : ""}` : null, tt = Y(K, q, {
|
|
74
|
+
suspense: V && !!K,
|
|
75
75
|
// Suspense only for Dynamic mode
|
|
76
|
-
fallbackData:
|
|
76
|
+
fallbackData: a && h ? h : void 0,
|
|
77
77
|
revalidateOnMount: !0
|
|
78
78
|
// Ensure we fetch fresh data in optimistic mode
|
|
79
|
-
}), g = y && h ? { data: h } :
|
|
79
|
+
}), g = y && h ? { data: h } : tt;
|
|
80
80
|
c(() => {
|
|
81
|
-
|
|
82
|
-
}, [g.data,
|
|
81
|
+
C(g.data);
|
|
82
|
+
}, [g.data, C]), ft(g?.data), c(() => {
|
|
83
83
|
E(e);
|
|
84
84
|
}, [e, E]), c(() => {
|
|
85
85
|
o && U(o);
|
|
@@ -91,39 +91,41 @@ const pt = ({ to: e, children: o, style: D, ...b }) => {
|
|
|
91
91
|
}) : Promise.resolve())
|
|
92
92
|
))();
|
|
93
93
|
}, [p]);
|
|
94
|
-
const k = !y && e && o ? `/devices/apps/${e}/node/${o}${
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
suspense:
|
|
98
|
-
fallbackData:
|
|
94
|
+
const k = !y && e && o ? `/devices/apps/${e}/node/${o}${N ? "?useLocal=1" : ""}` : null;
|
|
95
|
+
d.debug("Page data fetch", { pageUrl: k, mode: P });
|
|
96
|
+
const et = Y(k, q, {
|
|
97
|
+
suspense: V && !!k,
|
|
98
|
+
fallbackData: a && R ? R : void 0,
|
|
99
99
|
revalidateOnMount: !0
|
|
100
|
-
}), $ = y && R ? { data: R } :
|
|
100
|
+
}), $ = y && R ? { data: R } : et, [ot, T] = J(
|
|
101
101
|
O
|
|
102
102
|
);
|
|
103
103
|
c(() => {
|
|
104
|
-
|
|
104
|
+
a && e && o && !B() ? (async () => {
|
|
105
105
|
try {
|
|
106
|
-
const { CONST_COMPONENTS_CDN_URL: r } = await import("../packages/encore-lib/constants.js"), s = Math.round(Date.now() / 1e3), x = `${e}/draft/components/${o}`, v = `${r}/${x}.js?cacheBuster=${s}`,
|
|
107
|
-
if (
|
|
108
|
-
const
|
|
109
|
-
|
|
106
|
+
const { CONST_COMPONENTS_CDN_URL: r } = await import("../packages/encore-lib/constants.js"), s = Math.round(Date.now() / 1e3), x = `${e}/draft/components/${o}`, v = `${r}/${x}.js?cacheBuster=${s}`, i = await fetch(v);
|
|
107
|
+
if (i.ok) {
|
|
108
|
+
const _ = await i.text();
|
|
109
|
+
T((H) => _ === H ? (d.debug("Optimistic code is up to date"), H) : (d.debug(
|
|
110
|
+
"Refreshed optimistic component code (new version found)"
|
|
111
|
+
), _));
|
|
110
112
|
}
|
|
111
113
|
} catch (r) {
|
|
112
|
-
|
|
114
|
+
d.warn("Failed to background refresh optimistic component", r);
|
|
113
115
|
}
|
|
114
|
-
})() :
|
|
115
|
-
}, [O,
|
|
116
|
-
const
|
|
117
|
-
|
|
116
|
+
})() : T(O);
|
|
117
|
+
}, [O, a, e, o, W]);
|
|
118
|
+
const rt = a ? ot : O;
|
|
119
|
+
d.debug("Page data loaded", {
|
|
118
120
|
hasData: !!$?.data,
|
|
119
121
|
dataType: typeof $?.data
|
|
120
122
|
});
|
|
121
|
-
const
|
|
123
|
+
const st = at(() => {
|
|
122
124
|
let t = $.data?.clientData;
|
|
123
|
-
return
|
|
125
|
+
return d.debug("Building context", {
|
|
124
126
|
hasClientData: !!t,
|
|
125
127
|
clientDataKeys: Object.keys(t || {}).length
|
|
126
|
-
}), t &&
|
|
128
|
+
}), t && pt(t), {
|
|
127
129
|
nodeData: void 0,
|
|
128
130
|
// Allow overriding specific values by element id.
|
|
129
131
|
// For now, this is used to override TextComponent content when the node has the PROP:TEXT_VAR tag.
|
|
@@ -133,38 +135,38 @@ const pt = ({ to: e, children: o, style: D, ...b }) => {
|
|
|
133
135
|
// Support for standalone component data binding (encore:data tags at root level)
|
|
134
136
|
rootData: w,
|
|
135
137
|
// Support for embedded components
|
|
136
|
-
embedsById:
|
|
138
|
+
embedsById: M
|
|
137
139
|
};
|
|
138
|
-
}, [$.data?.clientData, w,
|
|
139
|
-
|
|
140
|
+
}, [$.data?.clientData, w, M]), nt = mt(
|
|
141
|
+
X
|
|
140
142
|
);
|
|
141
143
|
return c(() => {
|
|
142
144
|
if (L) {
|
|
143
|
-
const t =
|
|
145
|
+
const t = l.getState().setInputGroupValue;
|
|
144
146
|
Object.entries(L).forEach(([r, s]) => {
|
|
145
147
|
t(r, s);
|
|
146
148
|
});
|
|
147
149
|
}
|
|
148
150
|
}, [L]), c(() => {
|
|
149
|
-
if (!
|
|
151
|
+
if (!u) return;
|
|
150
152
|
const t = F.current;
|
|
151
153
|
if (!t) return;
|
|
152
154
|
const r = (v) => {
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
+
const i = v.contentRect;
|
|
156
|
+
u({ width: i.width, height: i.height });
|
|
155
157
|
}, s = new ResizeObserver((v) => {
|
|
156
|
-
for (const
|
|
157
|
-
r(
|
|
158
|
+
for (const i of v)
|
|
159
|
+
r(i);
|
|
158
160
|
}), x = t.getBoundingClientRect();
|
|
159
|
-
return
|
|
161
|
+
return u({ width: x.width, height: x.height }), s.observe(t), () => {
|
|
160
162
|
s.disconnect();
|
|
161
163
|
};
|
|
162
|
-
}, [
|
|
163
|
-
if (
|
|
164
|
-
return A(
|
|
164
|
+
}, [u]), c(() => {
|
|
165
|
+
if (f)
|
|
166
|
+
return A(f === "local" ? "local" : "remote"), () => {
|
|
165
167
|
A(null);
|
|
166
168
|
};
|
|
167
|
-
}, [
|
|
169
|
+
}, [f]), o ? /* @__PURE__ */ n(
|
|
168
170
|
"div",
|
|
169
171
|
{
|
|
170
172
|
ref: F,
|
|
@@ -184,20 +186,20 @@ const pt = ({ to: e, children: o, style: D, ...b }) => {
|
|
|
184
186
|
display: "flex",
|
|
185
187
|
flexDirection: "column"
|
|
186
188
|
},
|
|
187
|
-
children: /* @__PURE__ */ n(
|
|
188
|
-
|
|
189
|
+
children: /* @__PURE__ */ n(it, { fallback: b || /* @__PURE__ */ n("div", {}), children: /* @__PURE__ */ n(
|
|
190
|
+
ht,
|
|
189
191
|
{
|
|
190
192
|
componentId: D,
|
|
191
|
-
onAction:
|
|
192
|
-
repeatingContainerContextValue:
|
|
193
|
-
bindingContextValue:
|
|
193
|
+
onAction: Q,
|
|
194
|
+
repeatingContainerContextValue: nt,
|
|
195
|
+
bindingContextValue: st,
|
|
194
196
|
children: /* @__PURE__ */ n(
|
|
195
|
-
|
|
197
|
+
lt,
|
|
196
198
|
{
|
|
197
199
|
name: `${e}/draft/components/${o}`,
|
|
198
200
|
fallback: b,
|
|
199
|
-
reloadKey:
|
|
200
|
-
componentCode:
|
|
201
|
+
reloadKey: W,
|
|
202
|
+
componentCode: rt
|
|
201
203
|
}
|
|
202
204
|
)
|
|
203
205
|
}
|
|
@@ -211,9 +213,9 @@ const pt = ({ to: e, children: o, style: D, ...b }) => {
|
|
|
211
213
|
(t) => t?.id
|
|
212
214
|
)
|
|
213
215
|
) : g?.data?.app.pageIds || []).map((t) => /* @__PURE__ */ n(
|
|
214
|
-
|
|
216
|
+
yt,
|
|
215
217
|
{
|
|
216
|
-
to: `/apps/${e}/pages/${t}?noRedirect=${
|
|
218
|
+
to: `/apps/${e}/pages/${t}?noRedirect=${Z}`,
|
|
217
219
|
style: {
|
|
218
220
|
fontSize: 20,
|
|
219
221
|
display: "block",
|
|
@@ -225,6 +227,6 @@ const pt = ({ to: e, children: o, style: D, ...b }) => {
|
|
|
225
227
|
)) }) });
|
|
226
228
|
};
|
|
227
229
|
export {
|
|
228
|
-
|
|
230
|
+
jt as default
|
|
229
231
|
};
|
|
230
232
|
//# sourceMappingURL=EncoreApp.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EncoreApp.js","sources":["../../src/components/EncoreApp.tsx"],"sourcesContent":["\"use client\";\nimport useSWR from \"swr\";\nimport fetcher from \"../lib/fetcher\";\nimport useEncoreState from \"../stores/useEncoreState\";\nimport React, {\n Suspense,\n useEffect,\n useRef,\n useState,\n useCallback,\n useMemo,\n} from \"react\";\nimport { isLocalMode, setLocalModeOverride } from \"../lib/localMode\";\nimport type { EncoreActionPayload } from \"../contexts/EncoreActionContext\";\nimport DynamicComponent from \"./DynamicComponent\";\nimport { useEncoreRouter } from \"../contexts/EncoreRouterContext\";\nimport { usePusherUpdates } from \"../hooks/usePusherUpdates\";\nimport { useFontLoader } from \"../hooks/useFontLoader\";\nimport { useRepeatingContainers } from \"../hooks/useRepeatingContainers\";\nimport { EncoreContextProviders } from \"./EncoreContextProviders\";\nimport { patchPageData } from \"../lib/dataPatching\";\nimport logger from \"../lib/logger\";\n\n// Simple internal Link component that uses our router context\nconst Link = ({ to, children, style, ...props }: any) => {\n const { navigate } = useEncoreRouter();\n return (\n <a\n href={to}\n onClick={(e) => {\n e.preventDefault();\n navigate(to);\n }}\n style={{ cursor: \"pointer\", ...style }}\n {...props}\n >\n {children}\n </a>\n );\n};\n\n/**\n * Props for the EncoreApp component\n */\nexport type EncoreAppProps = {\n /** Unique identifier for the Encore app */\n appId: string;\n /** Unique identifier for the page to render. If not provided, shows page selector UI */\n pageId?: string;\n /** Optional component identifier for context tracking */\n componentId?: string;\n /** Fallback UI to show during component loading */\n fallback?: React.ReactNode;\n /** Callback fired when the container size changes */\n onSizeChange?: (size: { width: number; height: number }) => void;\n /** Callback fired when the content size changes (including overflow) */\n onContentSizeChange?: (size: { width: number; height: number }) => void;\n /** Callback fired when user interactions trigger actions (button clicks, form submissions, etc.) */\n onAction?: (payload: EncoreActionPayload) => void | Promise<void>;\n /** Data bindings for components with encore:data tags. Maps component IDs to display values */\n data?: Record<string, string | number | any[]>;\n /** Force component loading from \"remote\" CDN or \"local\" filesystem */\n source?: \"remote\" | \"local\";\n /** Control repeating containers (sliders, lists) programmatically by container ID */\n repeatingContainerControls?: Record<\n string,\n { currentIndex?: number; onIndexChange?: (index: number) => void }\n >;\n /** Control input groups (radio button-like behavior). Maps group name to active element */\n inputGroups?: Record<string, string>;\n /** Base URL for the Encore service API */\n baseURL?: string;\n /** Provide app definition directly instead of fetching (for offline/bundled deployments) */\n appDefinition?: any;\n /** Provide page definition directly instead of fetching (for offline/bundled deployments) */\n pageDefinition?: any;\n /** Provide component code directly instead of fetching (for offline/bundled deployments) */\n componentCode?: string;\n /** Deployment mode: dynamic (default), optimistic, or production */\n mode?: \"dynamic\" | \"optimistic\" | \"production\";\n /** Map of embedded components by ID */\n embeds?: Record<string, React.ReactNode>;\n};\n\ntype Props = EncoreAppProps;\n\ntype EncoreAssetsById = Record<string, { url?: string }>;\ntype EncoreState = {\n setApp: (app: unknown) => void;\n setAppId: (id: string) => void;\n setPageId: (id: string) => void;\n assetsById: EncoreAssetsById;\n};\n\nconst setAppSelector = (state: EncoreState) => state.setApp;\nconst setAppIdSelector = (state: EncoreState) => state.setAppId;\nconst setPageIdSelector = (state: EncoreState) => state.setPageId;\nconst assetsByIdSelector = (state: EncoreState) => state.assetsById;\n\n/**\n * Main Encore runtime component\n *\n * Loads and renders Encore Studio apps dynamically from the Encore service.\n * Handles data fetching, font loading, real-time updates, and component rendering.\n *\n * @example\n * // Basic usage\n * <EncoreApp appId=\"01ABC123\" pageId=\"01DEF456\" />\n *\n * @example\n * // With data binding\n * <EncoreApp\n * appId=\"01ABC123\"\n * pageId=\"01DEF456\"\n * data={{\n * \"title-component\": { text: \"Hello World\" }\n * }}\n * />\n *\n * @example\n * // Controlling a slider\n * const [slideIndex, setSlideIndex] = useState(0);\n * <EncoreApp\n * appId=\"01ABC123\"\n * pageId=\"01DEF456\"\n * repeatingContainerControls={{\n * \"slider-123\": {\n * currentIndex: slideIndex,\n * onIndexChange: setSlideIndex\n * }\n * }}\n * />\n */\nconst EncoreApp = ({\n appId,\n pageId,\n componentId,\n fallback,\n onSizeChange,\n onContentSizeChange,\n onAction,\n data,\n source,\n repeatingContainerControls,\n inputGroups,\n baseURL,\n appDefinition,\n pageDefinition,\n componentCode,\n mode,\n embeds,\n}: Props) => {\n logger.debug(\"EncoreApp render\", { appId, pageId, mode });\n\n // CRITICAL: Set baseURL BEFORE any hooks that might trigger fetches\n // This must happen synchronously, not in useEffect, because useSWR will fetch immediately\n if (baseURL) {\n const currentBaseURL = useEncoreState.getState().baseURL;\n if (currentBaseURL !== baseURL) {\n useEncoreState.getState().setBaseURL(baseURL);\n }\n }\n\n // Apply source override immediately so hooks below observe correct mode\n if (source) {\n setLocalModeOverride(source === \"local\" ? \"local\" : \"remote\");\n }\n\n const noRedirect = false;\n\n const setApp = useEncoreState(setAppSelector);\n const setAppId = useEncoreState(setAppIdSelector);\n const setPageId = useEncoreState(setPageIdSelector);\n const assetsById = useEncoreState(assetsByIdSelector);\n const containerRef = useRef<HTMLDivElement | null>(null);\n const contentWrapperRef = useRef<HTMLDivElement | null>(null);\n\n // Monitor content size changes\n useEffect(() => {\n if (!onContentSizeChange) return;\n const element = contentWrapperRef.current;\n if (!element) return;\n\n const notify = () => {\n // Use scroll dimensions to get full size including overflow\n onContentSizeChange({\n width: element.scrollWidth,\n height: element.scrollHeight,\n });\n };\n\n const observer = new ResizeObserver(() => {\n notify();\n });\n\n // Emit initial size\n notify();\n observer.observe(element);\n return () => observer.disconnect();\n }, [onContentSizeChange]);\n\n // State to force DynamicComponent reload when updates are received\n const [reloadKey, setReloadKey] = useState<string | number>(0);\n // Set up Pusher to listen for component updates\n const handleUpdate = useCallback(() => {\n // Increment reloadKey to force DynamicComponent to reload\n setReloadKey((prev) => (typeof prev === \"number\" ? prev + 1 : Date.now()));\n }, []);\n\n // Only enable Pusher in remote mode - it doesn't make sense in local mode\n usePusherUpdates({\n appId,\n pageId: pageId || undefined,\n enabled: !isLocalMode() && !appDefinition,\n onUpdate: handleUpdate,\n });\n\n const useLocalFlag = source === \"local\" || isLocalMode();\n\n // Determine if we should fetch app definition\n // Production: Do NOT fetch (use appDefinition)\n // Dynamic: Fetch always (ignore appDefinition unless valid) - wait, dynamic assumes no bundled data usually, or if provided, use it? Current logic uses simple check.\n // Optimistic: Fetch always, but use appDefinition as fallback/initial data\n\n // Logic:\n // If we have appDefinition:\n // - Production: Use it, no fetch.\n // - Optimistic: Use it as fallbackData (SWR), and fetch.\n // - Dynamic (Edge Case): User provided data but wants dynamic? Treat same as Optimistic roughly, or just ignore data.\n // But usually Dynamic won't have appDefinition passed unless manually done.\n\n // Refined Logic based on `mode`:\n const isProductionMode = mode === \"production\";\n const isOptimisticMode = mode === \"optimistic\";\n // Default to dynamic if not specified\n const isDynamicMode = !isProductionMode && !isOptimisticMode;\n\n const shouldFetchApp = !isProductionMode && appId;\n const appUrl = shouldFetchApp\n ? `/devices/apps/${appId}${useLocalFlag ? \"?useLocal=1\" : \"\"}`\n : null;\n\n const appSWR = useSWR(appUrl, fetcher, {\n suspense: isDynamicMode && !!appUrl, // Suspense only for Dynamic mode\n fallbackData: isOptimisticMode && appDefinition ? appDefinition : undefined,\n revalidateOnMount: true, // Ensure we fetch fresh data in optimistic mode\n });\n\n const app =\n isProductionMode && appDefinition ? { data: appDefinition } : appSWR;\n\n useEffect(() => {\n setApp(app.data);\n }, [app.data, setApp]);\n\n // Load fonts declared in app.json\n useFontLoader(app?.data);\n\n useEffect(() => {\n setAppId(appId);\n }, [appId, setAppId]);\n\n useEffect(() => {\n if (!pageId) return;\n setPageId(pageId);\n }, [pageId, setPageId]);\n\n // FIXME: Asset data should be embedded into & preloaded by component, not looked up like this\n useEffect(() => {\n if (Object.keys(assetsById).length === 0) return;\n (async () => {\n // Preload images in the browser\n await Promise.allSettled(\n Object.keys(assetsById).map((id) => {\n if (assetsById[id].url) {\n return new Promise((resolve) => {\n const img = new Image();\n img.onload = resolve;\n img.onerror = resolve; // tolerate failures to avoid unhandled rejections\n img.src = assetsById[id].url!;\n });\n }\n return Promise.resolve();\n })\n );\n })();\n }, [assetsById]);\n\n const shouldFetchPage = !isProductionMode && appId && pageId;\n const pageUrl = shouldFetchPage\n ? `/devices/apps/${appId}/node/${pageId}${\n useLocalFlag ? \"?useLocal=1\" : \"\"\n }`\n : null;\n\n logger.debug(\"Page data fetch\", { pageUrl, mode });\n\n const pageSWR = useSWR(pageUrl, fetcher, {\n suspense: isDynamicMode && !!pageUrl,\n fallbackData:\n isOptimisticMode && pageDefinition ? pageDefinition : undefined,\n revalidateOnMount: true,\n });\n\n const pageData =\n isProductionMode && pageDefinition ? { data: pageDefinition } : pageSWR;\n\n // Specific logic for Component Code fetching in Optimistic Mode\n // If optimistic, we have componentCode passed in (bundled). We use that initially.\n // BUT we also want to fetch the latest component code in background.\n // DynamicComponent uses 'componentCode' prop if present.\n // We need to fetch the code separately and override the prop passed to DynamicComponent.\n\n // We need to fetch the latest component JS in optimistic mode\n // The DynamicComponent currently logic: if (componentCode) loadAMDModule(code) else fetchDep(name).\n // We want: render with initial componentCode, BUT ALSO fetch new code, and when valid, switch to it.\n\n const [optimisticCode, setOptimisticCode] = useState<string | undefined>(\n componentCode\n );\n\n useEffect(() => {\n // If in optimistic mode, we want to fetch the latest code\n if (isOptimisticMode && appId && pageId && !isLocalMode()) {\n // We can reuse the logic from DynamicComponent/dynamicModules roughly, or just fetch text.\n // The URL logic is in dynamicModules.\n // Let's just rely on DynamicComponent to do the fetching if we pass undefined?\n // No, if we pass undefined it will suspend/show fallback. We want to show OLD code.\n\n // So we renders with `optimisticCode` (initially bundled).\n // We fire a background fetch. on success, updated `optimisticCode`.\n\n const fetchLatestCode = async () => {\n try {\n // Replicating URL logic from dynamicModules for remote mode\n const { CONST_COMPONENTS_CDN_URL } = await import(\"../../constants\");\n const cacheBuster = Math.round(Date.now() / 1000);\n const name = `${appId}/draft/components/${pageId}`;\n const url = `${CONST_COMPONENTS_CDN_URL}/${name}.js?cacheBuster=${cacheBuster}`;\n\n const resp = await fetch(url);\n if (resp.ok) {\n const text = await resp.text();\n // Only update if different?\n // Comparing huge strings might be expensive, but React state update checks equality anyway (ref).\n // Let's just set it.\n setOptimisticCode(text);\n logger.debug(\"Refreshed optimistic component code\");\n }\n } catch (e) {\n logger.warn(\"Failed to background refresh optimistic component\", e);\n }\n };\n\n fetchLatestCode();\n } else {\n // If prop changes (e.g. from HMR or parent), sync it\n setOptimisticCode(componentCode);\n }\n }, [componentCode, isOptimisticMode, appId, pageId]);\n\n // Use the calculated optimistic code or the passed prop depending on mode\n const effectiveComponentCode = isOptimisticMode\n ? optimisticCode\n : componentCode;\n\n logger.debug(\"Page data loaded\", {\n hasData: !!pageData?.data,\n dataType: typeof pageData?.data,\n });\n\n // Memoize the context object to prevent infinite re-renders\n // Only recreate when the actual data changes, not on every render\n const context = useMemo(() => {\n let clientData = pageData.data?.clientData;\n logger.debug(\"Building context\", {\n hasClientData: !!clientData,\n clientDataKeys: Object.keys(clientData || {}).length,\n });\n\n // Apply layout heuristics to fix common issues\n if (clientData) {\n patchPageData(clientData);\n }\n\n return {\n nodeData: undefined,\n // Allow overriding specific values by element id.\n // For now, this is used to override TextComponent content when the node has the PROP:TEXT_VAR tag.\n textOverridesById: data,\n // Support for encore:data:array tags - provide array data by component ID\n arrayDataById: data,\n // Support for standalone component data binding (encore:data tags at root level)\n rootData: data,\n // Support for embedded components\n embedsById: embeds,\n };\n }, [pageData.data?.clientData, data, embeds]);\n\n // Manage repeating container controls (sliders, lists)\n const repeatingContainerContextValue = useRepeatingContainers(\n repeatingContainerControls\n );\n\n // Sync input groups from props to store\n useEffect(() => {\n if (inputGroups) {\n const setInputGroupValue = useEncoreState.getState().setInputGroupValue;\n Object.entries(inputGroups).forEach(([groupName, elementName]) => {\n setInputGroupValue(groupName, elementName);\n });\n }\n }, [inputGroups]);\n\n // Observe size changes of the dynamic content container and notify consumer\n useEffect(() => {\n if (!onSizeChange) return;\n const element = containerRef.current;\n if (!element) return;\n const notify = (entry: ResizeObserverEntry) => {\n const cr = entry.contentRect;\n onSizeChange({ width: cr.width, height: cr.height });\n };\n const observer = new ResizeObserver((entries) => {\n for (const entry of entries) {\n notify(entry);\n }\n });\n // Emit initial size as soon as possible\n const rect = element.getBoundingClientRect();\n onSizeChange({ width: rect.width, height: rect.height });\n observer.observe(element);\n return () => {\n observer.disconnect();\n };\n }, [onSizeChange]);\n\n // Per-instance source override\n useEffect(() => {\n if (!source) return;\n setLocalModeOverride(source === \"local\" ? \"local\" : \"remote\");\n return () => {\n // Clear override when this instance unmounts\n setLocalModeOverride(null);\n };\n }, [source]);\n\n if (!pageId) {\n return (\n <div style={{ padding: \"30px\" }}>\n <div style={{ overflowY: \"auto\" }}>\n {(isLocalMode()\n ? // Local mode: app.json provides pages under app.data.pages\n ((app?.data as any)?.app?.data?.pages || []).map(\n (pg: any) => pg?.id\n )\n : app?.data?.app.pageIds || []\n ).map((p: string) => (\n <Link\n key={p}\n to={`/apps/${appId}/pages/${p}?noRedirect=${noRedirect}`}\n style={{\n fontSize: 20,\n display: \"block\",\n marginBottom: \"10px\",\n }}\n >\n {p}\n </Link>\n ))}\n </div>\n </div>\n );\n }\n return (\n <div\n ref={containerRef}\n style={{\n width: \"100%\",\n height: \"100%\",\n position: \"relative\",\n overflow: \"hidden\",\n }}\n >\n <div\n ref={contentWrapperRef}\n style={{\n width: \"100%\",\n height: \"100%\",\n display: \"flex\",\n flexDirection: \"column\",\n }}\n >\n <Suspense fallback={fallback || <div />}>\n <EncoreContextProviders\n componentId={componentId}\n onAction={onAction}\n repeatingContainerContextValue={repeatingContainerContextValue}\n bindingContextValue={context}\n >\n <DynamicComponent\n name={`${appId}/draft/components/${pageId}`}\n fallback={fallback}\n reloadKey={reloadKey}\n componentCode={effectiveComponentCode}\n />\n </EncoreContextProviders>\n </Suspense>\n </div>\n </div>\n );\n};\n\nexport default EncoreApp;\n"],"names":["Link","to","children","style","props","navigate","useEncoreRouter","jsx","e","setAppSelector","state","setAppIdSelector","setPageIdSelector","assetsByIdSelector","EncoreApp","appId","pageId","componentId","fallback","onSizeChange","onContentSizeChange","onAction","data","source","repeatingContainerControls","inputGroups","baseURL","appDefinition","pageDefinition","componentCode","mode","embeds","logger","useEncoreState","setLocalModeOverride","noRedirect","setApp","setAppId","setPageId","assetsById","containerRef","useRef","contentWrapperRef","useEffect","element","notify","observer","reloadKey","setReloadKey","useState","handleUpdate","useCallback","prev","usePusherUpdates","isLocalMode","useLocalFlag","isProductionMode","isOptimisticMode","isDynamicMode","appUrl","appSWR","useSWR","fetcher","app","useFontLoader","id","resolve","img","pageUrl","pageSWR","pageData","optimisticCode","setOptimisticCode","CONST_COMPONENTS_CDN_URL","cacheBuster","name","url","resp","text","effectiveComponentCode","context","useMemo","clientData","patchPageData","repeatingContainerContextValue","useRepeatingContainers","setInputGroupValue","groupName","elementName","entry","cr","entries","rect","Suspense","EncoreContextProviders","DynamicComponent","pg","p"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EncoreApp.js","sources":["../../src/components/EncoreApp.tsx"],"sourcesContent":["\"use client\";\nimport useSWR from \"swr\";\nimport fetcher from \"../lib/fetcher\";\nimport useEncoreState from \"../stores/useEncoreState\";\nimport React, {\n Suspense,\n useEffect,\n useRef,\n useState,\n useCallback,\n useMemo,\n} from \"react\";\nimport { isLocalMode, setLocalModeOverride } from \"../lib/localMode\";\nimport type { EncoreActionPayload } from \"../contexts/EncoreActionContext\";\nimport DynamicComponent from \"./DynamicComponent\";\nimport { useEncoreRouter } from \"../contexts/EncoreRouterContext\";\nimport { usePusherUpdates } from \"../hooks/usePusherUpdates\";\nimport { useFontLoader } from \"../hooks/useFontLoader\";\nimport { useRepeatingContainers } from \"../hooks/useRepeatingContainers\";\nimport { EncoreContextProviders } from \"./EncoreContextProviders\";\nimport { patchPageData } from \"../lib/dataPatching\";\nimport logger from \"../lib/logger\";\n\n// Simple internal Link component that uses our router context\nconst Link = ({ to, children, style, ...props }: any) => {\n const { navigate } = useEncoreRouter();\n return (\n <a\n href={to}\n onClick={(e) => {\n e.preventDefault();\n navigate(to);\n }}\n style={{ cursor: \"pointer\", ...style }}\n {...props}\n >\n {children}\n </a>\n );\n};\n\n/**\n * Props for the EncoreApp component\n */\nexport type EncoreAppProps = {\n /** Unique identifier for the Encore app */\n appId: string;\n /** Unique identifier for the page to render. If not provided, shows page selector UI */\n pageId?: string;\n /** Optional component identifier for context tracking */\n componentId?: string;\n /** Fallback UI to show during component loading */\n fallback?: React.ReactNode;\n /** Callback fired when the container size changes */\n onSizeChange?: (size: { width: number; height: number }) => void;\n /** Callback fired when the content size changes (including overflow) */\n onContentSizeChange?: (size: { width: number; height: number }) => void;\n /** Callback fired when user interactions trigger actions (button clicks, form submissions, etc.) */\n onAction?: (payload: EncoreActionPayload) => void | Promise<void>;\n /** Data bindings for components with encore:data tags. Maps component IDs to display values */\n data?: Record<string, string | number | any[]>;\n /** Force component loading from \"remote\" CDN or \"local\" filesystem */\n source?: \"remote\" | \"local\";\n /** Control repeating containers (sliders, lists) programmatically by container ID */\n repeatingContainerControls?: Record<\n string,\n { currentIndex?: number; onIndexChange?: (index: number) => void }\n >;\n /** Control input groups (radio button-like behavior). Maps group name to active element */\n inputGroups?: Record<string, string>;\n /** Base URL for the Encore service API */\n baseURL?: string;\n /** Provide app definition directly instead of fetching (for offline/bundled deployments) */\n appDefinition?: any;\n /** Provide page definition directly instead of fetching (for offline/bundled deployments) */\n pageDefinition?: any;\n /** Provide component code directly instead of fetching (for offline/bundled deployments) */\n componentCode?: string;\n /** Deployment mode: dynamic (default), optimistic, or production */\n mode?: \"dynamic\" | \"optimistic\" | \"production\";\n /** Map of embedded components by ID */\n embeds?: Record<string, React.ReactNode>;\n};\n\ntype Props = EncoreAppProps;\n\ntype EncoreAssetsById = Record<string, { url?: string }>;\ntype EncoreState = {\n setApp: (app: unknown) => void;\n setAppId: (id: string) => void;\n setPageId: (id: string) => void;\n assetsById: EncoreAssetsById;\n};\n\nconst setAppSelector = (state: EncoreState) => state.setApp;\nconst setAppIdSelector = (state: EncoreState) => state.setAppId;\nconst setPageIdSelector = (state: EncoreState) => state.setPageId;\nconst assetsByIdSelector = (state: EncoreState) => state.assetsById;\n\n/**\n * Main Encore runtime component\n *\n * Loads and renders Encore Studio apps dynamically from the Encore service.\n * Handles data fetching, font loading, real-time updates, and component rendering.\n *\n * @example\n * // Basic usage\n * <EncoreApp appId=\"01ABC123\" pageId=\"01DEF456\" />\n *\n * @example\n * // With data binding\n * <EncoreApp\n * appId=\"01ABC123\"\n * pageId=\"01DEF456\"\n * data={{\n * \"title-component\": { text: \"Hello World\" }\n * }}\n * />\n *\n * @example\n * // Controlling a slider\n * const [slideIndex, setSlideIndex] = useState(0);\n * <EncoreApp\n * appId=\"01ABC123\"\n * pageId=\"01DEF456\"\n * repeatingContainerControls={{\n * \"slider-123\": {\n * currentIndex: slideIndex,\n * onIndexChange: setSlideIndex\n * }\n * }}\n * />\n */\nconst EncoreApp = ({\n appId,\n pageId,\n componentId,\n fallback,\n onSizeChange,\n onContentSizeChange,\n onAction,\n data,\n source,\n repeatingContainerControls,\n inputGroups,\n baseURL,\n appDefinition,\n pageDefinition,\n componentCode,\n mode,\n embeds,\n}: Props) => {\n logger.debug(\"EncoreApp render\", { appId, pageId, mode });\n\n // CRITICAL: Set baseURL BEFORE any hooks that might trigger fetches\n // This must happen synchronously, not in useEffect, because useSWR will fetch immediately\n if (baseURL) {\n const currentBaseURL = useEncoreState.getState().baseURL;\n if (currentBaseURL !== baseURL) {\n useEncoreState.getState().setBaseURL(baseURL);\n }\n }\n\n // Apply source override immediately so hooks below observe correct mode\n if (source) {\n setLocalModeOverride(source === \"local\" ? \"local\" : \"remote\");\n }\n\n const noRedirect = false;\n\n const setApp = useEncoreState(setAppSelector);\n const setAppId = useEncoreState(setAppIdSelector);\n const setPageId = useEncoreState(setPageIdSelector);\n const assetsById = useEncoreState(assetsByIdSelector);\n const containerRef = useRef<HTMLDivElement | null>(null);\n const contentWrapperRef = useRef<HTMLDivElement | null>(null);\n\n // Monitor content size changes\n useEffect(() => {\n if (!onContentSizeChange) return;\n const element = contentWrapperRef.current;\n if (!element) return;\n\n const notify = () => {\n // Use scroll dimensions to get full size including overflow\n onContentSizeChange({\n width: element.scrollWidth,\n height: element.scrollHeight,\n });\n };\n\n const observer = new ResizeObserver(() => {\n notify();\n });\n\n // Emit initial size\n notify();\n observer.observe(element);\n return () => observer.disconnect();\n }, [onContentSizeChange]);\n\n // State to force DynamicComponent reload when updates are received\n const [reloadKey, setReloadKey] = useState<string | number>(0);\n // Set up Pusher to listen for component updates\n const handleUpdate = useCallback(() => {\n // Increment reloadKey to force DynamicComponent to reload\n setReloadKey((prev) => (typeof prev === \"number\" ? prev + 1 : Date.now()));\n }, []);\n\n const useLocalFlag = source === \"local\" || isLocalMode();\n\n // Determine if we should fetch app definition\n // Production: Do NOT fetch (use appDefinition)\n // Dynamic: Fetch always (ignore appDefinition unless valid) - wait, dynamic assumes no bundled data usually, or if provided, use it? Current logic uses simple check.\n // Optimistic: Fetch always, but use appDefinition as fallback/initial data\n\n // Logic:\n // If we have appDefinition:\n // - Production: Use it, no fetch.\n // - Optimistic: Use it as fallbackData (SWR), and fetch.\n // - Dynamic (Edge Case): User provided data but wants dynamic? Treat same as Optimistic roughly, or just ignore data.\n // But usually Dynamic won't have appDefinition passed unless manually done.\n\n // Refined Logic based on `mode`:\n const isProductionMode = mode === \"production\";\n const isOptimisticMode = mode === \"optimistic\";\n // Default to dynamic if not specified\n const isDynamicMode = !isProductionMode && !isOptimisticMode;\n\n // Only enable Pusher in remote mode - it doesn't make sense in local mode\n usePusherUpdates({\n appId,\n pageId: pageId || undefined,\n enabled: isOptimisticMode || (!isLocalMode() && !appDefinition),\n onUpdate: handleUpdate,\n });\n\n const shouldFetchApp = !isProductionMode && appId;\n const appUrl = shouldFetchApp\n ? `/devices/apps/${appId}${useLocalFlag ? \"?useLocal=1\" : \"\"}`\n : null;\n\n const appSWR = useSWR(appUrl, fetcher, {\n suspense: isDynamicMode && !!appUrl, // Suspense only for Dynamic mode\n fallbackData: isOptimisticMode && appDefinition ? appDefinition : undefined,\n revalidateOnMount: true, // Ensure we fetch fresh data in optimistic mode\n });\n\n const app =\n isProductionMode && appDefinition ? { data: appDefinition } : appSWR;\n\n useEffect(() => {\n setApp(app.data);\n }, [app.data, setApp]);\n\n // Load fonts declared in app.json\n useFontLoader(app?.data);\n\n useEffect(() => {\n setAppId(appId);\n }, [appId, setAppId]);\n\n useEffect(() => {\n if (!pageId) return;\n setPageId(pageId);\n }, [pageId, setPageId]);\n\n // FIXME: Asset data should be embedded into & preloaded by component, not looked up like this\n useEffect(() => {\n if (Object.keys(assetsById).length === 0) return;\n (async () => {\n // Preload images in the browser\n await Promise.allSettled(\n Object.keys(assetsById).map((id) => {\n if (assetsById[id].url) {\n return new Promise((resolve) => {\n const img = new Image();\n img.onload = resolve;\n img.onerror = resolve; // tolerate failures to avoid unhandled rejections\n img.src = assetsById[id].url!;\n });\n }\n return Promise.resolve();\n }),\n );\n })();\n }, [assetsById]);\n\n const shouldFetchPage = !isProductionMode && appId && pageId;\n const pageUrl = shouldFetchPage\n ? `/devices/apps/${appId}/node/${pageId}${\n useLocalFlag ? \"?useLocal=1\" : \"\"\n }`\n : null;\n\n logger.debug(\"Page data fetch\", { pageUrl, mode });\n\n const pageSWR = useSWR(pageUrl, fetcher, {\n suspense: isDynamicMode && !!pageUrl,\n fallbackData:\n isOptimisticMode && pageDefinition ? pageDefinition : undefined,\n revalidateOnMount: true,\n });\n\n const pageData =\n isProductionMode && pageDefinition ? { data: pageDefinition } : pageSWR;\n\n // Specific logic for Component Code fetching in Optimistic Mode\n // If optimistic, we have componentCode passed in (bundled). We use that initially.\n // BUT we also want to fetch the latest component code in background.\n // DynamicComponent uses 'componentCode' prop if present.\n // We need to fetch the code separately and override the prop passed to DynamicComponent.\n\n // We need to fetch the latest component JS in optimistic mode\n // The DynamicComponent currently logic: if (componentCode) loadAMDModule(code) else fetchDep(name).\n // We want: render with initial componentCode, BUT ALSO fetch new code, and when valid, switch to it.\n\n const [optimisticCode, setOptimisticCode] = useState<string | undefined>(\n componentCode,\n );\n\n useEffect(() => {\n // If in optimistic mode, we want to fetch the latest code\n if (isOptimisticMode && appId && pageId && !isLocalMode()) {\n // We can reuse the logic from DynamicComponent/dynamicModules roughly, or just fetch text.\n // The URL logic is in dynamicModules.\n // Let's just rely on DynamicComponent to do the fetching if we pass undefined?\n // No, if we pass undefined it will suspend/show fallback. We want to show OLD code.\n\n // So we renders with `optimisticCode` (initially bundled).\n // We fire a background fetch. on success, updated `optimisticCode`.\n\n const fetchLatestCode = async () => {\n try {\n // Replicating URL logic from dynamicModules for remote mode\n const { CONST_COMPONENTS_CDN_URL } = await import(\"../../constants\");\n const cacheBuster = Math.round(Date.now() / 1000);\n const name = `${appId}/draft/components/${pageId}`;\n const url = `${CONST_COMPONENTS_CDN_URL}/${name}.js?cacheBuster=${cacheBuster}`;\n\n const resp = await fetch(url);\n if (resp.ok) {\n const text = await resp.text();\n // Check if code is different from what we have (either bundled or previously fetched)\n // Use strict equality as strings are primitives.\n // If the server returns identical code, we skip the update to avoid re-rendering.\n setOptimisticCode((currentCode) => {\n if (text === currentCode) {\n logger.debug(\"Optimistic code is up to date\");\n return currentCode;\n }\n logger.debug(\n \"Refreshed optimistic component code (new version found)\",\n );\n return text;\n });\n }\n } catch (e) {\n logger.warn(\"Failed to background refresh optimistic component\", e);\n }\n };\n\n fetchLatestCode();\n } else {\n // If prop changes (e.g. from HMR or parent), sync it\n setOptimisticCode(componentCode);\n }\n }, [componentCode, isOptimisticMode, appId, pageId, reloadKey]);\n\n // Use the calculated optimistic code or the passed prop depending on mode\n const effectiveComponentCode = isOptimisticMode\n ? optimisticCode\n : componentCode;\n\n logger.debug(\"Page data loaded\", {\n hasData: !!pageData?.data,\n dataType: typeof pageData?.data,\n });\n\n // Memoize the context object to prevent infinite re-renders\n // Only recreate when the actual data changes, not on every render\n const context = useMemo(() => {\n let clientData = pageData.data?.clientData;\n logger.debug(\"Building context\", {\n hasClientData: !!clientData,\n clientDataKeys: Object.keys(clientData || {}).length,\n });\n\n // Apply layout heuristics to fix common issues\n if (clientData) {\n patchPageData(clientData);\n }\n\n return {\n nodeData: undefined,\n // Allow overriding specific values by element id.\n // For now, this is used to override TextComponent content when the node has the PROP:TEXT_VAR tag.\n textOverridesById: data,\n // Support for encore:data:array tags - provide array data by component ID\n arrayDataById: data,\n // Support for standalone component data binding (encore:data tags at root level)\n rootData: data,\n // Support for embedded components\n embedsById: embeds,\n };\n }, [pageData.data?.clientData, data, embeds]);\n\n // Manage repeating container controls (sliders, lists)\n const repeatingContainerContextValue = useRepeatingContainers(\n repeatingContainerControls,\n );\n\n // Sync input groups from props to store\n useEffect(() => {\n if (inputGroups) {\n const setInputGroupValue = useEncoreState.getState().setInputGroupValue;\n Object.entries(inputGroups).forEach(([groupName, elementName]) => {\n setInputGroupValue(groupName, elementName);\n });\n }\n }, [inputGroups]);\n\n // Observe size changes of the dynamic content container and notify consumer\n useEffect(() => {\n if (!onSizeChange) return;\n const element = containerRef.current;\n if (!element) return;\n const notify = (entry: ResizeObserverEntry) => {\n const cr = entry.contentRect;\n onSizeChange({ width: cr.width, height: cr.height });\n };\n const observer = new ResizeObserver((entries) => {\n for (const entry of entries) {\n notify(entry);\n }\n });\n // Emit initial size as soon as possible\n const rect = element.getBoundingClientRect();\n onSizeChange({ width: rect.width, height: rect.height });\n observer.observe(element);\n return () => {\n observer.disconnect();\n };\n }, [onSizeChange]);\n\n // Per-instance source override\n useEffect(() => {\n if (!source) return;\n setLocalModeOverride(source === \"local\" ? \"local\" : \"remote\");\n return () => {\n // Clear override when this instance unmounts\n setLocalModeOverride(null);\n };\n }, [source]);\n\n if (!pageId) {\n return (\n <div style={{ padding: \"30px\" }}>\n <div style={{ overflowY: \"auto\" }}>\n {(isLocalMode()\n ? // Local mode: app.json provides pages under app.data.pages\n ((app?.data as any)?.app?.data?.pages || []).map(\n (pg: any) => pg?.id,\n )\n : app?.data?.app.pageIds || []\n ).map((p: string) => (\n <Link\n key={p}\n to={`/apps/${appId}/pages/${p}?noRedirect=${noRedirect}`}\n style={{\n fontSize: 20,\n display: \"block\",\n marginBottom: \"10px\",\n }}\n >\n {p}\n </Link>\n ))}\n </div>\n </div>\n );\n }\n return (\n <div\n ref={containerRef}\n style={{\n width: \"100%\",\n height: \"100%\",\n position: \"relative\",\n overflow: \"hidden\",\n }}\n >\n <div\n ref={contentWrapperRef}\n style={{\n width: \"100%\",\n height: \"100%\",\n display: \"flex\",\n flexDirection: \"column\",\n }}\n >\n <Suspense fallback={fallback || <div />}>\n <EncoreContextProviders\n componentId={componentId}\n onAction={onAction}\n repeatingContainerContextValue={repeatingContainerContextValue}\n bindingContextValue={context}\n >\n <DynamicComponent\n name={`${appId}/draft/components/${pageId}`}\n fallback={fallback}\n reloadKey={reloadKey}\n componentCode={effectiveComponentCode}\n />\n </EncoreContextProviders>\n </Suspense>\n </div>\n </div>\n );\n};\n\nexport default EncoreApp;\n"],"names":["Link","to","children","style","props","navigate","useEncoreRouter","jsx","e","setAppSelector","state","setAppIdSelector","setPageIdSelector","assetsByIdSelector","EncoreApp","appId","pageId","componentId","fallback","onSizeChange","onContentSizeChange","onAction","data","source","repeatingContainerControls","inputGroups","baseURL","appDefinition","pageDefinition","componentCode","mode","embeds","logger","useEncoreState","setLocalModeOverride","noRedirect","setApp","setAppId","setPageId","assetsById","containerRef","useRef","contentWrapperRef","useEffect","element","notify","observer","reloadKey","setReloadKey","useState","handleUpdate","useCallback","prev","useLocalFlag","isLocalMode","isProductionMode","isOptimisticMode","isDynamicMode","usePusherUpdates","appUrl","appSWR","useSWR","fetcher","app","useFontLoader","id","resolve","img","pageUrl","pageSWR","pageData","optimisticCode","setOptimisticCode","CONST_COMPONENTS_CDN_URL","cacheBuster","name","url","resp","text","currentCode","effectiveComponentCode","context","useMemo","clientData","patchPageData","repeatingContainerContextValue","useRepeatingContainers","setInputGroupValue","groupName","elementName","entry","cr","entries","rect","Suspense","EncoreContextProviders","DynamicComponent","pg","p"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EncoreApp.d.ts","sourceRoot":"","sources":["../../../src/components/EncoreApp.tsx"],"names":[],"mappings":"AAIA,OAAO,KAON,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AA4B3E;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,wFAAwF;IACxF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mDAAmD;IACnD,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,qDAAqD;IACrD,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACjE,wEAAwE;IACxE,mBAAmB,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACxE,oGAAoG;IACpG,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE,+FAA+F;IAC/F,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,GAAG,EAAE,CAAC,CAAC;IAC/C,sEAAsE;IACtE,MAAM,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC;IAC5B,qFAAqF;IACrF,0BAA0B,CAAC,EAAE,MAAM,CACjC,MAAM,EACN;QAAE,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,CACnE,CAAC;IACF,2FAA2F;IAC3F,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,0CAA0C;IAC1C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4FAA4F;IAC5F,aAAa,CAAC,EAAE,GAAG,CAAC;IACpB,6FAA6F;IAC7F,cAAc,CAAC,EAAE,GAAG,CAAC;IACrB,4FAA4F;IAC5F,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,oEAAoE;IACpE,IAAI,CAAC,EAAE,SAAS,GAAG,YAAY,GAAG,YAAY,CAAC;IAC/C,uCAAuC;IACvC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;CAC1C,CAAC;AAEF,KAAK,KAAK,GAAG,cAAc,CAAC;AAe5B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,QAAA,MAAM,SAAS,GAAI,oNAkBhB,KAAK,
|
|
1
|
+
{"version":3,"file":"EncoreApp.d.ts","sourceRoot":"","sources":["../../../src/components/EncoreApp.tsx"],"names":[],"mappings":"AAIA,OAAO,KAON,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AA4B3E;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,wFAAwF;IACxF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mDAAmD;IACnD,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,qDAAqD;IACrD,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACjE,wEAAwE;IACxE,mBAAmB,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACxE,oGAAoG;IACpG,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE,+FAA+F;IAC/F,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,GAAG,EAAE,CAAC,CAAC;IAC/C,sEAAsE;IACtE,MAAM,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC;IAC5B,qFAAqF;IACrF,0BAA0B,CAAC,EAAE,MAAM,CACjC,MAAM,EACN;QAAE,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,CACnE,CAAC;IACF,2FAA2F;IAC3F,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,0CAA0C;IAC1C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4FAA4F;IAC5F,aAAa,CAAC,EAAE,GAAG,CAAC;IACpB,6FAA6F;IAC7F,cAAc,CAAC,EAAE,GAAG,CAAC;IACrB,4FAA4F;IAC5F,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,oEAAoE;IACpE,IAAI,CAAC,EAAE,SAAS,GAAG,YAAY,GAAG,YAAY,CAAC;IAC/C,uCAAuC;IACvC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;CAC1C,CAAC;AAEF,KAAK,KAAK,GAAG,cAAc,CAAC;AAe5B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,QAAA,MAAM,SAAS,GAAI,oNAkBhB,KAAK,4CAgXP,CAAC;AAEF,eAAe,SAAS,CAAC"}
|
package/dist/src/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const PACKAGE_VERSION = "0.1.
|
|
1
|
+
export declare const PACKAGE_VERSION = "0.1.48";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/version.js
CHANGED
package/dist/version.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.js","sources":["../src/version.ts"],"sourcesContent":["export const PACKAGE_VERSION = \"0.1.
|
|
1
|
+
{"version":3,"file":"version.js","sources":["../src/version.ts"],"sourcesContent":["export const PACKAGE_VERSION = \"0.1.48\";\n"],"names":["PACKAGE_VERSION"],"mappings":"AAAO,MAAMA,IAAkB;"}
|
package/package.json
CHANGED
|
@@ -207,14 +207,6 @@ const EncoreApp = ({
|
|
|
207
207
|
setReloadKey((prev) => (typeof prev === "number" ? prev + 1 : Date.now()));
|
|
208
208
|
}, []);
|
|
209
209
|
|
|
210
|
-
// Only enable Pusher in remote mode - it doesn't make sense in local mode
|
|
211
|
-
usePusherUpdates({
|
|
212
|
-
appId,
|
|
213
|
-
pageId: pageId || undefined,
|
|
214
|
-
enabled: !isLocalMode() && !appDefinition,
|
|
215
|
-
onUpdate: handleUpdate,
|
|
216
|
-
});
|
|
217
|
-
|
|
218
210
|
const useLocalFlag = source === "local" || isLocalMode();
|
|
219
211
|
|
|
220
212
|
// Determine if we should fetch app definition
|
|
@@ -235,6 +227,14 @@ const EncoreApp = ({
|
|
|
235
227
|
// Default to dynamic if not specified
|
|
236
228
|
const isDynamicMode = !isProductionMode && !isOptimisticMode;
|
|
237
229
|
|
|
230
|
+
// Only enable Pusher in remote mode - it doesn't make sense in local mode
|
|
231
|
+
usePusherUpdates({
|
|
232
|
+
appId,
|
|
233
|
+
pageId: pageId || undefined,
|
|
234
|
+
enabled: isOptimisticMode || (!isLocalMode() && !appDefinition),
|
|
235
|
+
onUpdate: handleUpdate,
|
|
236
|
+
});
|
|
237
|
+
|
|
238
238
|
const shouldFetchApp = !isProductionMode && appId;
|
|
239
239
|
const appUrl = shouldFetchApp
|
|
240
240
|
? `/devices/apps/${appId}${useLocalFlag ? "?useLocal=1" : ""}`
|
|
@@ -281,7 +281,7 @@ const EncoreApp = ({
|
|
|
281
281
|
});
|
|
282
282
|
}
|
|
283
283
|
return Promise.resolve();
|
|
284
|
-
})
|
|
284
|
+
}),
|
|
285
285
|
);
|
|
286
286
|
})();
|
|
287
287
|
}, [assetsById]);
|
|
@@ -316,7 +316,7 @@ const EncoreApp = ({
|
|
|
316
316
|
// We want: render with initial componentCode, BUT ALSO fetch new code, and when valid, switch to it.
|
|
317
317
|
|
|
318
318
|
const [optimisticCode, setOptimisticCode] = useState<string | undefined>(
|
|
319
|
-
componentCode
|
|
319
|
+
componentCode,
|
|
320
320
|
);
|
|
321
321
|
|
|
322
322
|
useEffect(() => {
|
|
@@ -341,11 +341,19 @@ const EncoreApp = ({
|
|
|
341
341
|
const resp = await fetch(url);
|
|
342
342
|
if (resp.ok) {
|
|
343
343
|
const text = await resp.text();
|
|
344
|
-
//
|
|
345
|
-
//
|
|
346
|
-
//
|
|
347
|
-
setOptimisticCode(
|
|
348
|
-
|
|
344
|
+
// Check if code is different from what we have (either bundled or previously fetched)
|
|
345
|
+
// Use strict equality as strings are primitives.
|
|
346
|
+
// If the server returns identical code, we skip the update to avoid re-rendering.
|
|
347
|
+
setOptimisticCode((currentCode) => {
|
|
348
|
+
if (text === currentCode) {
|
|
349
|
+
logger.debug("Optimistic code is up to date");
|
|
350
|
+
return currentCode;
|
|
351
|
+
}
|
|
352
|
+
logger.debug(
|
|
353
|
+
"Refreshed optimistic component code (new version found)",
|
|
354
|
+
);
|
|
355
|
+
return text;
|
|
356
|
+
});
|
|
349
357
|
}
|
|
350
358
|
} catch (e) {
|
|
351
359
|
logger.warn("Failed to background refresh optimistic component", e);
|
|
@@ -357,7 +365,7 @@ const EncoreApp = ({
|
|
|
357
365
|
// If prop changes (e.g. from HMR or parent), sync it
|
|
358
366
|
setOptimisticCode(componentCode);
|
|
359
367
|
}
|
|
360
|
-
}, [componentCode, isOptimisticMode, appId, pageId]);
|
|
368
|
+
}, [componentCode, isOptimisticMode, appId, pageId, reloadKey]);
|
|
361
369
|
|
|
362
370
|
// Use the calculated optimistic code or the passed prop depending on mode
|
|
363
371
|
const effectiveComponentCode = isOptimisticMode
|
|
@@ -399,7 +407,7 @@ const EncoreApp = ({
|
|
|
399
407
|
|
|
400
408
|
// Manage repeating container controls (sliders, lists)
|
|
401
409
|
const repeatingContainerContextValue = useRepeatingContainers(
|
|
402
|
-
repeatingContainerControls
|
|
410
|
+
repeatingContainerControls,
|
|
403
411
|
);
|
|
404
412
|
|
|
405
413
|
// Sync input groups from props to store
|
|
@@ -452,7 +460,7 @@ const EncoreApp = ({
|
|
|
452
460
|
{(isLocalMode()
|
|
453
461
|
? // Local mode: app.json provides pages under app.data.pages
|
|
454
462
|
((app?.data as any)?.app?.data?.pages || []).map(
|
|
455
|
-
(pg: any) => pg?.id
|
|
463
|
+
(pg: any) => pg?.id,
|
|
456
464
|
)
|
|
457
465
|
: app?.data?.app.pageIds || []
|
|
458
466
|
).map((p: string) => (
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const PACKAGE_VERSION = "0.1.
|
|
1
|
+
export const PACKAGE_VERSION = "0.1.48";
|