Router.tsx 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. /*
  2. * Copyright 2021-2025 Avaiga Private Limited
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
  5. * the License. You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
  10. * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
  11. * specific language governing permissions and limitations under the License.
  12. */
  13. import React, { useEffect, useReducer, useState } from "react";
  14. import axios from "axios";
  15. import Box from "@mui/material/Box";
  16. import CircularProgress from "@mui/material/CircularProgress";
  17. import CssBaseline from "@mui/material/CssBaseline";
  18. import { ThemeProvider } from "@mui/system";
  19. import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
  20. import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFnsV3";
  21. import { SnackbarProvider } from "notistack";
  22. import { HelmetProvider } from "react-helmet-async";
  23. import { BrowserRouter, Route, Routes } from "react-router";
  24. import { ErrorBoundary } from "react-error-boundary";
  25. import { DndProvider } from "react-dnd";
  26. import { HTML5Backend } from "react-dnd-html5-backend";
  27. import { PageContext, TaipyContext } from "../context/taipyContext";
  28. import {
  29. createBlockAction,
  30. createSetLocationsAction,
  31. initializeWebSocket,
  32. INITIAL_STATE,
  33. retrieveBlockUi,
  34. taipyInitialize,
  35. taipyReducer,
  36. } from "../context/taipyReducers";
  37. import UIBlocker from "./Taipy/UIBlocker";
  38. import Navigate from "./Taipy/Navigate";
  39. import Menu from "./Taipy/Menu";
  40. import TaipyNotification from "./Taipy/Notification";
  41. import GuiDownload from "./Taipy/GuiDownload";
  42. import ErrorFallback from "../utils/ErrorBoundary";
  43. import MainPage from "./pages/MainPage";
  44. import TaipyRendered from "./pages/TaipyRendered";
  45. import NotFound404 from "./pages/NotFound404";
  46. import { getBaseURL } from "../utils";
  47. import { useLocalStorageWithEvent } from "../hooks";
  48. interface AxiosRouter {
  49. router: string;
  50. locations: Record<string, string>;
  51. blockUI: boolean;
  52. }
  53. const mainSx = { flexGrow: 1, bgcolor: "background.default" };
  54. const containerSx = { display: "flex" };
  55. const progressSx = { position: "fixed", bottom: "1em", right: "1em" };
  56. const pageStore = {};
  57. const Router = () => {
  58. const [state, dispatch] = useReducer(taipyReducer, INITIAL_STATE, taipyInitialize);
  59. const [routes, setRoutes] = useState<Record<string, string>>({});
  60. const refresh = !!Object.keys(routes).length;
  61. const themeClass = "taipy-" + state.theme.palette.mode;
  62. const baseURL = getBaseURL();
  63. useLocalStorageWithEvent(dispatch, state.id);
  64. useEffect(() => {
  65. if (refresh) {
  66. // no need to access the backend again, the routes are static
  67. return;
  68. }
  69. if (!state.isSocketConnected) {
  70. // initialize only when there is an existing ws connection
  71. // --> assuring that there is a session data scope on the backend
  72. return;
  73. }
  74. // Fetch Flask Rendered JSX React Router
  75. axios
  76. .get<AxiosRouter>("taipy-init", { params: { client_id: state.id || "", v: window.taipyVersion } })
  77. .then((result) => {
  78. dispatch(createSetLocationsAction(result.data.locations));
  79. setRoutes(result.data.locations);
  80. result.data.blockUI && dispatch(createBlockAction(retrieveBlockUi()));
  81. })
  82. .catch((error) => {
  83. // Fallback router if there is any error
  84. setRoutes({ "/": "/TaiPy_root_page" });
  85. console.log(error);
  86. });
  87. }, [refresh, state.isSocketConnected, state.id]);
  88. useEffect(() => {
  89. initializeWebSocket(state.socket, dispatch);
  90. }, [state.socket]);
  91. useEffect(() => {
  92. const classes = [themeClass];
  93. document.body.classList.forEach((cls) => {
  94. if (!cls.startsWith("taipy-")) {
  95. classes.push(cls);
  96. }
  97. });
  98. document.body.className = classes.join(" ");
  99. }, [themeClass]);
  100. return (
  101. <TaipyContext.Provider value={{ state, dispatch }}>
  102. <HelmetProvider>
  103. <ThemeProvider theme={state.theme}>
  104. <SnackbarProvider maxSnack={5}>
  105. <LocalizationProvider dateAdapter={AdapterDateFns}>
  106. <DndProvider backend={HTML5Backend}>
  107. <PageContext.Provider value={pageStore}>
  108. <BrowserRouter>
  109. <Box style={containerSx}>
  110. <CssBaseline />
  111. <ErrorBoundary FallbackComponent={ErrorFallback}>
  112. <Menu {...state.menu} />
  113. </ErrorBoundary>
  114. <Box component="main" sx={mainSx}>
  115. <ErrorBoundary FallbackComponent={ErrorFallback}>
  116. {Object.keys(routes).length ? (
  117. <Routes>
  118. <Route
  119. path={baseURL}
  120. element={
  121. <MainPage
  122. path={routes["/"]}
  123. route={Object.keys(routes).find(
  124. (path) => path !== "/"
  125. )}
  126. />
  127. }
  128. >
  129. {Object.entries(routes)
  130. .filter(([path]) => path !== "/")
  131. .map(([path, name]) => (
  132. <Route
  133. key={name}
  134. path={path.substring(1)}
  135. element={<TaipyRendered />}
  136. />
  137. ))}
  138. <Route
  139. path="*"
  140. key="NotFound"
  141. element={<NotFound404 />}
  142. />
  143. </Route>
  144. </Routes>
  145. ) : null}
  146. </ErrorBoundary>
  147. </Box>
  148. {state.ackList.length ? (
  149. <Box sx={progressSx} className="taipy-busy">
  150. <CircularProgress size="1em" disableShrink />
  151. </Box>
  152. ) : null}
  153. </Box>
  154. <ErrorBoundary FallbackComponent={ErrorFallback}>
  155. <TaipyNotification notifications={state.notifications} />
  156. <UIBlocker block={state.block} />
  157. <Navigate
  158. to={state.navigateTo}
  159. params={state.navigateParams}
  160. tab={state.navigateTab}
  161. force={state.navigateForce}
  162. />
  163. <GuiDownload download={state.download} />
  164. </ErrorBoundary>
  165. </BrowserRouter>
  166. </PageContext.Provider>
  167. </DndProvider>
  168. </LocalizationProvider>
  169. </SnackbarProvider>
  170. </ThemeProvider>
  171. </HelmetProvider>
  172. </TaipyContext.Provider>
  173. );
  174. };
  175. export default Router;