@hitachivantara/app-shell-ui 2.0.0-next.1 → 2.0.0-next.2
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/AppShellProvider/AppShellProvider.js +100 -56
- package/dist/components/AppShellRoutes/AppShellRoutes.js +48 -32
- package/dist/components/layout/Header/Header.js +8 -8
- package/dist/components/layout/Header/HeaderActions/HeaderActions.js +4 -5
- package/dist/components/layout/Header/HeaderActions/InternalActions/AppSwitcherToggle/AppSwitcherToggle.js +3 -3
- package/dist/components/layout/VerticalNavigation/NavigationHeader.js +3 -3
- package/dist/hooks/useConditionsEvaluator.js +87 -0
- package/dist/hooks/useFilteredModel.js +30 -0
- package/dist/hooks/useModelFromConfig.js +42 -0
- package/dist/hooks/useNavigationMenuItems.js +2 -2
- package/dist/pages/ErrorPage/Footer.js +2 -2
- package/dist/pages/Root/Root.js +10 -5
- package/dist/utils/CombinedProviders.js +14 -17
- package/dist/utils/filterModel.js +177 -0
- package/dist/utils/lazyImport.js +40 -0
- package/dist/utils/processConfig.js +158 -0
- package/package.json +12 -12
|
@@ -1,45 +1,31 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { useState, useEffect, useMemo, useContext } from "react";
|
|
3
3
|
import { I18nContext } from "react-i18next";
|
|
4
|
-
import { HvAppShellContext, HvAppShellRuntimeContext, HvAppShellCombinedProvidersContext, CONFIG_TRANSLATIONS_NAMESPACE } from "@hitachivantara/app-shell-shared";
|
|
4
|
+
import { HvAppShellContext, HvAppShellModelContext, HvAppShellRuntimeContext, HvAppShellCombinedProvidersContext, CONFIG_TRANSLATIONS_NAMESPACE } from "@hitachivantara/app-shell-shared";
|
|
5
5
|
import { themes, HvProvider } from "@hitachivantara/uikit-react-core";
|
|
6
|
+
import { useFilteredModel } from "../../hooks/useFilteredModel.js";
|
|
6
7
|
import useLocalStorage from "../../hooks/useLocalStorage.js";
|
|
8
|
+
import { useModelFromConfig } from "../../hooks/useModelFromConfig.js";
|
|
7
9
|
import { addResourceBundles } from "../../i18n/index.js";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
config
|
|
11
|
-
|
|
10
|
+
import CombinedProviders from "../../utils/CombinedProviders.js";
|
|
11
|
+
const AppShellProviderInner = ({
|
|
12
|
+
config,
|
|
13
|
+
model,
|
|
14
|
+
children
|
|
12
15
|
}) => {
|
|
13
|
-
const { i18n } = useContext(I18nContext);
|
|
14
16
|
const { value: storedColorModeValue } = useLocalStorage("COLOR_MODE");
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
if (localConfig || !configUrl) return;
|
|
19
|
-
fetch(new URL(configUrl)).then((result) => result.json()).then((data) => setLoadedConfig(data)).catch((e) => {
|
|
20
|
-
console.error(`Failed to obtain the context from: ${configUrl}`, e);
|
|
21
|
-
setLoadedConfig(void 0);
|
|
22
|
-
setHasError(true);
|
|
23
|
-
});
|
|
24
|
-
}, [localConfig, configUrl]);
|
|
25
|
-
const theConfig = useMemo(
|
|
26
|
-
() => localConfig ?? loadedConfig,
|
|
27
|
-
[localConfig, loadedConfig]
|
|
28
|
-
);
|
|
29
|
-
if (hasError) {
|
|
30
|
-
throw Error("Failed to obtain the configuration");
|
|
31
|
-
}
|
|
32
|
-
if (theConfig?.translations) {
|
|
17
|
+
const { i18n } = useContext(I18nContext);
|
|
18
|
+
const { isPending: isModelPending, model: filteredModel } = useFilteredModel(model);
|
|
19
|
+
if (filteredModel?.translations) {
|
|
33
20
|
addResourceBundles(
|
|
34
21
|
i18n,
|
|
35
|
-
|
|
22
|
+
filteredModel.translations,
|
|
36
23
|
CONFIG_TRANSLATIONS_NAMESPACE
|
|
37
24
|
);
|
|
38
25
|
}
|
|
39
26
|
const [theme, setTheme] = useState();
|
|
40
|
-
const [providers, setProviders] = useState();
|
|
41
27
|
useEffect(() => {
|
|
42
|
-
const theme2 =
|
|
28
|
+
const theme2 = filteredModel?.theming?.theme;
|
|
43
29
|
if (!theme2) return;
|
|
44
30
|
if (themes[theme2]) {
|
|
45
31
|
setTheme(themes[theme2]);
|
|
@@ -53,29 +39,24 @@ const AppShellProvider = ({
|
|
|
53
39
|
}).catch((e) => {
|
|
54
40
|
console.error(`Import of theme bundle ${theme2} failed! ${e}`);
|
|
55
41
|
});
|
|
56
|
-
}, [
|
|
57
|
-
|
|
58
|
-
if (!
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
(loadedProviders) => setProviders(loadedProviders.filter((provider) => !!provider))
|
|
75
|
-
).catch((e) => {
|
|
76
|
-
console.error(`Import of providers failed!`, e);
|
|
77
|
-
});
|
|
78
|
-
}, [theConfig?.providers]);
|
|
42
|
+
}, [filteredModel?.theming?.theme]);
|
|
43
|
+
const providers = useMemo(() => {
|
|
44
|
+
if (!filteredModel?.providers) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const providersComponents = [];
|
|
48
|
+
for (const { bundle, key, config: config2 } of filteredModel.providers) {
|
|
49
|
+
const component = model.preloadedBundles.get(
|
|
50
|
+
bundle
|
|
51
|
+
);
|
|
52
|
+
providersComponents.push({
|
|
53
|
+
key,
|
|
54
|
+
component,
|
|
55
|
+
config: config2
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return providersComponents;
|
|
59
|
+
}, [filteredModel?.providers, model.preloadedBundles]);
|
|
79
60
|
const runtimeContext = useMemo(
|
|
80
61
|
() => ({
|
|
81
62
|
i18n
|
|
@@ -88,17 +69,80 @@ const AppShellProvider = ({
|
|
|
88
69
|
}),
|
|
89
70
|
[providers]
|
|
90
71
|
);
|
|
91
|
-
|
|
72
|
+
const appShellConfigContextValue = useMemo(() => config, [config]);
|
|
73
|
+
const appShellModelContextValue = useMemo(
|
|
74
|
+
() => filteredModel,
|
|
75
|
+
[filteredModel]
|
|
76
|
+
);
|
|
77
|
+
if (isModelPending || !filteredModel || filteredModel.theming?.theme && !theme) {
|
|
92
78
|
return null;
|
|
93
79
|
}
|
|
94
|
-
return /* @__PURE__ */ jsx(HvAppShellContext.Provider, { value:
|
|
80
|
+
return /* @__PURE__ */ jsx(HvAppShellContext.Provider, { value: appShellConfigContextValue, children: /* @__PURE__ */ jsx(HvAppShellModelContext.Provider, { value: appShellModelContextValue, children: /* @__PURE__ */ jsx(HvAppShellRuntimeContext.Provider, { value: runtimeContext, children: /* @__PURE__ */ jsx(
|
|
95
81
|
HvProvider,
|
|
96
82
|
{
|
|
97
83
|
theme,
|
|
98
|
-
colorMode: storedColorModeValue ??
|
|
99
|
-
children: /* @__PURE__ */ jsx(
|
|
84
|
+
colorMode: storedColorModeValue ?? filteredModel.theming?.colorMode,
|
|
85
|
+
children: /* @__PURE__ */ jsx(
|
|
86
|
+
HvAppShellCombinedProvidersContext.Provider,
|
|
87
|
+
{
|
|
88
|
+
value: providersContext,
|
|
89
|
+
children
|
|
90
|
+
}
|
|
91
|
+
)
|
|
100
92
|
}
|
|
101
|
-
) }) });
|
|
93
|
+
) }) }) });
|
|
94
|
+
};
|
|
95
|
+
const AppShellProvider = ({
|
|
96
|
+
children,
|
|
97
|
+
config: localConfig,
|
|
98
|
+
configUrl
|
|
99
|
+
}) => {
|
|
100
|
+
const [loadedConfig, setLoadedConfig] = useState(void 0);
|
|
101
|
+
const [hasError, setHasError] = useState(false);
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
if (!localConfig && configUrl) {
|
|
104
|
+
fetch(new URL(configUrl)).then((result) => result.json()).then((data) => setLoadedConfig(data)).catch((e) => {
|
|
105
|
+
console.error(`Failed to obtain the context from: ${configUrl}`, e);
|
|
106
|
+
setLoadedConfig(void 0);
|
|
107
|
+
setHasError(true);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}, [localConfig, configUrl]);
|
|
111
|
+
const rawConfig = useMemo(
|
|
112
|
+
() => localConfig ?? loadedConfig,
|
|
113
|
+
[localConfig, loadedConfig]
|
|
114
|
+
);
|
|
115
|
+
const [initialConfig, setInitialConfig] = useState(void 0);
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
if (rawConfig && !initialConfig) {
|
|
118
|
+
setInitialConfig(rawConfig);
|
|
119
|
+
}
|
|
120
|
+
}, [rawConfig, initialConfig]);
|
|
121
|
+
const { model, isPending: areBundlesLoading } = useModelFromConfig(initialConfig);
|
|
122
|
+
const systemProviders = useMemo(() => {
|
|
123
|
+
if (!model?.systemProviders) {
|
|
124
|
+
return void 0;
|
|
125
|
+
}
|
|
126
|
+
const providersComponents = [];
|
|
127
|
+
for (const provider of model.systemProviders) {
|
|
128
|
+
const component = model.preloadedBundles.get(
|
|
129
|
+
provider.bundle
|
|
130
|
+
);
|
|
131
|
+
providersComponents.push({
|
|
132
|
+
key: provider.key,
|
|
133
|
+
component,
|
|
134
|
+
config: provider.config
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
return providersComponents;
|
|
138
|
+
}, [model?.systemProviders, model?.preloadedBundles]);
|
|
139
|
+
if (hasError) {
|
|
140
|
+
throw Error("Failed to obtain the configuration");
|
|
141
|
+
}
|
|
142
|
+
if (!rawConfig || !model || areBundlesLoading) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
return /* @__PURE__ */ jsx(CombinedProviders, { providers: systemProviders, children: /* @__PURE__ */ jsx(AppShellProviderInner, { config: rawConfig, model, children }) });
|
|
102
146
|
};
|
|
103
147
|
export {
|
|
104
148
|
AppShellProvider as default
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
|
-
import { lazy } from "react";
|
|
2
|
+
import { useRef, useState, useMemo, useEffect, lazy } from "react";
|
|
3
3
|
import { ErrorBoundary } from "react-error-boundary";
|
|
4
|
-
import { RouterProvider, createBrowserRouter, Outlet } from "react-router-dom";
|
|
5
|
-
import {
|
|
4
|
+
import { matchRoutes, RouterProvider, createBrowserRouter, Outlet } from "react-router-dom";
|
|
5
|
+
import { useHvAppShellModel } from "@hitachivantara/app-shell-shared";
|
|
6
6
|
import { HvContainer } from "@hitachivantara/uikit-react-core";
|
|
7
7
|
import LoadingPage from "../../pages/LoadingPage/LoadingPage.js";
|
|
8
8
|
import { getAppIdFromBundle } from "../../utils/navigationUtil.js";
|
|
@@ -15,13 +15,13 @@ function renderNestedRoutes(views) {
|
|
|
15
15
|
return void 0;
|
|
16
16
|
}
|
|
17
17
|
return views.map((view) => {
|
|
18
|
-
const { bundle } = view;
|
|
18
|
+
const { bundle, route } = view;
|
|
19
19
|
const appId = getAppIdFromBundle(bundle);
|
|
20
20
|
const RouteComponent = lazy(() => import(
|
|
21
21
|
/* @vite-ignore */
|
|
22
22
|
bundle
|
|
23
23
|
));
|
|
24
|
-
const path =
|
|
24
|
+
const path = route.replace(/^\//, "");
|
|
25
25
|
return {
|
|
26
26
|
path,
|
|
27
27
|
// "Component" used instead of "element" due to lazy loading
|
|
@@ -29,16 +29,16 @@ function renderNestedRoutes(views) {
|
|
|
29
29
|
ErrorBoundary,
|
|
30
30
|
{
|
|
31
31
|
fallback: /* @__PURE__ */ jsx(GenericError, { fullPage: false }),
|
|
32
|
-
children: /* @__PURE__ */ jsx(RouteComponent, { ...view.config, children: view.views
|
|
32
|
+
children: /* @__PURE__ */ jsx(RouteComponent, { ...view.config, children: view.views ? /* @__PURE__ */ jsx(Outlet, {}) : null })
|
|
33
33
|
},
|
|
34
|
-
view.
|
|
34
|
+
view.key
|
|
35
35
|
) }),
|
|
36
36
|
children: renderNestedRoutes(view.views)
|
|
37
37
|
};
|
|
38
38
|
});
|
|
39
39
|
}
|
|
40
40
|
function renderRoutes(mainPanel) {
|
|
41
|
-
if (mainPanel
|
|
41
|
+
if (mainPanel?.views == null) {
|
|
42
42
|
return [];
|
|
43
43
|
}
|
|
44
44
|
const { views, maxWidth = "xl", ...mainContainerProps } = mainPanel;
|
|
@@ -49,6 +49,8 @@ function renderRoutes(mainPanel) {
|
|
|
49
49
|
config,
|
|
50
50
|
views: nestedViews,
|
|
51
51
|
maxWidth: viewMaxWidth,
|
|
52
|
+
key,
|
|
53
|
+
conditions,
|
|
52
54
|
...viewContainerProps
|
|
53
55
|
} = view;
|
|
54
56
|
const appId = getAppIdFromBundle(bundle);
|
|
@@ -56,25 +58,22 @@ function renderRoutes(mainPanel) {
|
|
|
56
58
|
/* @vite-ignore */
|
|
57
59
|
bundle
|
|
58
60
|
));
|
|
61
|
+
const containerProps = {
|
|
62
|
+
maxWidth: viewMaxWidth ?? maxWidth,
|
|
63
|
+
...mainContainerProps,
|
|
64
|
+
...viewContainerProps
|
|
65
|
+
};
|
|
59
66
|
return {
|
|
60
67
|
path: route,
|
|
61
68
|
// "Component" used instead of "element" due to lazy loading
|
|
62
|
-
Component: () => /* @__PURE__ */ jsx(
|
|
63
|
-
|
|
69
|
+
Component: () => /* @__PURE__ */ jsx(HvContainer, { ...containerProps, children: /* @__PURE__ */ jsx(AppShellViewProvider, { id: appId, children: /* @__PURE__ */ jsx(
|
|
70
|
+
ErrorBoundary,
|
|
64
71
|
{
|
|
65
|
-
|
|
66
|
-
...
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
{
|
|
71
|
-
fallback: /* @__PURE__ */ jsx(GenericError, { fullPage: false }),
|
|
72
|
-
children: /* @__PURE__ */ jsx(RouteComponent, { ...config, children: nestedViews != null ? /* @__PURE__ */ jsx(Outlet, {}) : null })
|
|
73
|
-
},
|
|
74
|
-
route
|
|
75
|
-
) })
|
|
76
|
-
}
|
|
77
|
-
),
|
|
72
|
+
fallback: /* @__PURE__ */ jsx(GenericError, { fullPage: false }),
|
|
73
|
+
children: /* @__PURE__ */ jsx(RouteComponent, { ...config, children: nestedViews ? /* @__PURE__ */ jsx(Outlet, {}) : null })
|
|
74
|
+
},
|
|
75
|
+
view.key
|
|
76
|
+
) }) }),
|
|
78
77
|
children: renderNestedRoutes(nestedViews)
|
|
79
78
|
};
|
|
80
79
|
});
|
|
@@ -89,8 +88,28 @@ function renderErrorRoutes(mainPanel) {
|
|
|
89
88
|
];
|
|
90
89
|
}
|
|
91
90
|
const AppShellRoutes = () => {
|
|
92
|
-
const { baseUrl, mainPanel
|
|
93
|
-
const
|
|
91
|
+
const { baseUrl, mainPanel } = useHvAppShellModel();
|
|
92
|
+
const prevRoutesRef = useRef([]);
|
|
93
|
+
const [routerKey, setRouterKey] = useState("router-initial");
|
|
94
|
+
const childRoutes = useMemo(
|
|
95
|
+
() => [...renderRoutes(mainPanel), ...renderErrorRoutes(mainPanel)],
|
|
96
|
+
[mainPanel]
|
|
97
|
+
);
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
if (prevRoutesRef.current.length === 0) {
|
|
100
|
+
prevRoutesRef.current = childRoutes;
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const currentPath = globalThis.location.pathname;
|
|
104
|
+
const prevMatch = matchRoutes(prevRoutesRef.current, currentPath);
|
|
105
|
+
const newMatch = matchRoutes(childRoutes, currentPath);
|
|
106
|
+
const prevWas404 = !prevMatch || prevMatch.at(-1)?.route.path === "*";
|
|
107
|
+
const newIs404 = !newMatch || newMatch.at(-1)?.route.path === "*";
|
|
108
|
+
if (prevWas404 && !newIs404 || !prevWas404 && newIs404) {
|
|
109
|
+
setRouterKey(`router-${newIs404 ? "404" : "valid"}`);
|
|
110
|
+
}
|
|
111
|
+
prevRoutesRef.current = childRoutes;
|
|
112
|
+
}, [baseUrl, childRoutes]);
|
|
94
113
|
return /* @__PURE__ */ jsx(
|
|
95
114
|
RouterProvider,
|
|
96
115
|
{
|
|
@@ -98,18 +117,15 @@ const AppShellRoutes = () => {
|
|
|
98
117
|
router: createBrowserRouter(
|
|
99
118
|
[
|
|
100
119
|
{
|
|
101
|
-
element: /* @__PURE__ */ jsx(Root, {
|
|
102
|
-
// All routes live inside `RootRoute`
|
|
120
|
+
element: /* @__PURE__ */ jsx(Root, {}),
|
|
103
121
|
errorElement: /* @__PURE__ */ jsx(GenericError, { fullPage: true }),
|
|
104
|
-
children:
|
|
105
|
-
...renderRoutes(mainPanel),
|
|
106
|
-
...renderErrorRoutes(mainPanel)
|
|
107
|
-
]
|
|
122
|
+
children: childRoutes
|
|
108
123
|
}
|
|
109
124
|
],
|
|
110
125
|
{ basename: baseUrl ?? "/" }
|
|
111
126
|
)
|
|
112
|
-
}
|
|
127
|
+
},
|
|
128
|
+
routerKey
|
|
113
129
|
);
|
|
114
130
|
};
|
|
115
131
|
export {
|
|
@@ -3,7 +3,7 @@ import { Helmet } from "react-helmet-async";
|
|
|
3
3
|
import { useTranslation } from "react-i18next";
|
|
4
4
|
import { css } from "@emotion/css";
|
|
5
5
|
import { useHvNavigation } from "@hitachivantara/app-shell-navigation";
|
|
6
|
-
import { CONFIG_TRANSLATIONS_NAMESPACE,
|
|
6
|
+
import { CONFIG_TRANSLATIONS_NAMESPACE, useHvAppShellModel } from "@hitachivantara/app-shell-shared";
|
|
7
7
|
import { useTheme, HvHeader, HvButton, HvHeaderBrand, HvHeaderNavigation } from "@hitachivantara/uikit-react-core";
|
|
8
8
|
import { useLayoutContext } from "../../../providers/LayoutProvider.js";
|
|
9
9
|
import { useNavigationContext } from "../../../providers/NavigationProvider.js";
|
|
@@ -14,7 +14,7 @@ import BrandLogo from "../BrandLogo/BrandLogo.js";
|
|
|
14
14
|
const Header = () => {
|
|
15
15
|
const { t } = useTranslation(void 0, { keyPrefix: "header.navigation" });
|
|
16
16
|
const { t: tConfig } = useTranslation(CONFIG_TRANSLATIONS_NAMESPACE);
|
|
17
|
-
const
|
|
17
|
+
const { navigationMode, name, logo } = useHvAppShellModel();
|
|
18
18
|
const { verticalNavigationWidth } = useLayoutContext();
|
|
19
19
|
const { activeTheme } = useTheme();
|
|
20
20
|
const { navigate } = useHvNavigation();
|
|
@@ -27,8 +27,8 @@ const Header = () => {
|
|
|
27
27
|
verticalNavigationMode,
|
|
28
28
|
verticalNavigationItems
|
|
29
29
|
} = useNavigationContext();
|
|
30
|
-
const isOnlyTopMode =
|
|
31
|
-
const showNavigation = !isCompactMode &&
|
|
30
|
+
const isOnlyTopMode = navigationMode === "ONLY_TOP";
|
|
31
|
+
const showNavigation = !isCompactMode && navigationMode !== "ONLY_LEFT" && items.length > 0;
|
|
32
32
|
const isVerticalNavigationClosed = verticalNavigationMode === "CLOSED";
|
|
33
33
|
const showVerticalNavigationButton = isCompactMode && verticalNavigationItems.length > 0;
|
|
34
34
|
const handleNavigationChange = (event, selectedItem) => {
|
|
@@ -40,7 +40,7 @@ const Header = () => {
|
|
|
40
40
|
};
|
|
41
41
|
const isPentahoTheme = activeTheme?.name === "pentahoPlus";
|
|
42
42
|
const shouldShrinkHeader = isPentahoTheme && verticalNavigationWidth > 0;
|
|
43
|
-
const
|
|
43
|
+
const appName = name ? tConfig(name) : "";
|
|
44
44
|
return /* @__PURE__ */ jsxs(
|
|
45
45
|
HvHeader,
|
|
46
46
|
{
|
|
@@ -49,7 +49,7 @@ const Header = () => {
|
|
|
49
49
|
width: shouldShrinkHeader ? `calc(100% - ${verticalNavigationWidth}px)` : void 0
|
|
50
50
|
}),
|
|
51
51
|
children: [
|
|
52
|
-
/* @__PURE__ */ jsx(Helmet, { children: /* @__PURE__ */ jsx("title", { children:
|
|
52
|
+
/* @__PURE__ */ jsx(Helmet, { children: /* @__PURE__ */ jsx("title", { children: appName }) }),
|
|
53
53
|
showVerticalNavigationButton && /* @__PURE__ */ jsx(
|
|
54
54
|
HvButton,
|
|
55
55
|
{
|
|
@@ -64,9 +64,9 @@ const Header = () => {
|
|
|
64
64
|
HvHeaderBrand,
|
|
65
65
|
{
|
|
66
66
|
...!isPentahoTheme && {
|
|
67
|
-
logo: /* @__PURE__ */ jsx(StyledIconWrapper, { children: /* @__PURE__ */ jsx(BrandLogo, { logo
|
|
67
|
+
logo: /* @__PURE__ */ jsx(StyledIconWrapper, { children: /* @__PURE__ */ jsx(BrandLogo, { logo }) })
|
|
68
68
|
},
|
|
69
|
-
name
|
|
69
|
+
name: appName
|
|
70
70
|
}
|
|
71
71
|
),
|
|
72
72
|
showNavigation && /* @__PURE__ */ jsx(
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { useHvAppShellModel } from "@hitachivantara/app-shell-shared";
|
|
3
3
|
import { HvHeaderActions } from "@hitachivantara/uikit-react-core";
|
|
4
4
|
import DynamicAction from "./DynamicAction.js";
|
|
5
5
|
import InternalAction, { internalActions } from "./InternalActions/InternalAction/InternalAction.js";
|
|
6
6
|
const HeaderActions = () => {
|
|
7
|
-
const { header } =
|
|
8
|
-
return /* @__PURE__ */ jsx(HvHeaderActions, { children: header?.actions.map((action
|
|
9
|
-
const headerActionKey = `${action.bundle}${index}`;
|
|
7
|
+
const { header } = useHvAppShellModel();
|
|
8
|
+
return /* @__PURE__ */ jsx(HvHeaderActions, { children: header?.actions.map((action) => {
|
|
10
9
|
const Component = internalActions.some(
|
|
11
10
|
(a) => a.bundle === action.bundle
|
|
12
11
|
) ? InternalAction : DynamicAction;
|
|
@@ -16,7 +15,7 @@ const HeaderActions = () => {
|
|
|
16
15
|
bundle: action.bundle,
|
|
17
16
|
...action.config
|
|
18
17
|
},
|
|
19
|
-
|
|
18
|
+
action.key
|
|
20
19
|
);
|
|
21
20
|
}) });
|
|
22
21
|
};
|
|
@@ -3,7 +3,7 @@ import { useState, useId } from "react";
|
|
|
3
3
|
import { createPortal } from "react-dom";
|
|
4
4
|
import { useTranslation } from "react-i18next";
|
|
5
5
|
import ClickAwayListener from "@mui/material/ClickAwayListener";
|
|
6
|
-
import { CONFIG_TRANSLATIONS_NAMESPACE,
|
|
6
|
+
import { CONFIG_TRANSLATIONS_NAMESPACE, useHvAppShellModel } from "@hitachivantara/app-shell-shared";
|
|
7
7
|
import { HvIconButton, theme, HvAppSwitcher, HvTypography } from "@hitachivantara/uikit-react-core";
|
|
8
8
|
import createAppContainerElement from "../../../../../../utils/documentUtil.js";
|
|
9
9
|
import IconUiKit from "../../../../../IconUiKit/index.js";
|
|
@@ -19,7 +19,7 @@ const AppSwitcherToggle = ({
|
|
|
19
19
|
const { t: tConfig } = useTranslation(CONFIG_TRANSLATIONS_NAMESPACE);
|
|
20
20
|
const [isPanelOpen, setIsPanelOpen] = useState(false);
|
|
21
21
|
const appSwitcherPanelId = useId();
|
|
22
|
-
const
|
|
22
|
+
const { logo } = useHvAppShellModel();
|
|
23
23
|
const createAppsList = () => {
|
|
24
24
|
return apps ? apps.map((app) => ({
|
|
25
25
|
name: tConfig(app.label),
|
|
@@ -53,7 +53,7 @@ const AppSwitcherToggle = ({
|
|
|
53
53
|
...isPanelOpen && { "aria-controls": appSwitcherPanelId },
|
|
54
54
|
children: [
|
|
55
55
|
/* @__PURE__ */ jsx(IconUiKit, { name: "AppSwitcher" }),
|
|
56
|
-
showLogo && /* @__PURE__ */ jsx(StyledIconWrapper, { style: { paddingRight: theme.space.xs }, children: /* @__PURE__ */ jsx(BrandLogo, { logo
|
|
56
|
+
showLogo && /* @__PURE__ */ jsx(StyledIconWrapper, { style: { paddingRight: theme.space.xs }, children: /* @__PURE__ */ jsx(BrandLogo, { logo }) })
|
|
57
57
|
]
|
|
58
58
|
}
|
|
59
59
|
),
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { useHvAppShellModel } from "@hitachivantara/app-shell-shared";
|
|
3
3
|
import { AppSwitcher } from "@hitachivantara/uikit-react-icons";
|
|
4
4
|
import { classes } from "./styles.js";
|
|
5
5
|
import BrandLogo from "../BrandLogo/BrandLogo.js";
|
|
6
6
|
const NavigationHeader = ({ isOpen }) => {
|
|
7
|
-
const
|
|
7
|
+
const { logo } = useHvAppShellModel();
|
|
8
8
|
return /* @__PURE__ */ jsxs("div", { className: classes.navigationHeader, children: [
|
|
9
9
|
/* @__PURE__ */ jsx(AppSwitcher, {}),
|
|
10
|
-
isOpen && /* @__PURE__ */ jsx(BrandLogo, { logo
|
|
10
|
+
isOpen && /* @__PURE__ */ jsx(BrandLogo, { logo })
|
|
11
11
|
] });
|
|
12
12
|
};
|
|
13
13
|
export {
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { useRef } from "react";
|
|
2
|
+
function assert(condition, message) {
|
|
3
|
+
if (!condition) {
|
|
4
|
+
throw new Error(message || "Assertion failed");
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
function areArraysEqual(a, b) {
|
|
8
|
+
if (a.length !== b.length) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
for (let i = 0; i < a.length; i++) {
|
|
12
|
+
if (a[i] !== b[i]) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
const useConditionsEvaluator = (allConditions, preloadedBundles) => {
|
|
19
|
+
const previousResultsRef = useRef([]);
|
|
20
|
+
if (preloadedBundles.size === 0) {
|
|
21
|
+
return {
|
|
22
|
+
isPending: false,
|
|
23
|
+
error: null,
|
|
24
|
+
result: previousResultsRef.current
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const hookResults = [];
|
|
28
|
+
for (const { bundle, config } of allConditions) {
|
|
29
|
+
const module = preloadedBundles.get(bundle);
|
|
30
|
+
if (!module) {
|
|
31
|
+
hookResults.push({
|
|
32
|
+
isPending: false,
|
|
33
|
+
error: new Error(`Bundle '${bundle}' not loaded`),
|
|
34
|
+
result: void 0
|
|
35
|
+
});
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const result2 = module(config);
|
|
40
|
+
hookResults.push(result2);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
hookResults.push({
|
|
43
|
+
isPending: false,
|
|
44
|
+
error,
|
|
45
|
+
result: void 0
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const isAnyPending = hookResults.some((result2) => result2.isPending);
|
|
50
|
+
const currentResults = [];
|
|
51
|
+
for (let i = 0; i < allConditions.length; i++) {
|
|
52
|
+
const condition = allConditions[i];
|
|
53
|
+
const asyncResult = hookResults[i];
|
|
54
|
+
if (asyncResult.error) {
|
|
55
|
+
console.error(
|
|
56
|
+
`Failed to execute ${condition.bundle} with: ${asyncResult.error}`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
const boolResult = asyncResult.isPending || asyncResult.error ? false : asyncResult.result;
|
|
60
|
+
assert(
|
|
61
|
+
i === condition.globalIndex,
|
|
62
|
+
`Index mismatch: expected ${i} to equal globalIndex ${condition.globalIndex}`
|
|
63
|
+
);
|
|
64
|
+
currentResults[condition.globalIndex] = boolResult;
|
|
65
|
+
}
|
|
66
|
+
const previousResults = previousResultsRef.current;
|
|
67
|
+
const hasChanged = !areArraysEqual(previousResults, currentResults);
|
|
68
|
+
const result = hasChanged ? currentResults : previousResults;
|
|
69
|
+
if (hasChanged) {
|
|
70
|
+
previousResultsRef.current = currentResults;
|
|
71
|
+
}
|
|
72
|
+
if (isAnyPending) {
|
|
73
|
+
return {
|
|
74
|
+
isPending: true,
|
|
75
|
+
error: null,
|
|
76
|
+
result: void 0
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
error: null,
|
|
81
|
+
result,
|
|
82
|
+
isPending: false
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
export {
|
|
86
|
+
useConditionsEvaluator
|
|
87
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import filterModel from "../utils/filterModel.js";
|
|
3
|
+
import { useConditionsEvaluator } from "./useConditionsEvaluator.js";
|
|
4
|
+
const useFilteredModel = (model) => {
|
|
5
|
+
const { isPending: isModelPending, result } = useConditionsEvaluator(
|
|
6
|
+
model.allConditions,
|
|
7
|
+
model.preloadedBundles
|
|
8
|
+
);
|
|
9
|
+
const resolvedModel = useMemo(() => {
|
|
10
|
+
if (isModelPending) {
|
|
11
|
+
return void 0;
|
|
12
|
+
}
|
|
13
|
+
return filterModel(model, result);
|
|
14
|
+
}, [isModelPending, model, result]);
|
|
15
|
+
if (isModelPending) {
|
|
16
|
+
return {
|
|
17
|
+
error: null,
|
|
18
|
+
isPending: true,
|
|
19
|
+
model: void 0
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
error: null,
|
|
24
|
+
isPending: false,
|
|
25
|
+
model: resolvedModel
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
export {
|
|
29
|
+
useFilteredModel
|
|
30
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { useMemo, useCallback } from "react";
|
|
2
|
+
import { useAsync } from "@hitachivantara/app-shell-shared";
|
|
3
|
+
import { importAllBundles } from "../utils/lazyImport.js";
|
|
4
|
+
import processConfig from "../utils/processConfig.js";
|
|
5
|
+
const useModelFromConfig = (config) => {
|
|
6
|
+
const initialModel = useMemo(
|
|
7
|
+
() => config ? processConfig(config) : void 0,
|
|
8
|
+
[config]
|
|
9
|
+
);
|
|
10
|
+
const systemProvidersBundles = useMemo(
|
|
11
|
+
() => (initialModel?.systemProviders ?? []).map((p) => p.bundle),
|
|
12
|
+
[initialModel]
|
|
13
|
+
);
|
|
14
|
+
const conditionBundles = useMemo(
|
|
15
|
+
() => (initialModel?.allConditions ?? []).map((c) => c.bundle),
|
|
16
|
+
[initialModel]
|
|
17
|
+
);
|
|
18
|
+
const providerBundles = useMemo(
|
|
19
|
+
() => (initialModel?.providers ?? []).map((p) => p.bundle),
|
|
20
|
+
[initialModel]
|
|
21
|
+
);
|
|
22
|
+
const bundles = useMemo(
|
|
23
|
+
() => [...systemProvidersBundles, ...conditionBundles, ...providerBundles],
|
|
24
|
+
[systemProvidersBundles, conditionBundles, providerBundles]
|
|
25
|
+
);
|
|
26
|
+
const promiseFactory = useCallback(async () => {
|
|
27
|
+
if (!initialModel) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (bundles.length === 0) {
|
|
31
|
+
return {
|
|
32
|
+
...initialModel
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const preloadedBundles = await importAllBundles(bundles);
|
|
36
|
+
return { ...initialModel, preloadedBundles };
|
|
37
|
+
}, [initialModel, bundles]);
|
|
38
|
+
return useAsync(promiseFactory, { dataProp: "model" });
|
|
39
|
+
};
|
|
40
|
+
export {
|
|
41
|
+
useModelFromConfig
|
|
42
|
+
};
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { useContext, useMemo, useEffect } from "react";
|
|
2
2
|
import { useLocation } from "react-router-dom";
|
|
3
3
|
import { useHvNavigation } from "@hitachivantara/app-shell-navigation";
|
|
4
|
-
import {
|
|
4
|
+
import { useHvAppShellModel, HvAppShellRuntimeContext, CONFIG_TRANSLATIONS_NAMESPACE, useHvMenuItems } from "@hitachivantara/app-shell-shared";
|
|
5
5
|
import { createNavigationMenuItems } from "../utils/navigationUtil.js";
|
|
6
6
|
const MAX_TOP_MENU_DEPTH = 2;
|
|
7
7
|
const useNavigationMenuItems = () => {
|
|
8
8
|
const { pathname } = useLocation();
|
|
9
|
-
const { navigationMode } =
|
|
9
|
+
const { navigationMode } = useHvAppShellModel();
|
|
10
10
|
const { navigate } = useHvNavigation();
|
|
11
11
|
const { i18n } = useContext(HvAppShellRuntimeContext) ?? {};
|
|
12
12
|
const tConfig = useMemo(
|
|
@@ -3,7 +3,7 @@ import { useErrorBoundary } from "react-error-boundary";
|
|
|
3
3
|
import { useTranslation, Trans } from "react-i18next";
|
|
4
4
|
import styled from "@emotion/styled";
|
|
5
5
|
import { useHvNavigation } from "@hitachivantara/app-shell-navigation";
|
|
6
|
-
import {
|
|
6
|
+
import { useHvAppShellModel } from "@hitachivantara/app-shell-shared";
|
|
7
7
|
import { HvTypography } from "@hitachivantara/uikit-react-core";
|
|
8
8
|
import { useNavigationContext } from "../../providers/NavigationProvider.js";
|
|
9
9
|
const StyledFooterWrapper = styled("div")({
|
|
@@ -12,7 +12,7 @@ const StyledFooterWrapper = styled("div")({
|
|
|
12
12
|
const Footer = () => {
|
|
13
13
|
const { t } = useTranslation();
|
|
14
14
|
const { navigate } = useHvNavigation();
|
|
15
|
-
const { navigationMode } =
|
|
15
|
+
const { navigationMode } = useHvAppShellModel();
|
|
16
16
|
const { resetBoundary } = useErrorBoundary();
|
|
17
17
|
const { items, verticalNavigationItems, isCompactMode } = useNavigationContext();
|
|
18
18
|
const getFirstMenuItem = () => {
|
package/dist/pages/Root/Root.js
CHANGED
|
@@ -3,6 +3,7 @@ import { Suspense } from "react";
|
|
|
3
3
|
import { ErrorBoundary } from "react-error-boundary";
|
|
4
4
|
import { Outlet } from "react-router-dom";
|
|
5
5
|
import ServiceManagerProvider from "@hitachivantara/app-shell-services";
|
|
6
|
+
import { useHvAppShellModel, useHvAppShellCombinedProviders } from "@hitachivantara/app-shell-shared";
|
|
6
7
|
import { BannerProvider } from "../../providers/BannerProvider.js";
|
|
7
8
|
import { NavigationProvider } from "../../providers/NavigationProvider.js";
|
|
8
9
|
import CombinedProviders from "../../utils/CombinedProviders.js";
|
|
@@ -11,11 +12,15 @@ import CustomHooksInitializer from "../../components/CustomHooksInitializer/Cust
|
|
|
11
12
|
import Header from "../../components/layout/Header/Header.js";
|
|
12
13
|
import Main from "../../components/layout/Main/Main.js";
|
|
13
14
|
import GenericError from "../GenericError/GenericError.js";
|
|
14
|
-
const Root = (
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
/* @__PURE__ */ jsx(
|
|
18
|
-
|
|
15
|
+
const Root = () => {
|
|
16
|
+
const { services } = useHvAppShellModel();
|
|
17
|
+
const { providers } = useHvAppShellCombinedProviders();
|
|
18
|
+
return /* @__PURE__ */ jsx(ErrorBoundary, { fallback: /* @__PURE__ */ jsx(GenericError, { fullPage: true }), children: /* @__PURE__ */ jsx(ServiceManagerProvider, { config: { services }, children: /* @__PURE__ */ jsx(CombinedProviders, { providers, children: /* @__PURE__ */ jsx(NavigationProvider, { children: /* @__PURE__ */ jsxs(BannerProvider, { children: [
|
|
19
|
+
/* @__PURE__ */ jsx(CustomHooksInitializer, {}),
|
|
20
|
+
/* @__PURE__ */ jsx(Header, {}),
|
|
21
|
+
/* @__PURE__ */ jsx(Main, { children: /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(LoadingPage, {}), children: /* @__PURE__ */ jsx(Outlet, {}) }) })
|
|
22
|
+
] }) }) }) }) });
|
|
23
|
+
};
|
|
19
24
|
export {
|
|
20
25
|
Root as default
|
|
21
26
|
};
|
|
@@ -1,22 +1,19 @@
|
|
|
1
|
-
import { jsx } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
1
|
+
import { jsx, Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo } from "react";
|
|
3
3
|
const CombinedProviders = ({
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
children: mainChildren,
|
|
5
|
+
providers
|
|
6
6
|
}) => {
|
|
7
|
-
const
|
|
8
|
-
(
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
[providers]
|
|
18
|
-
);
|
|
19
|
-
return /* @__PURE__ */ jsx(Combined, { children: mainChildren });
|
|
7
|
+
const combined = useMemo(() => {
|
|
8
|
+
if (!providers || providers.length === 0) {
|
|
9
|
+
return mainChildren;
|
|
10
|
+
}
|
|
11
|
+
return providers.reduceRight(
|
|
12
|
+
(Acc, { component: Curr, config, key }) => /* @__PURE__ */ jsx(Curr, { ...config, children: Acc }, key),
|
|
13
|
+
mainChildren
|
|
14
|
+
);
|
|
15
|
+
}, [providers, mainChildren]);
|
|
16
|
+
return /* @__PURE__ */ jsx(Fragment, { children: combined });
|
|
20
17
|
};
|
|
21
18
|
export {
|
|
22
19
|
CombinedProviders as default
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
const shouldInclude = (conditions, conditionResults) => {
|
|
2
|
+
if (!conditions || conditions.length === 0) {
|
|
3
|
+
return true;
|
|
4
|
+
}
|
|
5
|
+
for (const condition of conditions) {
|
|
6
|
+
if (!conditionResults[condition.globalIndex]) {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
return true;
|
|
11
|
+
};
|
|
12
|
+
const filterViews = (views, conditionResults) => {
|
|
13
|
+
const filteredViews = [];
|
|
14
|
+
let hasChanged = false;
|
|
15
|
+
for (const view of views) {
|
|
16
|
+
const include = shouldInclude(view.conditions, conditionResults);
|
|
17
|
+
if (!include) {
|
|
18
|
+
hasChanged = true;
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
if (!view.views) {
|
|
22
|
+
filteredViews.push(view);
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const [filteredSubViews, subViewsChanged] = filterViews(
|
|
26
|
+
view.views,
|
|
27
|
+
conditionResults
|
|
28
|
+
);
|
|
29
|
+
if (subViewsChanged) {
|
|
30
|
+
filteredViews.push({ ...view, views: filteredSubViews });
|
|
31
|
+
hasChanged = true;
|
|
32
|
+
} else {
|
|
33
|
+
filteredViews.push(view);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return [hasChanged ? filteredViews : views, hasChanged];
|
|
37
|
+
};
|
|
38
|
+
const filterMenus = (menus, conditionResults) => {
|
|
39
|
+
const filteredMenus = [];
|
|
40
|
+
let hasChanged = false;
|
|
41
|
+
for (const menu of menus) {
|
|
42
|
+
const include = shouldInclude(menu.conditions, conditionResults);
|
|
43
|
+
if (!include) {
|
|
44
|
+
hasChanged = true;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (!menu.submenus) {
|
|
48
|
+
filteredMenus.push(menu);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const [filteredSubmenus, submenusChanged] = filterMenus(
|
|
52
|
+
menu.submenus,
|
|
53
|
+
conditionResults
|
|
54
|
+
);
|
|
55
|
+
if (filteredSubmenus.length === 0) {
|
|
56
|
+
hasChanged = true;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (submenusChanged) {
|
|
60
|
+
filteredMenus.push({
|
|
61
|
+
...menu,
|
|
62
|
+
submenus: filteredSubmenus
|
|
63
|
+
});
|
|
64
|
+
hasChanged = true;
|
|
65
|
+
} else {
|
|
66
|
+
filteredMenus.push(menu);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return [hasChanged ? filteredMenus : menus, hasChanged];
|
|
70
|
+
};
|
|
71
|
+
const filterActions = (actions, conditionResults) => {
|
|
72
|
+
const filteredHeaderActions = [];
|
|
73
|
+
for (const action of actions) {
|
|
74
|
+
const include = shouldInclude(action.conditions, conditionResults);
|
|
75
|
+
if (include) filteredHeaderActions.push(action);
|
|
76
|
+
}
|
|
77
|
+
const hasChanged = filteredHeaderActions.length !== actions.length;
|
|
78
|
+
return [hasChanged ? filteredHeaderActions : actions, hasChanged];
|
|
79
|
+
};
|
|
80
|
+
const filterProviders = (providers, conditionResults) => {
|
|
81
|
+
const filteredProviders = [];
|
|
82
|
+
for (const provider of providers) {
|
|
83
|
+
const include = shouldInclude(provider.conditions, conditionResults);
|
|
84
|
+
if (include) filteredProviders.push(provider);
|
|
85
|
+
}
|
|
86
|
+
const hasChanged = filteredProviders.length !== providers.length;
|
|
87
|
+
return [hasChanged ? filteredProviders : providers, hasChanged];
|
|
88
|
+
};
|
|
89
|
+
const filterServices = (services, conditionResults) => {
|
|
90
|
+
if (!services) {
|
|
91
|
+
return [services, false];
|
|
92
|
+
}
|
|
93
|
+
const filteredServices = {};
|
|
94
|
+
let hasChanged = false;
|
|
95
|
+
for (const [serviceId, serviceProviderModels] of Object.entries(services)) {
|
|
96
|
+
const result = [];
|
|
97
|
+
let localChanged = false;
|
|
98
|
+
for (const serviceConfigModel of serviceProviderModels) {
|
|
99
|
+
const include = shouldInclude(
|
|
100
|
+
serviceConfigModel.conditions,
|
|
101
|
+
conditionResults
|
|
102
|
+
);
|
|
103
|
+
if (include) {
|
|
104
|
+
result.push(serviceConfigModel);
|
|
105
|
+
} else {
|
|
106
|
+
localChanged = true;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (localChanged) hasChanged = true;
|
|
110
|
+
filteredServices[serviceId] = localChanged ? result : serviceProviderModels;
|
|
111
|
+
}
|
|
112
|
+
return [hasChanged ? filteredServices : services, hasChanged];
|
|
113
|
+
};
|
|
114
|
+
function filterModel(model, conditionResults) {
|
|
115
|
+
let hasChanged = false;
|
|
116
|
+
const filtered = { ...model };
|
|
117
|
+
if (model.mainPanel?.views) {
|
|
118
|
+
const [filteredViews, viewsChanged] = filterViews(
|
|
119
|
+
model.mainPanel.views,
|
|
120
|
+
conditionResults
|
|
121
|
+
);
|
|
122
|
+
if (viewsChanged) {
|
|
123
|
+
filtered.mainPanel = {
|
|
124
|
+
...model.mainPanel,
|
|
125
|
+
views: filteredViews
|
|
126
|
+
};
|
|
127
|
+
hasChanged = true;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (model.menu) {
|
|
131
|
+
const [filteredMenus, menusChanged] = filterMenus(
|
|
132
|
+
model.menu,
|
|
133
|
+
conditionResults
|
|
134
|
+
);
|
|
135
|
+
if (menusChanged) {
|
|
136
|
+
filtered.menu = filteredMenus;
|
|
137
|
+
hasChanged = true;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (model.header?.actions) {
|
|
141
|
+
const [filteredActions, actionsChanged] = filterActions(
|
|
142
|
+
model.header.actions,
|
|
143
|
+
conditionResults
|
|
144
|
+
);
|
|
145
|
+
if (actionsChanged) {
|
|
146
|
+
filtered.header = {
|
|
147
|
+
...model.header,
|
|
148
|
+
actions: filteredActions
|
|
149
|
+
};
|
|
150
|
+
hasChanged = true;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (model.providers) {
|
|
154
|
+
const [filteredProviders, providersChanged] = filterProviders(
|
|
155
|
+
model.providers,
|
|
156
|
+
conditionResults
|
|
157
|
+
);
|
|
158
|
+
if (providersChanged) {
|
|
159
|
+
filtered.providers = filteredProviders;
|
|
160
|
+
hasChanged = true;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (model.services) {
|
|
164
|
+
const [filteredServices, servicesChanged] = filterServices(
|
|
165
|
+
model.services,
|
|
166
|
+
conditionResults
|
|
167
|
+
);
|
|
168
|
+
if (servicesChanged) {
|
|
169
|
+
filtered.services = filteredServices;
|
|
170
|
+
hasChanged = true;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return hasChanged ? filtered : model;
|
|
174
|
+
}
|
|
175
|
+
export {
|
|
176
|
+
filterModel as default
|
|
177
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
async function lazyImport(bundle) {
|
|
2
|
+
try {
|
|
3
|
+
const module = await import(
|
|
4
|
+
/* @vite-ignore */
|
|
5
|
+
bundle
|
|
6
|
+
);
|
|
7
|
+
return {
|
|
8
|
+
isPending: false,
|
|
9
|
+
error: null,
|
|
10
|
+
module: module.default
|
|
11
|
+
};
|
|
12
|
+
} catch (err) {
|
|
13
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
14
|
+
return {
|
|
15
|
+
isPending: false,
|
|
16
|
+
error,
|
|
17
|
+
module: void 0
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
async function importAllBundles(bundles) {
|
|
22
|
+
const results = await Promise.all(
|
|
23
|
+
bundles.map((bundle) => lazyImport(bundle))
|
|
24
|
+
);
|
|
25
|
+
const preloadedBundles = /* @__PURE__ */ new Map();
|
|
26
|
+
for (let i = 0; i < results.length; i++) {
|
|
27
|
+
const result = results[i];
|
|
28
|
+
const bundle = bundles[i];
|
|
29
|
+
if (result.error) {
|
|
30
|
+
console.error(`Failed to load bundle ${bundle}:`, result.error);
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
preloadedBundles.set(bundle, result.module);
|
|
34
|
+
}
|
|
35
|
+
return preloadedBundles;
|
|
36
|
+
}
|
|
37
|
+
export {
|
|
38
|
+
importAllBundles,
|
|
39
|
+
lazyImport
|
|
40
|
+
};
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
const generateKey = () => {
|
|
2
|
+
return `${Date.now()}-${Math.round(1e3 * Math.random())}`;
|
|
3
|
+
};
|
|
4
|
+
const createRouteConditionsMap = (views) => {
|
|
5
|
+
const map = /* @__PURE__ */ new Map();
|
|
6
|
+
const processView = (view) => {
|
|
7
|
+
if (view.route && view.conditions) {
|
|
8
|
+
const normalizedRoute = view.route.replace(/^\//, "");
|
|
9
|
+
map.set(normalizedRoute, view.conditions);
|
|
10
|
+
}
|
|
11
|
+
if (view.views) {
|
|
12
|
+
for (const nestedView of view.views) {
|
|
13
|
+
processView(nestedView);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
for (const view of views) {
|
|
18
|
+
processView(view);
|
|
19
|
+
}
|
|
20
|
+
return map;
|
|
21
|
+
};
|
|
22
|
+
const applyViewConditionsToMenus = (menus, routeConditionsMap) => {
|
|
23
|
+
return menus.map((menu) => {
|
|
24
|
+
let updatedMenu = menu;
|
|
25
|
+
if (menu.target) {
|
|
26
|
+
const normalizedTarget = menu.target.replace(/^\//, "");
|
|
27
|
+
const viewConditions = routeConditionsMap.get(normalizedTarget);
|
|
28
|
+
if (viewConditions) {
|
|
29
|
+
const mergedConditions = [
|
|
30
|
+
...menu.conditions || [],
|
|
31
|
+
...viewConditions
|
|
32
|
+
];
|
|
33
|
+
if (mergedConditions.length > 0) {
|
|
34
|
+
updatedMenu = { ...menu, conditions: mergedConditions };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (menu.submenus) {
|
|
39
|
+
const updatedSubmenus = applyViewConditionsToMenus(
|
|
40
|
+
menu.submenus,
|
|
41
|
+
routeConditionsMap
|
|
42
|
+
);
|
|
43
|
+
if (updatedSubmenus !== menu.submenus) {
|
|
44
|
+
updatedMenu = { ...updatedMenu, submenus: updatedSubmenus };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return updatedMenu;
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
function processConfig(appShellConfig) {
|
|
51
|
+
let globalIndex = 0;
|
|
52
|
+
const allConditions = [];
|
|
53
|
+
const registerElement = (configElement) => {
|
|
54
|
+
const key = generateKey();
|
|
55
|
+
const conditions = configElement.conditions?.map((condConfig) => {
|
|
56
|
+
const conditionModel = {
|
|
57
|
+
...condConfig,
|
|
58
|
+
globalIndex: globalIndex++
|
|
59
|
+
};
|
|
60
|
+
allConditions.push(conditionModel);
|
|
61
|
+
return conditionModel;
|
|
62
|
+
});
|
|
63
|
+
return {
|
|
64
|
+
...configElement,
|
|
65
|
+
key,
|
|
66
|
+
...conditions && { conditions }
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
const processViews = (viewsConfig) => {
|
|
70
|
+
if (!viewsConfig) {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
return viewsConfig.map((viewConfig) => {
|
|
74
|
+
const viewModel = registerElement(viewConfig);
|
|
75
|
+
if (viewConfig.views) {
|
|
76
|
+
return {
|
|
77
|
+
...viewModel,
|
|
78
|
+
views: processViews(viewConfig.views)
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return viewModel;
|
|
82
|
+
});
|
|
83
|
+
};
|
|
84
|
+
const processMenus = (menusConfig) => {
|
|
85
|
+
if (!menusConfig) {
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
return menusConfig.map((menuConfig) => {
|
|
89
|
+
const menuModel = registerElement(
|
|
90
|
+
menuConfig
|
|
91
|
+
);
|
|
92
|
+
if (menuConfig.submenus) {
|
|
93
|
+
return {
|
|
94
|
+
...menuModel,
|
|
95
|
+
submenus: processMenus(menuConfig.submenus)
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
return menuModel;
|
|
99
|
+
});
|
|
100
|
+
};
|
|
101
|
+
const config = { ...appShellConfig };
|
|
102
|
+
let viewsModel;
|
|
103
|
+
if (appShellConfig.mainPanel?.views) {
|
|
104
|
+
viewsModel = processViews(appShellConfig.mainPanel.views);
|
|
105
|
+
config.mainPanel = {
|
|
106
|
+
...appShellConfig.mainPanel,
|
|
107
|
+
views: viewsModel
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
if (appShellConfig.menu) {
|
|
111
|
+
let menusModel = processMenus(appShellConfig.menu);
|
|
112
|
+
if (viewsModel) {
|
|
113
|
+
const routeConditionsMap = createRouteConditionsMap(viewsModel);
|
|
114
|
+
menusModel = applyViewConditionsToMenus(menusModel, routeConditionsMap);
|
|
115
|
+
}
|
|
116
|
+
config.menu = menusModel;
|
|
117
|
+
}
|
|
118
|
+
if (appShellConfig.header?.actions) {
|
|
119
|
+
config.header = {
|
|
120
|
+
...appShellConfig.header,
|
|
121
|
+
actions: appShellConfig.header.actions.map(
|
|
122
|
+
(action) => registerElement(action)
|
|
123
|
+
)
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
if (appShellConfig.systemProviders) {
|
|
127
|
+
config.systemProviders = appShellConfig.systemProviders.map((provider) => ({
|
|
128
|
+
...provider,
|
|
129
|
+
key: generateKey()
|
|
130
|
+
}));
|
|
131
|
+
}
|
|
132
|
+
if (appShellConfig.providers) {
|
|
133
|
+
config.providers = appShellConfig.providers.map(
|
|
134
|
+
(provider) => registerElement(provider)
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
if (appShellConfig.services) {
|
|
138
|
+
const servicesModel = {};
|
|
139
|
+
for (const [serviceId, configs] of Object.entries(
|
|
140
|
+
appShellConfig.services
|
|
141
|
+
)) {
|
|
142
|
+
servicesModel[serviceId] = configs.map(
|
|
143
|
+
(serviceConfig) => registerElement(
|
|
144
|
+
serviceConfig
|
|
145
|
+
)
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
config.services = servicesModel;
|
|
149
|
+
}
|
|
150
|
+
return Object.freeze({
|
|
151
|
+
...config,
|
|
152
|
+
allConditions,
|
|
153
|
+
preloadedBundles: /* @__PURE__ */ new Map()
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
export {
|
|
157
|
+
processConfig as default
|
|
158
|
+
};
|
package/package.json
CHANGED
|
@@ -1,33 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hitachivantara/app-shell-ui",
|
|
3
|
-
"version": "2.0.0-next.
|
|
3
|
+
"version": "2.0.0-next.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"private": false,
|
|
6
6
|
"author": "Hitachi Vantara UI Kit Team",
|
|
7
7
|
"description": "AppShell Component",
|
|
8
|
-
"homepage": "https://github.com/
|
|
8
|
+
"homepage": "https://github.com/pentaho/hv-uikit-react",
|
|
9
9
|
"sideEffects": false,
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"repository": {
|
|
12
12
|
"type": "git",
|
|
13
|
-
"url": "git+https://github.com/
|
|
13
|
+
"url": "git+https://github.com/pentaho/hv-uikit-react.git",
|
|
14
14
|
"directory": "packages/app-shell-ui"
|
|
15
15
|
},
|
|
16
|
-
"bugs": "https://github.com/
|
|
16
|
+
"bugs": "https://github.com/pentaho/hv-uikit-react/issues",
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@emotion/css": "^11.10.5",
|
|
19
19
|
"@emotion/react": "^11.10.5",
|
|
20
20
|
"@emotion/styled": "^11.10.5",
|
|
21
|
-
"@hitachivantara/app-shell-events": "^2.0.0-next.
|
|
22
|
-
"@hitachivantara/app-shell-navigation": "^2.0.0-next.
|
|
23
|
-
"@hitachivantara/app-shell-services": "^2.0.0-next.
|
|
24
|
-
"@hitachivantara/app-shell-shared": "^2.0.0-next.
|
|
25
|
-
"@hitachivantara/uikit-react-core": "^6.0.0-next.
|
|
26
|
-
"@hitachivantara/uikit-react-icons": "^6.0.0-next.
|
|
21
|
+
"@hitachivantara/app-shell-events": "^2.0.0-next.2",
|
|
22
|
+
"@hitachivantara/app-shell-navigation": "^2.0.0-next.2",
|
|
23
|
+
"@hitachivantara/app-shell-services": "^2.0.0-next.2",
|
|
24
|
+
"@hitachivantara/app-shell-shared": "^2.0.0-next.2",
|
|
25
|
+
"@hitachivantara/uikit-react-core": "^6.0.0-next.3",
|
|
26
|
+
"@hitachivantara/uikit-react-icons": "^6.0.0-next.3",
|
|
27
27
|
"@mui/material": "^7.0.2",
|
|
28
28
|
"i18next": "^24.2.2",
|
|
29
29
|
"i18next-browser-languagedetector": "^8.0.3",
|
|
30
|
-
"react-error-boundary": "^
|
|
30
|
+
"react-error-boundary": "^6.0.0",
|
|
31
31
|
"react-helmet-async": "^2.0.1",
|
|
32
32
|
"react-i18next": "15.7.1",
|
|
33
33
|
"uid": "^2.0.2"
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"access": "public",
|
|
45
45
|
"directory": "package"
|
|
46
46
|
},
|
|
47
|
-
"gitHead": "
|
|
47
|
+
"gitHead": "f404f47dd2c0a9d4033b9acccc87282aa9e42119",
|
|
48
48
|
"exports": {
|
|
49
49
|
".": {
|
|
50
50
|
"types": "./dist/index.d.ts",
|