Skip to content

Commit

Permalink
each element is rendered as a separate jsx
Browse files Browse the repository at this point in the history
  • Loading branch information
dinhlongviolin1 committed Feb 20, 2025
1 parent 3f6da5c commit f627f24
Show file tree
Hide file tree
Showing 11 changed files with 362 additions and 332 deletions.
85 changes: 41 additions & 44 deletions frontend/taipy-gui/base/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,15 @@ import { TaipyWsAdapter, WsAdapter } from "./wsAdapter";
import { WsMessageType } from "../../src/context/wsUtils";
import { getBase } from "./utils";
import { CookieHandler } from "./cookieHandler";
import { ElementAction, ElementManager } from "./renderer/elementManager";
import useStore from "./store";
import { CanvasRenderConfig, Element, ElementAction, ElementManager } from "./renderer/elementManager";

export type OnInitHandler = (taipyApp: TaipyApp) => void;
export type OnChangeHandler = (taipyApp: TaipyApp, encodedName: string, value: unknown, dataEventKey?: string) => void;
export type OnNotifyHandler = (taipyApp: TaipyApp, type: string, message: string) => void;
export type OnReloadHandler = (taipyApp: TaipyApp, removedChanges: ModuleData) => void;
export type OnWsMessage = (taipyApp: TaipyApp, event: string, payload: unknown) => void;
export type OnWsStatusUpdate = (taipyApp: TaipyApp, messageQueue: string[]) => void;
export type OnCanvasReRender = (taipyApp: TaipyApp, isEditMode: boolean, elementActions?: ElementAction) => void;
export type OnCanvasReRender = (taipyApp: TaipyApp, isEditMode: boolean, elementAction?: ElementAction) => void;
export type OnEvent =
| OnInitHandler
| OnChangeHandler
Expand All @@ -32,16 +31,15 @@ type RequestDataCallback = (taipyApp: TaipyApp, encodedName: string, dataEventKe

export class TaipyApp {
socket: Socket;
_onInit: OnInitHandler | undefined;
_onChange: OnChangeHandler | undefined;
_onNotify: OnNotifyHandler | undefined;
_onReload: OnReloadHandler | undefined;
_onWsMessage: OnWsMessage | undefined;
_onWsStatusUpdate: OnWsStatusUpdate | undefined;
_onCanvasReRender: OnCanvasReRender | undefined;
_ackList: string[];
_rdc: Record<string, Record<string, RequestDataCallback>>;
_cookieHandler: CookieHandler | undefined;
#onInit: OnInitHandler | undefined;
#onChange: OnChangeHandler | undefined;
#onNotify: OnNotifyHandler | undefined;
#onReload: OnReloadHandler | undefined;
#onWsMessage: OnWsMessage | undefined;
#onWsStatusUpdate: OnWsStatusUpdate | undefined;
#onCanvasReRender: OnCanvasReRender | undefined;
ackList: string[];
rdc: Record<string, Record<string, RequestDataCallback>>;
variableData: DataManager | undefined;
functionData: DataManager | undefined;
appId: string;
Expand Down Expand Up @@ -74,116 +72,114 @@ export class TaipyApp {
this.socket = socket;
this.wsAdapters = [new TaipyWsAdapter()];
this.elementManager = new ElementManager(this);
this._ackList = [];
this._rdc = {};
this._cookieHandler = handleCookie ? new CookieHandler() : undefined;
this.ackList = [];
this.rdc = {};
// Init socket io connection only when cookie is not handled
// Socket will be initialized by cookie handler when it is used
this._cookieHandler ? this._cookieHandler?.init(socket, this) : initSocket(socket, this);
handleCookie ? new CookieHandler().init(socket, this) : initSocket(socket, this);
}

// Getter and setter
get onInit() {
return this._onInit;
return this.#onInit;
}

set onInit(handler: OnInitHandler | undefined) {
if (handler !== undefined && handler.length !== 1) {
throw new Error("onInit() requires one parameter");
}
this._onInit = handler;
this.#onInit = handler;
}

onInitEvent() {
this.onInit && this.onInit(this);
}

get onChange() {
return this._onChange;
return this.#onChange;
}

set onChange(handler: OnChangeHandler | undefined) {
if (handler !== undefined && handler.length !== 3 && handler.length !== 4) {
throw new Error("onChange() requires three or four parameters");
}
this._onChange = handler;
this.#onChange = handler;
}

onChangeEvent(encodedName: string, value: unknown, dataEventKey?: string) {
this.onChange && this.onChange(this, encodedName, value, dataEventKey);
}

get onNotify() {
return this._onNotify;
return this.#onNotify;
}

set onNotify(handler: OnNotifyHandler | undefined) {
if (handler !== undefined && handler.length !== 3) {
throw new Error("onNotify() requires three parameters");
}
this._onNotify = handler;
this.#onNotify = handler;
}

onNotifyEvent(type: string, message: string) {
this.onNotify && this.onNotify(this, type, message);
}

get onReload() {
return this._onReload;
return this.#onReload;
}
set onReload(handler: OnReloadHandler | undefined) {
if (handler !== undefined && handler?.length !== 2) {
throw new Error("onReload() requires two parameters");
}
this._onReload = handler;
this.#onReload = handler;
}

onReloadEvent(removedChanges: ModuleData) {
this.onReload && this.onReload(this, removedChanges);
}

get onWsMessage() {
return this._onWsMessage;
return this.#onWsMessage;
}
set onWsMessage(handler: OnWsMessage | undefined) {
if (handler !== undefined && handler?.length !== 3) {
throw new Error("onWsMessage() requires three parameters");
}
this._onWsMessage = handler;
this.#onWsMessage = handler;
}

onWsMessageEvent(event: string, payload: unknown) {
this.onWsMessage && this.onWsMessage(this, event, payload);
}

get onWsStatusUpdate() {
return this._onWsStatusUpdate;
return this.#onWsStatusUpdate;
}
set onWsStatusUpdate(handler: OnWsStatusUpdate | undefined) {
if (handler !== undefined && handler?.length !== 2) {
throw new Error("onWsStatusUpdate() requires two parameters");
}
this._onWsStatusUpdate = handler;
this.#onWsStatusUpdate = handler;
}

onWsStatusUpdateEvent(messageQueue: string[]) {
this.onWsStatusUpdate && this.onWsStatusUpdate(this, messageQueue);
}

get onCanvasReRender() {
return this._onCanvasReRender;
return this.#onCanvasReRender;
}

set onCanvasReRender(handler: OnCanvasReRender | undefined) {
if (handler !== undefined && handler?.length !== 3) {
throw new Error("onCanvasReRender() requires three parameter");
}
this._onCanvasReRender = handler;
this.#onCanvasReRender = handler;
}

onCanvasReRenderEvent() {
this.onCanvasReRender &&
this.onCanvasReRender(this, useStore.getState().editMode, this.elementManager.getElementActionFromQueue());
onCanvasReRenderEvent(canvasIsEditMode: boolean, elementAction?: ElementAction) {
this.onCanvasReRender && this.onCanvasReRender(this, canvasIsEditMode, elementAction);
}

// Utility methods
Expand Down Expand Up @@ -212,8 +208,8 @@ export class TaipyApp {
}
const ackId = sendWsMessage(this.socket, type, id, payload, this.clientId, context);
if (ackId) {
this._ackList.push(ackId);
this.onWsStatusUpdateEvent(this._ackList);
this.ackList.push(ackId);
this.onWsStatusUpdateEvent(this.ackList);
}
}

Expand Down Expand Up @@ -281,7 +277,7 @@ export class TaipyApp {
// preserve options for this data key so it can be called during refresh
this.variableData?.addRequestDataOptions(encodedName, dataKey, options);
// preserve callback so it can be called later
this._rdc[encodedName] = { ...this._rdc[encodedName], [dataKey]: cb };
this.rdc[encodedName] = { ...this.rdc[encodedName], [dataKey]: cb };
// call the ws to request data
this.sendWsMessage("DU", encodedName, options);
}
Expand Down Expand Up @@ -311,32 +307,33 @@ export class TaipyApp {
}

getWsStatus() {
return this._ackList;
return this.ackList;
}

getBaseUrl() {
return getBase();
}

// ElementManager API
createCanvas(canvasDomElement: HTMLElement, canvasEditModeCanvas?: HTMLElement) {
this.elementManager.init(canvasDomElement, canvasEditModeCanvas);
}

addElement2Canvas(
type: string,
properties: Record<string, string> | undefined = undefined,
wrapperHtml: [string, string] | undefined = undefined,
wrapperHtmlEditMode: [string, string] | undefined = undefined,
id: string | undefined = undefined,
id: string,
rootId: string,
wrapper: CanvasRenderConfig["wrapper"],
properties: Element["properties"] | undefined = undefined,
) {
this.elementManager.addElement({ id, type, properties, wrapperHtml, wrapperHtmlEditMode });
this.elementManager.addElement(type, id, rootId, wrapper, properties);
}

setCanvasEditMode(bool: boolean) {
this.elementManager.setEditMode(bool);
}

modifyElement(id: string, elemenetProperties: Record<string, string>) {
modifyElement(id: string, elemenetProperties: Element["properties"]) {
this.elementManager.modifyElement(id, elemenetProperties);
}

Expand Down
75 changes: 75 additions & 0 deletions frontend/taipy-gui/base/src/components/Taipy/TaipyElement.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import React, { ComponentType, useContext, useEffect, useMemo, useState } from "react";
import { createPortal } from "react-dom";
import { ErrorBoundary } from "react-error-boundary";
import JsxParser from "react-jsx-parser";

import { PageContext, TaipyContext } from "../../../../src/context/taipyContext";
import { Element } from "../../renderer/elementManager";
import useStore from "../../store";
import { getJsx } from "./utils";
import { emptyArray } from "../../../../src/utils";
import ErrorFallback from "../../../../src/utils/ErrorBoundary";
import { getRegisteredComponents } from "../../../../src/components/Taipy";
import { renderError, unregisteredRender } from "../../../../src/components/Taipy/Unregistered";

interface TaipyElementProps {
editMode: boolean;
element: Element;
}

const TaipyElement = (props: TaipyElementProps) => {
const { state } = useContext(TaipyContext);
const [module, setModule] = useState<string>("");
const [jsx, setJsx] = useState<string>("");
const app = useStore((state) => state.app);

const renderConfig = useMemo(
() => (props.editMode ? props.element.editModeRenderConfig : props.element.renderConfig),
[props.element, props.editMode],
);

const pageState = useMemo(() => {
return { jsx, module };
}, [jsx, module]);

useEffect(() => {
app && setModule(app.getContext());
}, [app]);

useEffect(() => {
const setJsxAsync = async () => {
if (!app || !renderConfig) {
setJsx("");
return;
}
const res = await getJsx(app, props.element, props.editMode);
setJsx(`${renderConfig.wrapper[0]}${res}${renderConfig.wrapper[1]}`);
};
setJsxAsync();
}, [app, props.editMode, props.element, renderConfig]);

return renderConfig ? (
createPortal(
<PageContext.Provider value={pageState}>
<ErrorBoundary FallbackComponent={ErrorFallback}>
<JsxParser
disableKeyGeneration={true}
bindings={state.data}
components={getRegisteredComponents() as Record<string, ComponentType>}
jsx={pageState.jsx}
renderUnrecognized={unregisteredRender}
allowUnknownElements={false}
renderError={renderError}
blacklistedAttrs={emptyArray}
renderInWrapper={false}
/>
</ErrorBoundary>
</PageContext.Provider>,
renderConfig.root,
)
) : (
<></>
);
};

export default TaipyElement;
46 changes: 12 additions & 34 deletions frontend/taipy-gui/base/src/components/Taipy/TaipyRendered.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,40 +11,30 @@
* specific language governing permissions and limitations under the License.
*/

import React, { ComponentType, useEffect, useMemo, useReducer } from "react";
import { ErrorBoundary } from "react-error-boundary";
import JsxParser from "react-jsx-parser";
import React, { useEffect, useReducer } from "react";

import { ThemeProvider } from "@mui/system";
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFnsV3";

import { PageContext, TaipyContext } from "../../../../src/context/taipyContext";
import { emptyArray } from "../../../../src/utils";
import ErrorFallback from "../../../../src/utils/ErrorBoundary";
import { getRegisteredComponents } from "../../../../src/components/Taipy";
import { renderError, unregisteredRender } from "../../../../src/components/Taipy/Unregistered";
import { TaipyContext } from "../../../../src/context/taipyContext";
import {
INITIAL_STATE,
initializeWebSocket,
taipyInitialize,
taipyReducer,
} from "../../../../src/context/taipyReducers";
import useStore from "../../store";
import useStore, { getElementAction } from "../../store";
import TaipyElement from "./TaipyElement";

interface TaipyRenderedProps {
editMode?: boolean;
editMode: boolean;
}

const TaipyRendered = (props: TaipyRenderedProps) => {
const { editMode } = props;
const jsx = useStore((state) => (editMode ? state.editModeJsx : state.jsx));
const module = useStore((state) => state.module);
const app = useStore((state) => state.app);
const pageState = useMemo(() => {
return { jsx, module };
}, [jsx, module]);
const [state, dispatch] = useReducer(taipyReducer, INITIAL_STATE, taipyInitialize);
const elements = useStore((state) => state.elements);
const app = useStore((state) => state.app);
const themeClass = "taipy-" + state.theme.palette.mode;

useEffect(() => {
Expand All @@ -62,28 +52,16 @@ const TaipyRendered = (props: TaipyRenderedProps) => {
}, [themeClass]);

useEffect(() => {
app && app.onCanvasReRenderEvent();
}, [jsx, app]);
app && app.onCanvasReRenderEvent(props.editMode, getElementAction(props.editMode));
}, [elements, app, props.editMode]);

return (
<TaipyContext.Provider value={{ state, dispatch }}>
<ThemeProvider theme={state.theme}>
<LocalizationProvider dateAdapter={AdapterDateFns}>
<PageContext.Provider value={pageState}>
<ErrorBoundary FallbackComponent={ErrorFallback}>
<JsxParser
disableKeyGeneration={true}
bindings={state.data}
components={getRegisteredComponents() as Record<string, ComponentType>}
jsx={pageState.jsx}
renderUnrecognized={unregisteredRender}
allowUnknownElements={false}
renderError={renderError}
blacklistedAttrs={emptyArray}
renderInWrapper={false}
/>
</ErrorBoundary>
</PageContext.Provider>
{elements.map((element) => (
<TaipyElement element={element} key={element.id} editMode={props.editMode} />
))}
</LocalizationProvider>
</ThemeProvider>
</TaipyContext.Provider>
Expand Down
Loading

0 comments on commit f627f24

Please sign in to comment.