@gooddata/sdk-ui-pluggable-host 11.57.0-alpha.1 → 11.57.0-alpha.3
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/esm/components/HostErrorBoundary.d.ts +45 -0
- package/esm/components/HostErrorBoundary.d.ts.map +1 -0
- package/esm/components/HostErrorBoundary.js +39 -0
- package/esm/components/HostUiContainer.d.ts +2 -1
- package/esm/components/HostUiContainer.d.ts.map +1 -1
- package/esm/components/HostUiContainer.js +28 -3
- package/esm/components/Root.js +1 -1
- package/esm/debug.js +1 -1
- package/esm/index.d.ts +1 -0
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +1 -0
- package/esm/platformContext/loadPlatformContext.d.ts +0 -9
- package/esm/platformContext/loadPlatformContext.d.ts.map +1 -1
- package/esm/platformContext/loadPlatformContext.js +14 -9
- package/esm/platformContext/useLoadPlatformContext.d.ts.map +1 -1
- package/esm/platformContext/useLoadPlatformContext.js +54 -2
- package/esm/sdk-ui-pluggable-host.d.ts +47 -0
- package/esm/translations/zh-HK.json +2 -2
- package/esm/ui/DefaultHostUi.d.ts.map +1 -1
- package/esm/ui/DefaultHostUi.js +16 -14
- package/package.json +21 -20
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Component, type ErrorInfo, type ReactNode } from "react";
|
|
2
|
+
import { type ILocale } from "@gooddata/sdk-model";
|
|
3
|
+
import "./Root.scss";
|
|
4
|
+
/**
|
|
5
|
+
* Properties of {@link HostErrorBoundary}.
|
|
6
|
+
*
|
|
7
|
+
* @alpha
|
|
8
|
+
*/
|
|
9
|
+
export interface IHostErrorBoundaryProps {
|
|
10
|
+
/**
|
|
11
|
+
* Reports the caught error together with the React component stack it was thrown from.
|
|
12
|
+
*/
|
|
13
|
+
onError?: (error: string, context: string) => void;
|
|
14
|
+
locale?: ILocale;
|
|
15
|
+
/**
|
|
16
|
+
* A caught error is cleared when this value changes, so the subtree gets a fresh
|
|
17
|
+
* mount attempt (e.g. on navigation) instead of showing the fallback forever.
|
|
18
|
+
*/
|
|
19
|
+
resetKey?: string | number;
|
|
20
|
+
children: ReactNode;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* State of {@link HostErrorBoundary}.
|
|
24
|
+
*
|
|
25
|
+
* @alpha
|
|
26
|
+
*/
|
|
27
|
+
export interface IHostErrorBoundaryState {
|
|
28
|
+
hasError: boolean;
|
|
29
|
+
resetKey?: string | number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Last-resort boundary for errors escaping to the host application root. The host page is
|
|
33
|
+
* composed of nested React roots rendering into DOM owned by the topmost root, so without
|
|
34
|
+
* a boundary React unmounts the entire tree — header included — leaving a blank page.
|
|
35
|
+
*
|
|
36
|
+
* @alpha
|
|
37
|
+
*/
|
|
38
|
+
export declare class HostErrorBoundary extends Component<IHostErrorBoundaryProps, IHostErrorBoundaryState> {
|
|
39
|
+
state: IHostErrorBoundaryState;
|
|
40
|
+
static getDerivedStateFromError(): Partial<IHostErrorBoundaryState>;
|
|
41
|
+
static getDerivedStateFromProps(props: IHostErrorBoundaryProps, state: IHostErrorBoundaryState): Partial<IHostErrorBoundaryState> | null;
|
|
42
|
+
componentDidCatch(error: Error, info: ErrorInfo): void;
|
|
43
|
+
render(): ReactNode;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=HostErrorBoundary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HostErrorBoundary.d.ts","sourceRoot":"","sources":["../../src/components/HostErrorBoundary.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAIlE,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAMnD,OAAO,aAAa,CAAC;AAIrB;;;;GAIG;AACH,MAAM,WAAW,uBAAuB;IACpC;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,QAAQ,EAAE,SAAS,CAAC;CACvB;AAED;;;;GAIG;AACH,MAAM,WAAW,uBAAuB;IACpC,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC9B;AAED;;;;;;GAMG;AACH,qBAAa,iBAAkB,SAAQ,SAAS,CAAC,uBAAuB,EAAE,uBAAuB,CAAC;IACrF,KAAK,EAAE,uBAAuB,CAAuB;IAE9D,MAAM,CAAC,wBAAwB,IAAI,OAAO,CAAC,uBAAuB,CAAC,CAElE;IAED,MAAM,CAAC,wBAAwB,CAC3B,KAAK,EAAE,uBAAuB,EAC9B,KAAK,EAAE,uBAAuB,GAC/B,OAAO,CAAC,uBAAuB,CAAC,GAAG,IAAI,CAKzC;IAEQ,iBAAiB,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,GAAG,IAAI,CAE9D;IAEQ,MAAM,IAAI,SAAS,CAkB3B;CACJ"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
// (C) 2026 GoodData Corporation
|
|
3
|
+
import { Component } from "react";
|
|
4
|
+
import { FormattedMessage } from "react-intl";
|
|
5
|
+
import { DEFAULT_LANGUAGE } from "@gooddata/sdk-ui";
|
|
6
|
+
import { bemFactory } from "@gooddata/sdk-ui-kit";
|
|
7
|
+
import { HostIntlProvider } from "../ui/HostIntlProvider.js";
|
|
8
|
+
import "./Root.scss";
|
|
9
|
+
const { e } = bemFactory("gd-host-root");
|
|
10
|
+
/**
|
|
11
|
+
* Last-resort boundary for errors escaping to the host application root. The host page is
|
|
12
|
+
* composed of nested React roots rendering into DOM owned by the topmost root, so without
|
|
13
|
+
* a boundary React unmounts the entire tree — header included — leaving a blank page.
|
|
14
|
+
*
|
|
15
|
+
* @alpha
|
|
16
|
+
*/
|
|
17
|
+
export class HostErrorBoundary extends Component {
|
|
18
|
+
state = { hasError: false };
|
|
19
|
+
static getDerivedStateFromError() {
|
|
20
|
+
return { hasError: true };
|
|
21
|
+
}
|
|
22
|
+
static getDerivedStateFromProps(props, state) {
|
|
23
|
+
if (props.resetKey !== state.resetKey) {
|
|
24
|
+
return { resetKey: props.resetKey, hasError: false };
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
componentDidCatch(error, info) {
|
|
29
|
+
this.props.onError?.(String(error), info.componentStack ?? "");
|
|
30
|
+
}
|
|
31
|
+
render() {
|
|
32
|
+
if (!this.state.hasError) {
|
|
33
|
+
return this.props.children;
|
|
34
|
+
}
|
|
35
|
+
// Exception details go to telemetry (onError) and the console only — rendering them
|
|
36
|
+
// could expose internal backend details in the UI.
|
|
37
|
+
return (_jsx(HostIntlProvider, { locale: this.props.locale ?? DEFAULT_LANGUAGE, children: _jsx("div", { className: e("error"), role: "alert", children: _jsx("h1", { children: _jsx(FormattedMessage, { id: "gs.host.error.somethingWentWrong" }) }) }) }));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -7,11 +7,12 @@ export interface IHostUiContainerProps {
|
|
|
7
7
|
apps: PluggableApplicationRegistryItem[];
|
|
8
8
|
pathname: string;
|
|
9
9
|
routerNavigate: NavigateFunction;
|
|
10
|
+
onError?: (error: string, context: string) => void;
|
|
10
11
|
}
|
|
11
12
|
/**
|
|
12
13
|
* Mounts the host UI module into a container div, then renders the active
|
|
13
14
|
* pluggable application into the host's app slot. Handles all lifecycle
|
|
14
15
|
* updates (context, apps, pathname) via the host mount handle.
|
|
15
16
|
*/
|
|
16
|
-
export declare function HostUiContainer({ ctx, apps, pathname, routerNavigate }: IHostUiContainerProps): import("react/jsx-runtime").JSX.Element;
|
|
17
|
+
export declare function HostUiContainer({ ctx, apps, pathname, routerNavigate, onError }: IHostUiContainerProps): import("react/jsx-runtime").JSX.Element;
|
|
17
18
|
//# sourceMappingURL=HostUiContainer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HostUiContainer.d.ts","sourceRoot":"","sources":["../../src/components/HostUiContainer.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"HostUiContainer.d.ts","sourceRoot":"","sources":["../../src/components/HostUiContainer.tsx"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,EAA0B,KAAK,gCAAgC,EAAE,MAAM,qBAAqB,CAAC;AACpG,OAAO,EAGH,KAAK,gBAAgB,EAExB,MAAM,2CAA2C,CAAC;AAiBnD,OAAO,wBAAwB,CAAC;AAMhC,MAAM,WAAW,qBAAqB;IAClC,GAAG,EAAE,gBAAgB,CAAC;IACtB,IAAI,EAAE,gCAAgC,EAAE,CAAC;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,gBAAgB,CAAC;IACjC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACtD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,cAAc,EAAE,OAAO,EAAE,EAAE,qBAAqB,2CA0VtG"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
// (C) 2026 GoodData Corporation
|
|
3
3
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { isEqual, omit } from "lodash-es";
|
|
4
5
|
import { createRoot } from "react-dom/client";
|
|
5
6
|
import { resolveLocale, useAutoupdateRef } from "@gooddata/sdk-ui";
|
|
6
7
|
import { now } from "../debug.js";
|
|
@@ -12,19 +13,40 @@ import { HostIntlProvider } from "../ui/HostIntlProvider.js";
|
|
|
12
13
|
import { PluggableApplicationRenderer } from "../ui/PluggableApplicationRenderer.js";
|
|
13
14
|
import "./HostUiContainer.scss";
|
|
14
15
|
import { resolveHostUiModule } from "../ui/resolveHostUiModule.js";
|
|
16
|
+
import { HostErrorBoundary } from "./HostErrorBoundary.js";
|
|
15
17
|
import { runGuardedNavigation } from "./navigationGuard.js";
|
|
16
18
|
/**
|
|
17
19
|
* Mounts the host UI module into a container div, then renders the active
|
|
18
20
|
* pluggable application into the host's app slot. Handles all lifecycle
|
|
19
21
|
* updates (context, apps, pathname) via the host mount handle.
|
|
20
22
|
*/
|
|
21
|
-
export function HostUiContainer({ ctx, apps, pathname, routerNavigate }) {
|
|
23
|
+
export function HostUiContainer({ ctx, apps, pathname, routerNavigate, onError }) {
|
|
22
24
|
const activeInternalApplication = useMemo(() => getActiveInternalApplication(apps, ctx, pathname), [apps, ctx, pathname]);
|
|
23
25
|
const containerRef = useRef(null);
|
|
24
26
|
const handleRef = useRef(undefined);
|
|
25
27
|
const appRootRef = useRef(undefined);
|
|
26
28
|
const [hostReady, setHostReady] = useState(false);
|
|
27
29
|
const latestMountStateRef = useAutoupdateRef({ ctx, apps, pathname });
|
|
30
|
+
const onErrorRef = useAutoupdateRef(onError);
|
|
31
|
+
const reportError = useCallback((error, context) => onErrorRef.current?.(error, context), [onErrorRef]);
|
|
32
|
+
// Mounted apps hot-apply only auth from updateContext — every other context field
|
|
33
|
+
// (permissions, settings, entitlements, org permissions, …) is seeded into their stores
|
|
34
|
+
// at mount only. A refresh that lands ANY content change outside auth must therefore
|
|
35
|
+
// remount the app. Content comparison, not identity: every refresh produces new objects,
|
|
36
|
+
// and remounting on each token refresh would defeat keeping the app mounted at all.
|
|
37
|
+
// The color palette is also excluded: its load is not part of the platform-context gate,
|
|
38
|
+
// so it legitimately arrives after the first ready context — a styling update, not a
|
|
39
|
+
// reason to abort the mounted app.
|
|
40
|
+
const [appContextEpoch, setAppContextEpoch] = useState(0);
|
|
41
|
+
const prevAppContextRef = useRef(ctx);
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
const prev = prevAppContextRef.current;
|
|
44
|
+
prevAppContextRef.current = ctx;
|
|
45
|
+
const epochFields = (c) => omit(c, ["auth", "colorPalette"]);
|
|
46
|
+
if (prev !== ctx && !isEqual(epochFields(prev), epochFields(ctx))) {
|
|
47
|
+
setAppContextEpoch((epoch) => epoch + 1);
|
|
48
|
+
}
|
|
49
|
+
}, [ctx]);
|
|
28
50
|
const [headerOptions, setHeaderOptions] = useState(undefined);
|
|
29
51
|
const [pageTitle, setPageTitle] = useState(undefined);
|
|
30
52
|
// Host-owned chat open-state, sourced from HostChat and forwarded to the active pluggable app.
|
|
@@ -124,6 +146,7 @@ export function HostUiContainer({ ctx, apps, pathname, routerNavigate }) {
|
|
|
124
146
|
replace: navigationMountRef.current.replace,
|
|
125
147
|
onChatToggleRequested: navigationMountRef.current.onChatToggleRequested,
|
|
126
148
|
onAskAiAssistant: navigationMountRef.current.onAskAiAssistant,
|
|
149
|
+
onError: reportError,
|
|
127
150
|
});
|
|
128
151
|
handleRef.current = handle;
|
|
129
152
|
appRootRef.current = createRoot(handle.getAppContainer());
|
|
@@ -152,7 +175,7 @@ export function HostUiContainer({ ctx, apps, pathname, routerNavigate }) {
|
|
|
152
175
|
});
|
|
153
176
|
};
|
|
154
177
|
// Mount only once; updates are pushed via handle
|
|
155
|
-
}, [latestMountStateRef, navigationMountRef]);
|
|
178
|
+
}, [latestMountStateRef, navigationMountRef, reportError]);
|
|
156
179
|
// Push updates when context, apps, or pathname change after initial mount
|
|
157
180
|
useEffect(() => {
|
|
158
181
|
if (!hostReady) {
|
|
@@ -218,7 +241,7 @@ export function HostUiContainer({ ctx, apps, pathname, routerNavigate }) {
|
|
|
218
241
|
return;
|
|
219
242
|
}
|
|
220
243
|
if (activeInternalApplication) {
|
|
221
|
-
appRootRef.current.render(_jsx(HostIntlProvider, { locale: resolveLocale(ctx.preferredLocale), children: _jsx(PluggableApplicationRenderer, { app: activeInternalApplication, ctx: ctx, pathname: pathname, aiAssistantOpen: aiAssistantOpen, onOpenAiAssistant: requestOpenAi, onCloseAiAssistant: requestCloseAi, onAiAssistantContext: setAiAssistantContext, aiLinkClickHandlerRef: appAiLinkClickRef, navigationRequestRef: appNavigationRequestRef, aiEventReceiveRef: appEventReceiveRef, onHeaderChange: onHeaderChange, onDocumentTitleChange: onDocumentTitleChange }, activeInternalApplication.id) }));
|
|
244
|
+
appRootRef.current.render(_jsx(HostErrorBoundary, { resetKey: pathname, onError: reportError, locale: resolveLocale(ctx.preferredLocale), children: _jsx(HostIntlProvider, { locale: resolveLocale(ctx.preferredLocale), children: _jsx(PluggableApplicationRenderer, { app: activeInternalApplication, ctx: ctx, pathname: pathname, aiAssistantOpen: aiAssistantOpen, onOpenAiAssistant: requestOpenAi, onCloseAiAssistant: requestCloseAi, onAiAssistantContext: setAiAssistantContext, aiLinkClickHandlerRef: appAiLinkClickRef, navigationRequestRef: appNavigationRequestRef, aiEventReceiveRef: appEventReceiveRef, onHeaderChange: onHeaderChange, onDocumentTitleChange: onDocumentTitleChange }, activeInternalApplication.id) }) }, `${activeInternalApplication.id}:${appContextEpoch}`));
|
|
222
245
|
}
|
|
223
246
|
else {
|
|
224
247
|
appRootRef.current.render(null);
|
|
@@ -226,11 +249,13 @@ export function HostUiContainer({ ctx, apps, pathname, routerNavigate }) {
|
|
|
226
249
|
}, [
|
|
227
250
|
hostReady,
|
|
228
251
|
activeInternalApplication,
|
|
252
|
+
appContextEpoch,
|
|
229
253
|
ctx,
|
|
230
254
|
onHeaderChange,
|
|
231
255
|
onDocumentTitleChange,
|
|
232
256
|
pathname,
|
|
233
257
|
aiAssistantOpen,
|
|
258
|
+
reportError,
|
|
234
259
|
requestOpenAi,
|
|
235
260
|
requestCloseAi,
|
|
236
261
|
setAiAssistantContext,
|
package/esm/components/Root.js
CHANGED
|
@@ -62,5 +62,5 @@ function ReadyRoot({ ctx, callbacks }) {
|
|
|
62
62
|
if (redirect.state === "error") {
|
|
63
63
|
return (_jsx(HostIntlProvider, { locale: resolveLocale(ctx.preferredLocale), children: _jsxs("main", { className: e("error"), children: [_jsx("h1", { children: _jsx(FormattedMessage, { id: "gs.host.error.somethingWentWrong" }) }), _jsx("p", { children: redirect.error })] }) }));
|
|
64
64
|
}
|
|
65
|
-
return _jsx(HostUiContainer, { ctx: ctx, apps: apps, pathname: pathname, routerNavigate: routerNavigate });
|
|
65
|
+
return (_jsx(HostUiContainer, { ctx: ctx, apps: apps, pathname: pathname, routerNavigate: routerNavigate, onError: callbacks?.onError }));
|
|
66
66
|
}
|
package/esm/debug.js
CHANGED
package/esm/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export { registerLocalApplications } from "./registry/pluggableApplicationsRegis
|
|
|
6
6
|
export { registerAppLifecycleCallbacks } from "./loader/pluggableApplicationsLoader.js";
|
|
7
7
|
export { mapLegacyUrlToHost, type ILegacyLocation } from "./loader/legacyRedirect.js";
|
|
8
8
|
export { Root, type IRootCallbacks } from "./components/Root.js";
|
|
9
|
+
export { HostErrorBoundary, type IHostErrorBoundaryProps, type IHostErrorBoundaryState, } from "./components/HostErrorBoundary.js";
|
|
9
10
|
export { type IAppLifecycleCallbacks } from "./types/lifecycle.js";
|
|
10
11
|
export { STALE_CHUNK_RELOAD_PARAM, installPreloadErrorHandler, installVersionWatcher, reloadForStaleChunks, setStaleChunkReloadListener, type IStaleChunkReloadInfo, type IVersionWatcherOptions, type StaleChunkReloadListener, } from "./lib/chunkReloadGuard.js";
|
|
11
12
|
export { dispatchHostNotification } from "./lib/hostNotifications.js";
|
package/esm/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACjF,OAAO,EAAE,gCAAgC,EAAE,MAAM,6CAA6C,CAAC;AAC/F,OAAO,EAAE,KAAK,6BAA6B,EAAE,MAAM,0CAA0C,CAAC;AAE9F,OAAO,EACH,+BAA+B,EAC/B,KAAK,+BAA+B,GACvC,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAE,yBAAyB,EAAE,MAAM,6CAA6C,CAAC;AACxF,OAAO,EAAE,6BAA6B,EAAE,MAAM,yCAAyC,CAAC;AAExF,OAAO,EAAE,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAEtF,OAAO,EAAE,IAAI,EAAE,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACjE,OAAO,EAAE,KAAK,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAEnE,OAAO,EACH,wBAAwB,EACxB,0BAA0B,EAC1B,qBAAqB,EACrB,oBAAoB,EACpB,2BAA2B,EAC3B,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC3B,KAAK,wBAAwB,GAChC,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAEtE,OAAO,EACH,uBAAuB,EACvB,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,GAC1B,MAAM,8BAA8B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACjF,OAAO,EAAE,gCAAgC,EAAE,MAAM,6CAA6C,CAAC;AAC/F,OAAO,EAAE,KAAK,6BAA6B,EAAE,MAAM,0CAA0C,CAAC;AAE9F,OAAO,EACH,+BAA+B,EAC/B,KAAK,+BAA+B,GACvC,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAE,yBAAyB,EAAE,MAAM,6CAA6C,CAAC;AACxF,OAAO,EAAE,6BAA6B,EAAE,MAAM,yCAAyC,CAAC;AAExF,OAAO,EAAE,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAEtF,OAAO,EAAE,IAAI,EAAE,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACjE,OAAO,EACH,iBAAiB,EACjB,KAAK,uBAAuB,EAC5B,KAAK,uBAAuB,GAC/B,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,KAAK,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAEnE,OAAO,EACH,wBAAwB,EACxB,0BAA0B,EAC1B,qBAAqB,EACrB,oBAAoB,EACpB,2BAA2B,EAC3B,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC3B,KAAK,wBAAwB,GAChC,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAEtE,OAAO,EACH,uBAAuB,EACvB,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,GAC1B,MAAM,8BAA8B,CAAC"}
|
package/esm/index.js
CHANGED
|
@@ -6,6 +6,7 @@ export { registerLocalApplications } from "./registry/pluggableApplicationsRegis
|
|
|
6
6
|
export { registerAppLifecycleCallbacks } from "./loader/pluggableApplicationsLoader.js";
|
|
7
7
|
export { mapLegacyUrlToHost } from "./loader/legacyRedirect.js";
|
|
8
8
|
export { Root } from "./components/Root.js";
|
|
9
|
+
export { HostErrorBoundary, } from "./components/HostErrorBoundary.js";
|
|
9
10
|
export { STALE_CHUNK_RELOAD_PARAM, installPreloadErrorHandler, installVersionWatcher, reloadForStaleChunks, setStaleChunkReloadListener, } from "./lib/chunkReloadGuard.js";
|
|
10
11
|
export { dispatchHostNotification } from "./lib/hostNotifications.js";
|
|
11
12
|
export { setHostPricingExtension, } from "./ui/useHostChromePricing.js";
|
|
@@ -15,15 +15,6 @@ export interface ILoadPlatformContextOptions {
|
|
|
15
15
|
auth?: IAuthCredentials;
|
|
16
16
|
callbacks?: ILoadPlatformContextCallbacks;
|
|
17
17
|
}
|
|
18
|
-
/**
|
|
19
|
-
* This approach is not correct - the host app should only care about the first part of the URL,
|
|
20
|
-
* and the module should only care about the second part.
|
|
21
|
-
* Here we are crossing the boundaries, and the host app is checking the full URL.
|
|
22
|
-
* The proper fix requires larger changes not only here, but in gdc-nas as well.
|
|
23
|
-
* This is scheduled to happen with the gdc-reports module.
|
|
24
|
-
*
|
|
25
|
-
* @internal - exported for testing
|
|
26
|
-
*/
|
|
27
18
|
export declare function detectExportMode(): boolean;
|
|
28
19
|
export declare function loadPlatformContext(options?: ILoadPlatformContextOptions): Promise<IBackendPlatformContext>;
|
|
29
20
|
//# sourceMappingURL=loadPlatformContext.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loadPlatformContext.d.ts","sourceRoot":"","sources":["../../src/platformContext/loadPlatformContext.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,2CAA2C,CAAC;AAMlF,OAAO,EAAE,KAAK,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAE1D,qBAAa,4BAA6B,SAAQ,KAAK;IACnD,cAGC;CACJ;AAED;;GAEG;AACH,MAAM,WAAW,6BAA6B;IAC1C,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5D,QAAQ,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;CAC3C;AAED,MAAM,WAAW,2BAA2B;IACxC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,SAAS,CAAC,EAAE,6BAA6B,CAAC;CAC7C;
|
|
1
|
+
{"version":3,"file":"loadPlatformContext.d.ts","sourceRoot":"","sources":["../../src/platformContext/loadPlatformContext.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,2CAA2C,CAAC;AAMlF,OAAO,EAAE,KAAK,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAE1D,qBAAa,4BAA6B,SAAQ,KAAK;IACnD,cAGC;CACJ;AAED;;GAEG;AACH,MAAM,WAAW,6BAA6B;IAC1C,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5D,QAAQ,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;CAC3C;AAED,MAAM,WAAW,2BAA2B;IACxC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,SAAS,CAAC,EAAE,6BAA6B,CAAC;CAC7C;AAqBD,wBAAgB,gBAAgB,IAAI,OAAO,CAkB1C;AAED,wBAAsB,mBAAmB,CACrC,OAAO,GAAE,2BAAgC,GAC1C,OAAO,CAAC,uBAAuB,CAAC,CA4ClC"}
|
|
@@ -22,19 +22,24 @@ function throwIfAborted(signal) {
|
|
|
22
22
|
*
|
|
23
23
|
* @internal - exported for testing
|
|
24
24
|
*/
|
|
25
|
+
// Dashboards name their export render mode `export`, reports `export_slideshow`; both are what the
|
|
26
|
+
// backend exporter opens.
|
|
27
|
+
const EXPORT_MODE_VALUES = new Set(["export", "export_slideshow"]);
|
|
25
28
|
export function detectExportMode() {
|
|
26
29
|
if (typeof window === "undefined") {
|
|
27
30
|
return false;
|
|
28
31
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
for (const
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
32
|
+
// A hash-routed app carries its query inside the fragment; a browser-routed one in the search
|
|
33
|
+
// string. Either may name an export mode.
|
|
34
|
+
const { search, hash } = window.location;
|
|
35
|
+
const hashQuery = hash.indexOf("?");
|
|
36
|
+
const queries = [search ?? "", hashQuery === -1 ? "" : hash.slice(hashQuery + 1)];
|
|
37
|
+
for (const query of queries) {
|
|
38
|
+
for (const [key, value] of new URLSearchParams(query)) {
|
|
39
|
+
const name = key.toLowerCase();
|
|
40
|
+
if ((name === "mode" || name === "displaymode") && EXPORT_MODE_VALUES.has(value.toLowerCase())) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
38
43
|
}
|
|
39
44
|
}
|
|
40
45
|
return false;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useLoadPlatformContext.d.ts","sourceRoot":"","sources":["../../src/platformContext/useLoadPlatformContext.ts"],"names":[],"mappings":"AAOA,OAAO,EAA2B,KAAK,gBAAgB,EAAE,MAAM,2CAA2C,CAAC;AAM3G,OAAO,EACH,KAAK,6BAA6B,EAGrC,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACH,KAAK,uBAAuB,EAC5B,KAAK,0BAA0B,EAElC,MAAM,YAAY,CAAC;AAYpB;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,IAAI,0BAA0B,CAAC,gBAAgB,CAAC,
|
|
1
|
+
{"version":3,"file":"useLoadPlatformContext.d.ts","sourceRoot":"","sources":["../../src/platformContext/useLoadPlatformContext.ts"],"names":[],"mappings":"AAOA,OAAO,EAA2B,KAAK,gBAAgB,EAAE,MAAM,2CAA2C,CAAC;AAM3G,OAAO,EACH,KAAK,6BAA6B,EAGrC,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACH,KAAK,uBAAuB,EAC5B,KAAK,0BAA0B,EAElC,MAAM,YAAY,CAAC;AAYpB;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,IAAI,0BAA0B,CAAC,gBAAgB,CAAC,CAoJrF;AAED;;;;GAIG;AACH,MAAM,WAAW,+BAA+B;IAC5C,YAAY,CAAC,SAAS,EAAE,6BAA6B,GAAG,IAAI,CAAC;IAC7D,SAAS,EAAE,MAAM,QAAQ,CAAC,0BAA0B,CAAC,uBAAuB,CAAC,CAAC,CAAC;IAC/E,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAC;IAChD,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7B;AAmED;;GAEG;AACH,eAAO,MAAM,8BAA8B,EAAE,+BACA,CAAC;AAE9C;;;;;;GAMG;AACH,wBAAgB,gCAAgC,CAAC,SAAS,EAAE,6BAA6B,GAAG,IAAI,CAE/F"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// (C) 2026 GoodData Corporation
|
|
2
|
-
import { useMemo, useSyncExternalStore } from "react";
|
|
2
|
+
import { useEffect, useMemo, useState, useSyncExternalStore } from "react";
|
|
3
3
|
import { useLocation } from "react-router";
|
|
4
4
|
import { isLocale } from "@gooddata/sdk-model";
|
|
5
5
|
import { isProduction } from "../lib/isProduction.js";
|
|
@@ -33,7 +33,8 @@ export function useLoadPlatformContext() {
|
|
|
33
33
|
const workspaceSettingsState = useWorkspaceSettings(backend, workspaceId);
|
|
34
34
|
const workspaceColorPaletteState = useWorkspaceColorPalette(backend, workspaceId);
|
|
35
35
|
const workspaceThemeState = useWorkspaceTheme(backend, workspaceId);
|
|
36
|
-
|
|
36
|
+
const [lastReady, setLastReady] = useState(undefined);
|
|
37
|
+
const result = useMemo(() => {
|
|
37
38
|
if (backendContext.state !== "ready") {
|
|
38
39
|
return backendContext;
|
|
39
40
|
}
|
|
@@ -83,6 +84,57 @@ export function useLoadPlatformContext() {
|
|
|
83
84
|
workspaceColorPaletteState,
|
|
84
85
|
workspaceThemeState,
|
|
85
86
|
]);
|
|
87
|
+
// Remember the last COMMITTED result in state (an effect never runs for a discarded
|
|
88
|
+
// render, so an abandoned concurrent render cannot leak its context into the fallback).
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
if (result.state === "ready") {
|
|
91
|
+
setLastReady({ workspaceId, result });
|
|
92
|
+
}
|
|
93
|
+
else if (result.state === "error") {
|
|
94
|
+
setLastReady(undefined);
|
|
95
|
+
}
|
|
96
|
+
}, [result, workspaceId]);
|
|
97
|
+
// Fallback served while the same workspace's data refreshes: the previous workspace-scoped
|
|
98
|
+
// values merged over the CURRENT backend context, so a refreshed auth reaches the mounted
|
|
99
|
+
// app immediately instead of waiting for every workspace request to finish.
|
|
100
|
+
const refreshingFallback = useMemo(() => {
|
|
101
|
+
if (backendContext.state !== "ready" ||
|
|
102
|
+
lastReady === undefined ||
|
|
103
|
+
lastReady.workspaceId !== workspaceId ||
|
|
104
|
+
lastReady.result.state !== "ready" ||
|
|
105
|
+
// A different principal or organization must never see the previous principal's
|
|
106
|
+
// permissions/settings, even for the duration of the refresh — fall through to
|
|
107
|
+
// the loader instead of merging across principals.
|
|
108
|
+
lastReady.result.ctx.user?.login !== backendContext.ctx.user?.login ||
|
|
109
|
+
lastReady.result.ctx.organization?.id !== backendContext.ctx.organization?.id) {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
const { currentApplicationScope, currentWorkspaceId, workspacePermissions, workspaceSettings, colorPalette, settings, preferredLocale, theme, } = lastReady.result.ctx;
|
|
113
|
+
return {
|
|
114
|
+
state: "ready",
|
|
115
|
+
ctx: {
|
|
116
|
+
...backendContext.ctx,
|
|
117
|
+
currentApplicationScope,
|
|
118
|
+
currentWorkspaceId,
|
|
119
|
+
workspacePermissions,
|
|
120
|
+
workspaceSettings,
|
|
121
|
+
colorPalette,
|
|
122
|
+
settings,
|
|
123
|
+
preferredLocale,
|
|
124
|
+
theme,
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}, [backendContext, lastReady, workspaceId]);
|
|
128
|
+
// Keep the previously rendered context while the same workspace's data refreshes (backend
|
|
129
|
+
// re-creation after a token refresh, reload-platform-context requests). Returning "loading"
|
|
130
|
+
// here would swap the whole HostUiContainer for a loader, unmounting the mounted app and
|
|
131
|
+
// cancelling its in-flight work; the refreshed context is pushed via handle.updateContext
|
|
132
|
+
// instead. A workspace change must NOT reuse the old context (permissions/theme belong to
|
|
133
|
+
// the previous workspace), so it still falls through to the loader.
|
|
134
|
+
if (result.state === "loading" && refreshingFallback !== undefined) {
|
|
135
|
+
return refreshingFallback;
|
|
136
|
+
}
|
|
137
|
+
return result;
|
|
86
138
|
}
|
|
87
139
|
class BackendPlatformContextProviderClass {
|
|
88
140
|
_loadingStateMemo = { state: "loading" };
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { Component } from 'react';
|
|
2
|
+
import { ErrorInfo } from 'react';
|
|
1
3
|
import { IAnalyticalBackend } from '@gooddata/sdk-backend-spi';
|
|
2
4
|
import { IHostUiNotification } from '@gooddata/sdk-pluggable-application-model';
|
|
5
|
+
import { ILocale } from '@gooddata/sdk-model';
|
|
3
6
|
import { IPlatformContext } from '@gooddata/sdk-pluggable-application-model';
|
|
4
7
|
import { IPluggableApp } from '@gooddata/sdk-pluggable-application-model';
|
|
5
8
|
import { IPluggableAppTelemetryCallbacks } from '@gooddata/sdk-pluggable-application-model';
|
|
@@ -24,6 +27,21 @@ export declare function dispatchHostNotification(notification: IHostUiNotificati
|
|
|
24
27
|
*/
|
|
25
28
|
export declare function getBackend(): IAnalyticalBackend;
|
|
26
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Last-resort boundary for errors escaping to the host application root. The host page is
|
|
32
|
+
* composed of nested React roots rendering into DOM owned by the topmost root, so without
|
|
33
|
+
* a boundary React unmounts the entire tree — header included — leaving a blank page.
|
|
34
|
+
*
|
|
35
|
+
* @alpha
|
|
36
|
+
*/
|
|
37
|
+
export declare class HostErrorBoundary extends Component<IHostErrorBoundaryProps, IHostErrorBoundaryState> {
|
|
38
|
+
state: IHostErrorBoundaryState;
|
|
39
|
+
static getDerivedStateFromError(): Partial<IHostErrorBoundaryState>;
|
|
40
|
+
static getDerivedStateFromProps(props: IHostErrorBoundaryProps, state: IHostErrorBoundaryState): Partial<IHostErrorBoundaryState> | null;
|
|
41
|
+
componentDidCatch(error: Error, info: ErrorInfo): void;
|
|
42
|
+
render(): ReactNode;
|
|
43
|
+
}
|
|
44
|
+
|
|
27
45
|
/**
|
|
28
46
|
* @alpha
|
|
29
47
|
*/
|
|
@@ -60,6 +78,35 @@ export declare interface IHostChromePricing {
|
|
|
60
78
|
onUpsellButtonClick: () => void;
|
|
61
79
|
}
|
|
62
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Properties of {@link HostErrorBoundary}.
|
|
83
|
+
*
|
|
84
|
+
* @alpha
|
|
85
|
+
*/
|
|
86
|
+
export declare interface IHostErrorBoundaryProps {
|
|
87
|
+
/**
|
|
88
|
+
* Reports the caught error together with the React component stack it was thrown from.
|
|
89
|
+
*/
|
|
90
|
+
onError?: (error: string, context: string) => void;
|
|
91
|
+
locale?: ILocale;
|
|
92
|
+
/**
|
|
93
|
+
* A caught error is cleared when this value changes, so the subtree gets a fresh
|
|
94
|
+
* mount attempt (e.g. on navigation) instead of showing the fallback forever.
|
|
95
|
+
*/
|
|
96
|
+
resetKey?: string | number;
|
|
97
|
+
children: ReactNode;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* State of {@link HostErrorBoundary}.
|
|
102
|
+
*
|
|
103
|
+
* @alpha
|
|
104
|
+
*/
|
|
105
|
+
export declare interface IHostErrorBoundaryState {
|
|
106
|
+
hasError: boolean;
|
|
107
|
+
resetKey?: string | number;
|
|
108
|
+
}
|
|
109
|
+
|
|
63
110
|
/**
|
|
64
111
|
* @alpha
|
|
65
112
|
*/
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"gs.host.contractExpired.trial.description": "如果您想繼續使用GoodData Cloud,請聯繫我們。",
|
|
19
19
|
"gs.host.contractExpired.nonTrial.title": "您的存取權限已被鎖定。",
|
|
20
20
|
"gs.host.contractExpired.nonTrial.description": "請與帳戶管理員聯繫以重新獲得存取權限。",
|
|
21
|
-
"gs.host.contractExpired.buttonTitle": "
|
|
21
|
+
"gs.host.contractExpired.buttonTitle": "聯絡我們",
|
|
22
22
|
"gs.host.error.failedToLoad": "無法載入",
|
|
23
23
|
"gs.host.error.pageNotFound": "找不到頁面",
|
|
24
24
|
"gs.host.error.pageNotFoundDescription": "您正在尋找的頁面不存在,或者您沒有權限存取該頁面。",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"gs.host.error.appBuildExpired": "此應用程式已過期,必須由其作者重新建置後才能再次執行。",
|
|
28
28
|
"gs.host.error.appSecurityMetadataMissing": "此應用程式缺少必要的安全性中繼資料,無法載入。",
|
|
29
29
|
"gs.host.demoApp.validUntil": "示範應用程式有效期至 {date}",
|
|
30
|
-
"messages.genAi.visualisation.saved.success": "
|
|
30
|
+
"messages.genAi.visualisation.saved.success": "很好!我們已儲存您的可視化。",
|
|
31
31
|
"messages.genAi.visualisation.saved.error": "糟糕,保存可視化時出現問題。",
|
|
32
32
|
"messages.genAi.visualisation.saved.error.detail": "{errorType}: {errorMessage}",
|
|
33
33
|
"messages.genAi.visualisation.link.copied": "可視化連結已複製到剪貼板。",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DefaultHostUi.d.ts","sourceRoot":"","sources":["../../src/ui/DefaultHostUi.tsx"],"names":[],"mappings":"AAQA,OAAO,EAEH,KAAK,aAAa,EAKrB,MAAM,2CAA2C,CAAC;
|
|
1
|
+
{"version":3,"file":"DefaultHostUi.d.ts","sourceRoot":"","sources":["../../src/ui/DefaultHostUi.tsx"],"names":[],"mappings":"AAQA,OAAO,EAEH,KAAK,aAAa,EAKrB,MAAM,2CAA2C,CAAC;AAOnD,OAAO,sBAAsB,CAAC;AA+N9B;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,EAAE,aAEjC,CAAC"}
|
package/esm/ui/DefaultHostUi.js
CHANGED
|
@@ -3,10 +3,12 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
3
3
|
import { useLayoutEffect, useRef, useState } from "react";
|
|
4
4
|
import { flushSync } from "react-dom";
|
|
5
5
|
import { createRoot } from "react-dom/client";
|
|
6
|
+
import { resolveLocale } from "@gooddata/sdk-ui";
|
|
7
|
+
import { HostErrorBoundary } from "../components/HostErrorBoundary.js";
|
|
6
8
|
import { HostChrome } from "./HostChrome.js";
|
|
7
9
|
import { e } from "./hostChromeBem.js";
|
|
8
10
|
import "./DefaultHostUi.scss";
|
|
9
|
-
function HostUiBridge({ initialCtx, initialApps, initialPathname, navigate, replace, onChatToggleRequested, onAskAiAssistant, onAppContainerReady, onReady, }) {
|
|
11
|
+
function HostUiBridge({ initialCtx, initialApps, initialPathname, navigate, replace, onChatToggleRequested, onAskAiAssistant, onError, onAppContainerReady, onReady, }) {
|
|
10
12
|
const [ctx, setCtx] = useState(initialCtx);
|
|
11
13
|
const [apps, setApps] = useState(initialApps);
|
|
12
14
|
const [pathname, setPathname] = useState(initialPathname);
|
|
@@ -26,13 +28,13 @@ function HostUiBridge({ initialCtx, initialApps, initialPathname, navigate, repl
|
|
|
26
28
|
// Only call once on mount
|
|
27
29
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
28
30
|
}, []);
|
|
29
|
-
return (_jsx(HostChrome, { ctx: ctx, resolvedApplications: apps, pathname: pathname, onNavigate: navigate, onReplace: replace, headerOptions: headerOptions, notification: notification, showChatItem: chatState.showChatItem, chatIsOpen: chatState.isOpen, onChatToggle: onChatToggleRequested, onAskAiAssistant: onAskAiAssistant, appPageTitle: pageTitle, children: _jsx("div", { ref: appContainerRef, className: e("app-container") }) }));
|
|
31
|
+
return (_jsx(HostErrorBoundary, { onError: onError, locale: resolveLocale(ctx.preferredLocale), children: _jsx(HostChrome, { ctx: ctx, resolvedApplications: apps, pathname: pathname, onNavigate: navigate, onReplace: replace, headerOptions: headerOptions, notification: notification, showChatItem: chatState.showChatItem, chatIsOpen: chatState.isOpen, onChatToggle: onChatToggleRequested, onAskAiAssistant: onAskAiAssistant, appPageTitle: pageTitle, children: _jsx("div", { ref: appContainerRef, className: e("app-container") }) }) }));
|
|
30
32
|
}
|
|
31
33
|
// ---------------------------------------------------------------------------
|
|
32
34
|
// Imperative mount function conforming to IHostUiModule
|
|
33
35
|
// ---------------------------------------------------------------------------
|
|
34
36
|
function mountDefaultHostUi(options) {
|
|
35
|
-
const { container, ctx, resolvedApplications, pathname, navigate, replace, onChatToggleRequested, onAskAiAssistant, } = options;
|
|
37
|
+
const { container, ctx, resolvedApplications, pathname, navigate, replace, onChatToggleRequested, onAskAiAssistant, onError, } = options;
|
|
36
38
|
let reactRoot = createRoot(container);
|
|
37
39
|
let appContainer = null;
|
|
38
40
|
let updateCtxFn = null;
|
|
@@ -45,17 +47,17 @@ function mountDefaultHostUi(options) {
|
|
|
45
47
|
// Use flushSync so that the DOM is ready synchronously after mount() returns,
|
|
46
48
|
// making getAppContainer() safe to call immediately.
|
|
47
49
|
flushSync(() => {
|
|
48
|
-
reactRoot?.render(_jsx(HostUiBridge, { initialCtx: ctx, initialApps: resolvedApplications, initialPathname: pathname, navigate: navigate, replace: replace, onChatToggleRequested: onChatToggleRequested, onAskAiAssistant: onAskAiAssistant, onAppContainerReady: (el) => {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
50
|
+
reactRoot?.render(_jsx(HostErrorBoundary, { onError: onError, locale: resolveLocale(ctx.preferredLocale), children: _jsx(HostUiBridge, { initialCtx: ctx, initialApps: resolvedApplications, initialPathname: pathname, navigate: navigate, replace: replace, onChatToggleRequested: onChatToggleRequested, onAskAiAssistant: onAskAiAssistant, onError: onError, onAppContainerReady: (el) => {
|
|
51
|
+
appContainer = el;
|
|
52
|
+
}, onReady: (setCtx, setApps, setPathname, setHeaderOptions, setNotification, setPageTitle, setChatState) => {
|
|
53
|
+
updateCtxFn = setCtx;
|
|
54
|
+
updateAppsFn = setApps;
|
|
55
|
+
updatePathnameFn = setPathname;
|
|
56
|
+
updateHeaderFn = setHeaderOptions;
|
|
57
|
+
updateNotificationFn = setNotification;
|
|
58
|
+
updateDocumentTitleFn = setPageTitle;
|
|
59
|
+
updateChatStateFn = setChatState;
|
|
60
|
+
} }) }));
|
|
59
61
|
});
|
|
60
62
|
return {
|
|
61
63
|
unmount() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gooddata/sdk-ui-pluggable-host",
|
|
3
|
-
"version": "11.57.0-alpha.
|
|
3
|
+
"version": "11.57.0-alpha.3",
|
|
4
4
|
"description": "GoodData SDK runtime for hosting pluggable applications — registry, loader, routing, platform context, default UI chrome",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "GoodData Corporation",
|
|
@@ -29,20 +29,20 @@
|
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@module-federation/runtime": "2.6.0",
|
|
31
31
|
"lodash-es": "^4.17.23",
|
|
32
|
-
"@gooddata/sdk-backend-base": "11.57.0-alpha.
|
|
33
|
-
"@gooddata/sdk-
|
|
34
|
-
"@gooddata/sdk-
|
|
35
|
-
"@gooddata/sdk-
|
|
36
|
-
"@gooddata/sdk-
|
|
37
|
-
"@gooddata/sdk-
|
|
38
|
-
"@gooddata/sdk-
|
|
39
|
-
"@gooddata/sdk-ui-
|
|
40
|
-
"@gooddata/sdk-ui-
|
|
41
|
-
"@gooddata/sdk-ui-
|
|
42
|
-
"@gooddata/sdk-ui-
|
|
43
|
-
"@gooddata/
|
|
44
|
-
"@gooddata/sdk-ui-
|
|
45
|
-
"@gooddata/
|
|
32
|
+
"@gooddata/sdk-backend-base": "11.57.0-alpha.3",
|
|
33
|
+
"@gooddata/sdk-model": "11.57.0-alpha.3",
|
|
34
|
+
"@gooddata/sdk-embedding": "11.57.0-alpha.3",
|
|
35
|
+
"@gooddata/sdk-pluggable-application-model": "11.57.0-alpha.3",
|
|
36
|
+
"@gooddata/sdk-backend-tiger": "11.57.0-alpha.3",
|
|
37
|
+
"@gooddata/sdk-ui": "11.57.0-alpha.3",
|
|
38
|
+
"@gooddata/sdk-backend-spi": "11.57.0-alpha.3",
|
|
39
|
+
"@gooddata/sdk-ui-ext": "11.57.0-alpha.3",
|
|
40
|
+
"@gooddata/sdk-ui-application-header": "11.57.0-alpha.3",
|
|
41
|
+
"@gooddata/sdk-ui-gen-ai": "11.57.0-alpha.3",
|
|
42
|
+
"@gooddata/sdk-ui-theme-provider": "11.57.0-alpha.3",
|
|
43
|
+
"@gooddata/util": "11.57.0-alpha.3",
|
|
44
|
+
"@gooddata/sdk-ui-semantic-search": "11.57.0-alpha.3",
|
|
45
|
+
"@gooddata/sdk-ui-kit": "11.57.0-alpha.3"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@microsoft/api-documenter": "7.30.10",
|
|
@@ -68,21 +68,22 @@
|
|
|
68
68
|
"jiti": "2.6.1",
|
|
69
69
|
"npm-run-all": "4.1.5",
|
|
70
70
|
"oxfmt": "0.66.0",
|
|
71
|
-
"oxlint": "1.
|
|
72
|
-
"oxlint-
|
|
71
|
+
"oxlint": "1.67.0",
|
|
72
|
+
"oxlint-plugin-eslint": "1.82.0",
|
|
73
|
+
"oxlint-tsgolint": "0.22.1",
|
|
73
74
|
"react": "19.1.1",
|
|
74
75
|
"react-dom": "19.1.1",
|
|
75
76
|
"react-intl": "10.1.25",
|
|
76
77
|
"react-router": "7.18.1",
|
|
77
78
|
"rolldown": "1.0.3",
|
|
78
|
-
"sass": "1.
|
|
79
|
+
"sass": "1.104.0",
|
|
79
80
|
"tslib": "2.8.1",
|
|
80
81
|
"typescript": "7.0.2",
|
|
81
82
|
"vite": "8.0.16",
|
|
82
83
|
"vitest": "4.1.8",
|
|
83
84
|
"vitest-dom": "0.1.1",
|
|
84
|
-
"@gooddata/eslint-config": "11.57.0-alpha.
|
|
85
|
-
"@gooddata/oxlint-config": "11.57.0-alpha.
|
|
85
|
+
"@gooddata/eslint-config": "11.57.0-alpha.3",
|
|
86
|
+
"@gooddata/oxlint-config": "11.57.0-alpha.3"
|
|
86
87
|
},
|
|
87
88
|
"peerDependencies": {
|
|
88
89
|
"react": ">=18.3.1",
|