1
0

TaipyRendered.tsx 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  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, { ComponentType, useContext, useEffect, useState } from "react";
  14. import axios from "axios";
  15. import { ErrorBoundary } from "react-error-boundary";
  16. import { Helmet } from "react-helmet-async";
  17. import JsxParser from "react-jsx-parser";
  18. import { useLocation } from "react-router-dom";
  19. import { PageContext, TaipyContext } from "../../context/taipyContext";
  20. import { createPartialAction } from "../../context/taipyReducers";
  21. import { emptyArray, getBaseURL } from "../../utils";
  22. import ErrorFallback from "../../utils/ErrorBoundary";
  23. import { getRegisteredComponents } from "../Taipy";
  24. import { renderError, unregisteredRender } from "../Taipy/Unregistered";
  25. interface TaipyRenderedProps {
  26. path?: string;
  27. partial?: boolean;
  28. fromBlock?: boolean;
  29. }
  30. interface HeadProps {
  31. tag: string;
  32. props: Record<string, string>;
  33. content: string;
  34. }
  35. interface AxiosRenderer {
  36. jsx: string;
  37. style: string;
  38. head: HeadProps[];
  39. context: string;
  40. scriptPaths: string[];
  41. }
  42. // set global style the traditional way
  43. const setStyle = (id: string, styleString: string): void => {
  44. let style = document.getElementById(id);
  45. if (style && style.tagName !== "STYLE") {
  46. style = null;
  47. id = "TaiPy_" + id;
  48. }
  49. if (!style && styleString) {
  50. style = document.createElement("style");
  51. style.id = id;
  52. document.head.append(style);
  53. }
  54. if (style) {
  55. style.textContent = styleString;
  56. }
  57. };
  58. // set script tag for the page
  59. const setScript = (id: string, scriptPaths: string[]): void => {
  60. document.querySelectorAll(`script[id^="${id}_"]`).forEach(script => script.remove());
  61. scriptPaths.forEach((path, index) => {
  62. const script = document.createElement("script");
  63. script.id = `${id}_${index}`;
  64. script.src = path;
  65. script.defer = true;
  66. document.head.append(script);
  67. });
  68. };
  69. interface PageState {
  70. jsx?: string;
  71. module?: string;
  72. }
  73. const TaipyRendered = (props: TaipyRenderedProps) => {
  74. const { partial, fromBlock } = props;
  75. const location = useLocation();
  76. const [pageState, setPageState] = useState<PageState>({});
  77. const [head, setHead] = useState<HeadProps[]>([]);
  78. const { state, dispatch } = useContext(TaipyContext);
  79. const baseURL = getBaseURL();
  80. const pathname = baseURL == "/" ? location.pathname : location.pathname.replace(baseURL, "/");
  81. const path =
  82. props.path || (state.locations && pathname in state.locations && state.locations[pathname]) || pathname;
  83. useEffect(() => {
  84. // Fetch JSX Flask Backend Render
  85. if (partial) {
  86. dispatch(createPartialAction(path.slice(1), false));
  87. } else {
  88. const searchParams = new URLSearchParams(location.search);
  89. const params = Object.fromEntries(searchParams.entries());
  90. axios
  91. .get<AxiosRenderer>(`taipy-jsx${path}`, {
  92. params: { ...params, client_id: state.id || "", v: window.taipyVersion },
  93. })
  94. .then((result) => {
  95. // set rendered JSX and CSS style from fetch result
  96. if (typeof result.data.jsx === "string") {
  97. setPageState({ module: result.data.context, jsx: result.data.jsx });
  98. }
  99. if (!fromBlock) {
  100. setStyle(
  101. path == "/TaiPy_root_page" ? "Taipy_root_style" : "Taipy_style",
  102. result.data.style || "",
  103. );
  104. Array.isArray(result.data.head) && setHead(result.data.head);
  105. Array.isArray(result.data.scriptPaths) && setScript("Taipy_script", result.data.scriptPaths);
  106. }
  107. })
  108. .catch((error) => {
  109. const res =
  110. error.response?.data && /<p\sclass=\"errormsg\">([\s\S]*?)<\/p>/gm.exec(error.response?.data);
  111. setPageState({
  112. jsx: `<h1>${res ? res[0] : "Unknown Error"}</h1><h2>No data fetched from backend from ${
  113. path === "/TaiPy_root_page" ? baseURL : baseURL + path
  114. }</h2><br></br>${res[0] ? "" : error}`,
  115. });
  116. });
  117. }
  118. // eslint-disable-next-line react-hooks/exhaustive-deps
  119. }, [path, state.id, dispatch, partial, fromBlock, baseURL]);
  120. return (
  121. <ErrorBoundary FallbackComponent={ErrorFallback}>
  122. {head.length ? (
  123. <Helmet>
  124. {head.map((v, i) => React.createElement(v.tag, { key: `head${i}`, ...v.props }, v.content))}
  125. </Helmet>
  126. ) : null}
  127. <PageContext.Provider value={pageState}>
  128. <JsxParser
  129. disableKeyGeneration={true}
  130. bindings={state.data}
  131. components={getRegisteredComponents() as Record<string, ComponentType>}
  132. jsx={pageState.jsx}
  133. renderUnrecognized={unregisteredRender}
  134. allowUnknownElements={false}
  135. renderError={renderError}
  136. blacklistedAttrs={emptyArray}
  137. />
  138. </PageContext.Provider>
  139. </ErrorBoundary>
  140. );
  141. };
  142. export default TaipyRendered;