浏览代码

Remove taipy gui custom frontend (designer react) (#2630)

Dinh Long Nguyen 3 天之前
父节点
当前提交
12afd567f6

+ 0 - 1
frontend/taipy-gui/base/.gitignore

@@ -1 +0,0 @@
-dist

+ 0 - 308
frontend/taipy-gui/base/src/app.ts

@@ -1,308 +0,0 @@
-import { getLocalStorageValue } from "../../src/context/utils";
-import { sendWsMessage, TAIPY_CLIENT_ID } from "../../src/context/wsUtils";
-import { uploadFile } from "../../src/workers/fileupload";
-
-import { Socket, io } from "socket.io-client";
-import { nanoid } from "nanoid";
-import { DataManager, ModuleData, RequestDataOptions } from "./dataManager";
-import { initSocket } from "./socket";
-import { TaipyWsAdapter, WsAdapter } from "./wsAdapter";
-import { WsMessageType } from "../../src/context/wsUtils";
-import { getBase } from "./utils";
-import { CookieHandler } from "./cookieHandler";
-
-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 OnEvent =
-    | OnInitHandler
-    | OnChangeHandler
-    | OnNotifyHandler
-    | OnReloadHandler
-    | OnWsMessage
-    | OnWsStatusUpdate;
-type Route = [string, string];
-type RequestDataCallback = (taipyApp: TaipyApp, encodedName: string, dataEventKey: string, value: unknown) => void;
-
-export class TaipyApp {
-    socket: Socket;
-    _onInit: OnInitHandler | undefined;
-    _onChange: OnChangeHandler | undefined;
-    _onNotify: OnNotifyHandler | undefined;
-    _onReload: OnReloadHandler | undefined;
-    _onWsMessage: OnWsMessage | undefined;
-    _onWsStatusUpdate: OnWsStatusUpdate | undefined;
-    _ackList: string[];
-    _rdc: Record<string, Record<string, RequestDataCallback>>;
-    _cookieHandler: CookieHandler | undefined;
-    variableData: DataManager | undefined;
-    functionData: DataManager | undefined;
-    guiAddr: string;
-    clientId: string;
-    context: string;
-    metadata: Record<string, unknown>;
-    path: string | undefined;
-    routes: Route[] | undefined;
-    wsAdapters: WsAdapter[];
-
-    constructor(
-        onInit: OnInitHandler | undefined = undefined,
-        onChange: OnChangeHandler | undefined = undefined,
-        path: string | undefined = undefined,
-        socket: Socket | undefined = undefined,
-        handleCookie: boolean = true,
-    ) {
-        socket = socket || io("/", { autoConnect: false, path: `${this.getBaseUrl()}socket.io` });
-        this.onInit = onInit;
-        this.onChange = onChange;
-        this.variableData = undefined;
-        this.functionData = undefined;
-        this.clientId = "";
-        this.context = "";
-        this.metadata = {};
-        this.guiAddr = "";
-        this.routes = undefined;
-        this.path = path;
-        this.socket = socket;
-        this.wsAdapters = [new TaipyWsAdapter()];
-        this._ackList = [];
-        this._rdc = {};
-        this._cookieHandler = handleCookie ? new CookieHandler() : undefined;
-        // 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);
-    }
-
-    // Getter and setter
-    get 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;
-    }
-
-    onInitEvent() {
-        this.onInit && this.onInit(this);
-    }
-
-    get 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;
-    }
-
-    onChangeEvent(encodedName: string, value: unknown, dataEventKey?: string) {
-        this.onChange && this.onChange(this, encodedName, value, dataEventKey);
-    }
-
-    get 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;
-    }
-
-    onNotifyEvent(type: string, message: string) {
-        this.onNotify && this.onNotify(this, type, message);
-    }
-
-    get 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;
-    }
-
-    onReloadEvent(removedChanges: ModuleData) {
-        this.onReload && this.onReload(this, removedChanges);
-    }
-
-    get 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;
-    }
-
-    onWsMessageEvent(event: string, payload: unknown) {
-        this.onWsMessage && this.onWsMessage(this, event, payload);
-    }
-
-    get 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;
-    }
-
-    onWsStatusUpdateEvent(messageQueue: string[]) {
-        this.onWsStatusUpdate && this.onWsStatusUpdate(this, messageQueue);
-    }
-
-    // Utility methods
-    init() {
-        this.clientId = "";
-        this.context = "";
-        this.guiAddr = "";
-        this.routes = undefined;
-        const id = getLocalStorageValue(TAIPY_CLIENT_ID, "");
-        this.sendWsMessage("ID", TAIPY_CLIENT_ID, id);
-        if (id !== "") {
-            this.clientId = id;
-            this.initApp();
-            this.updateContext(this.path);
-        }
-    }
-
-    initApp() {
-        this.sendWsMessage("GA", "connect", "");
-        this.sendWsMessage("GR", "", "");
-    }
-
-    sendWsMessage(type: WsMessageType, id: string, payload: unknown, context: string | undefined = undefined) {
-        if (context === undefined) {
-            context = this.context;
-        }
-        const ackId = sendWsMessage(this.socket, type, id, payload, this.clientId, context);
-        if (ackId) {
-            this._ackList.push(ackId);
-            this.onWsStatusUpdateEvent(this._ackList);
-        }
-    }
-
-    // Public methods
-    registerWsAdapter(wsAdapter: WsAdapter) {
-        this.wsAdapters.unshift(wsAdapter);
-    }
-
-    getEncodedName(varName: string, module: string) {
-        return this.variableData?.getEncodedName(varName, module);
-    }
-
-    getName(encodedName: string) {
-        return this.variableData?.getName(encodedName);
-    }
-
-    get(encodedName: string, dataEventKey?: string) {
-        return this.variableData?.get(encodedName, dataEventKey);
-    }
-
-    getInfo(encodedName: string) {
-        return this.variableData?.getInfo(encodedName);
-    }
-
-    getDataTree() {
-        return this.variableData?.getDataTree();
-    }
-
-    getAllData() {
-        return this.variableData?.getAllData();
-    }
-
-    getFunctionList() {
-        const functionData = this.functionData?.getDataTree()[this.context];
-        return Object.keys(functionData || {});
-    }
-
-    getRoutes() {
-        return this.routes;
-    }
-
-    deleteRequestedData(encodedName: string, dataEventKey: string) {
-        this.variableData?.deleteRequestedData(encodedName, dataEventKey);
-    }
-
-    // This update will only send the request to Taipy Gui backend
-    // the actual update will be handled when the backend responds
-    update(encodedName: string, value: unknown) {
-        this.sendWsMessage("U", encodedName, { value: value });
-    }
-
-    // Request Data from taipy backend
-    // This will trigger the backend to send the data to the frontend
-    requestData(encodedName: string, cb: RequestDataCallback, options?: RequestDataOptions) {
-        const varInfo = this.getInfo(encodedName);
-        if (!varInfo?.data_update) {
-            throw new Error(`Cannot request data for ${encodedName}. Not supported for type of ${varInfo?.type}`);
-        }
-        // Populate pagekey if there is no pagekey
-        if (!options) {
-            options = { pagekey: nanoid(10) };
-        }
-        options.pagekey = options?.pagekey || nanoid(10);
-        const dataKey = options.pagekey;
-        // 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 };
-        // call the ws to request data
-        this.sendWsMessage("DU", encodedName, options);
-    }
-
-    getContext() {
-        return this.context;
-    }
-
-    updateContext(path: string | undefined = "") {
-        if (!path || path === "") {
-            path = window.location.pathname.replace(this.getBaseUrl(), "") || "/";
-        }
-        this.sendWsMessage("GMC", "get_module_context", { path: path || "/" });
-    }
-
-    trigger(actionName: string, triggerId: string, payload: Record<string, unknown> = {}) {
-        payload["action"] = actionName;
-        this.sendWsMessage("A", triggerId, payload);
-    }
-
-    upload(encodedName: string, files: FileList, progressCallback: (val: number) => void) {
-        return uploadFile(encodedName, undefined, undefined, undefined, files, progressCallback, this.clientId);
-    }
-
-    getPageMetadata() {
-        return this.metadata;
-    }
-
-    getWsStatus() {
-        return this._ackList;
-    }
-
-    getBaseUrl() {
-        return getBase();
-    }
-}
-
-export const createApp = (
-    onInit?: OnInitHandler,
-    onChange?: OnChangeHandler,
-    path?: string,
-    socket?: Socket,
-    handleCookie?: boolean,
-) => {
-    return new TaipyApp(onInit, onChange, path, socket, handleCookie);
-};

+ 0 - 57
frontend/taipy-gui/base/src/cookieHandler.ts

@@ -1,57 +0,0 @@
-import { Socket } from "socket.io-client";
-import { TaipyApp } from "./app";
-import { initSocket } from "./socket";
-import axios from "axios";
-import { getLocalStorageValue } from "../../src/context/utils";
-
-export const TAIPY_RESOURCE_HANDLER = "tprh";
-
-export class CookieHandler {
-    resourceHandlerId: string;
-    constructor() {
-        this.resourceHandlerId = getLocalStorageValue(TAIPY_RESOURCE_HANDLER, "");
-    }
-    async init(socket: Socket, taipyApp: TaipyApp) {
-        const hasValidCookies = await this.verifyCookieStatus();
-        if (!hasValidCookies) {
-            await this.deleteCookie();
-            localStorage.removeItem(TAIPY_RESOURCE_HANDLER);
-            window.location.reload();
-            return;
-        }
-        this.addBeforeUnloadListener();
-        initSocket(socket, taipyApp);
-    }
-
-    async verifyCookieStatus(): Promise<boolean> {
-        // check to see if local storage has the resource handler id (potentially having a cookie)
-        // If not, then some part of the code must have removed the cookie
-        // or wants to remove the cookie by removing the local storage
-        if (!this.resourceHandlerId) {
-            return new Promise((resolve) => resolve(false));
-        }
-        try {
-            // call to get cookie status
-            const { data } = await axios.get("taipy-rh");
-            // validate cookie status
-            if (data?.rh_id !== this.resourceHandlerId) {
-                return new Promise((resolve) => resolve(false));
-            }
-        } catch (error) {
-            console.error("Error while validating cookie:", error);
-            return new Promise((resolve) => resolve(false));
-        }
-        return new Promise((resolve) => resolve(true));
-    }
-
-    addBeforeUnloadListener() {
-        window.addEventListener("beforeunload", () => {
-            localStorage.removeItem(TAIPY_RESOURCE_HANDLER);
-            this.deleteCookie();
-        });
-    }
-
-    async deleteCookie() {
-        await axios.delete("taipy-rh");
-    }
-}

+ 0 - 179
frontend/taipy-gui/base/src/dataManager.ts

@@ -1,179 +0,0 @@
-export type ModuleData = Record<string, VarName>;
-
-export type VarName = Record<string, VarData>;
-
-export interface VarData {
-    type: string;
-    value: unknown;
-    encoded_name: string;
-    data_update: boolean;
-}
-
-type ColumnName = string;
-
-export type RequestDataOptions = {
-    columns?: Array<ColumnName>;
-    pagekey?: string;
-    alldata?: boolean;
-    start?: number;
-    end?: number;
-    filters?: Array<{ col: ColumnName; value: string | boolean | number; action: string }>;
-    aggregates?: Array<ColumnName>;
-    applies?: { [key: ColumnName]: string };
-    infinite?: boolean;
-    reverse?: boolean;
-    orderby?: ColumnName;
-    sort?: "asc" | "desc";
-    styles?: { [key: ColumnName]: string };
-    tooltips?: { [key: ColumnName]: string };
-    handlenan?: boolean;
-    compare_datas?: string;
-};
-
-type RequestDataEntry = {
-    options: RequestDataOptions;
-    receivedData: unknown;
-}
-
-export const getRequestedDataKey = (payload?: unknown) =>
-    (!!payload && typeof payload == "object" && "pagekey" in payload && (payload["pagekey"] as string)) || undefined;
-
-// This class hold the information of variables and real-time value of variables
-export class DataManager {
-    // key: encoded name, value: real-time value
-    _data: Record<string, unknown>;
-    // Initial data fetched from taipy-gui backend
-    _init_data: ModuleData;
-    // key: encodedName -> dataEventKey -> requeste data
-    _requested_data: Record<string, Record<string, RequestDataEntry>>;
-
-    constructor(variableModuleData: ModuleData) {
-        this._data = {};
-        this._init_data = {};
-        this._requested_data = {};
-        this.init(variableModuleData);
-    }
-
-    init(variableModuleData: ModuleData) {
-        // Identify changes between the new and old data
-        const changes: ModuleData = {};
-        for (const context in this._init_data) {
-            if (!(context in variableModuleData)) {
-                changes[context] = this._init_data[context];
-                continue;
-            }
-            for (const variable in this._init_data[context]) {
-                if (!(variable in variableModuleData[context])) {
-                    if (!(context in changes)) {
-                        changes[context] = {};
-                    }
-                    changes[context][variable] = this._init_data[context][variable];
-                }
-            }
-        }
-        if (Object.keys(changes).length !== 0) {
-            console.error("Unmatched data tree! Removed changes: ", changes);
-        }
-        // Reset the initial data
-        this._init_data = variableModuleData;
-        this._data = {};
-        for (const context in this._init_data) {
-            for (const variable in this._init_data[context]) {
-                const vData = this._init_data[context][variable];
-                this._data[vData["encoded_name"]] = vData.value;
-            }
-        }
-        return changes;
-    }
-
-    getEncodedName(varName: string, module: string): string | undefined {
-        if (module in this._init_data && varName in this._init_data[module]) {
-            return this._init_data[module][varName].encoded_name;
-        }
-        return undefined;
-    }
-
-    // return [name, moduleName]
-    getName(encodedName: string): [string, string] | undefined {
-        for (const context in this._init_data) {
-            for (const variable in this._init_data[context]) {
-                const vData = this._init_data[context][variable];
-                if (vData.encoded_name === encodedName) {
-                    return [variable, context];
-                }
-            }
-        }
-        return undefined;
-    }
-
-    get(encodedName: string, dataEventKey?: string): unknown {
-        // handle requested data
-        if (dataEventKey) {
-            if (!(encodedName in this._requested_data)) {
-                throw new Error(`Encoded name '${encodedName}' is not available in Taipy GUI`);
-            }
-            if (!(dataEventKey in this._requested_data[encodedName])) {
-                throw new Error(`Event key '${dataEventKey}' is not available for encoded name '${encodedName}' in Taipy GUI`);
-            }
-            return this._requested_data[encodedName][dataEventKey].receivedData;
-        }
-        // handle normal data
-        if (!(encodedName in this._data)) {
-            throw new Error(`${encodedName} is not available in Taipy GUI`);
-        }
-        return this._data[encodedName];
-    }
-
-    addRequestDataOptions(encodedName: string, dataEventKey: string, options: RequestDataOptions) {
-        if (!(encodedName in this._requested_data)) {
-            this._requested_data[encodedName] = {};
-        }
-        // This would overrides object with the same key
-        this._requested_data[encodedName][dataEventKey] = { options: options, receivedData: undefined };
-    }
-
-    getInfo(encodedName: string): VarData | undefined {
-        for (const context in this._init_data) {
-            for (const variable in this._init_data[context]) {
-                const vData = this._init_data[context][variable];
-                if (vData.encoded_name === encodedName) {
-                    return { ...vData, value: this._data[encodedName] };
-                }
-            }
-        }
-        return undefined;
-    }
-
-    getDataTree(): ModuleData {
-        return this._init_data;
-    }
-
-    getAllData(): Record<string, unknown> {
-        return this._data;
-    }
-
-    update(encodedName: string, value: unknown, dataEventKey?: string) {
-        // handle requested data
-        if (dataEventKey) {
-            if (!(encodedName in this._requested_data)) {
-                throw new Error(`Encoded name '${encodedName}' is not available in Taipy GUI`);
-            }
-            if (!(dataEventKey in this._requested_data[encodedName])) {
-                throw new Error(`Event key '${dataEventKey}' is not available for encoded name '${encodedName}' in Taipy GUI`);
-            }
-            this._requested_data[encodedName][dataEventKey].receivedData = value;
-            return;
-        }
-        // handle normal data
-        if (!(encodedName in this._data)) {
-            throw new Error(`${encodedName} is not available in Taipy Gui`);
-        }
-        this._data[encodedName] = value;
-    }
-
-    deleteRequestedData(encodedName: string, dataEventKey: string) {
-        if (encodedName in this._requested_data && dataEventKey in this._requested_data[encodedName]) {
-            delete this._requested_data[encodedName][dataEventKey];
-        }
-    }
-}

+ 0 - 7
frontend/taipy-gui/base/src/exports.ts

@@ -1,7 +0,0 @@
-import { TaipyApp, createApp, OnChangeHandler, OnInitHandler } from "./app";
-import { WsAdapter } from "./wsAdapter";
-import { ModuleData } from "./dataManager";
-
-export default TaipyApp;
-export { TaipyApp, createApp, WsAdapter };
-export type { OnChangeHandler, OnInitHandler, ModuleData };

+ 0 - 6
frontend/taipy-gui/base/src/index.ts

@@ -1,6 +0,0 @@
-import { TaipyApp, createApp, OnChangeHandler, OnInitHandler } from "./app";
-import { ModuleData } from "./dataManager";
-
-export default TaipyApp;
-export { TaipyApp, createApp };
-export type { OnChangeHandler, OnInitHandler, ModuleData };

+ 0 - 7
frontend/taipy-gui/base/src/packaging/package.json

@@ -1,7 +0,0 @@
-{
-  "name": "taipy-gui-base",
-  "version": "4.1.0",
-  "private": true,
-  "main": "./taipy-gui-base.js",
-  "types": "./taipy-gui-base.d.ts"
-}

+ 0 - 191
frontend/taipy-gui/base/src/packaging/taipy-gui-base.d.ts

@@ -1,191 +0,0 @@
-import { Socket } from "socket.io-client";
-
-export type ModuleData = Record<string, VarName>;
-export type VarName = Record<string, VarData>;
-export interface VarData {
-    type: string;
-    value: unknown;
-    encoded_name: string;
-    data_update: boolean;
-}
-export type ColumnName = string;
-export type RequestDataOptions = {
-    columns?: Array<ColumnName>;
-    pagekey?: string;
-    alldata?: boolean;
-    start?: number;
-    end?: number;
-    filters?: Array<{
-        col: ColumnName;
-        value: string | boolean | number;
-        action: string;
-    }>;
-    aggregates?: Array<ColumnName>;
-    applies?: {
-        [key: ColumnName]: string;
-    };
-    infinite?: boolean;
-    reverse?: boolean;
-    orderby?: ColumnName;
-    sort?: "asc" | "desc";
-    styles?: {
-        [key: ColumnName]: string;
-    };
-    tooltips?: {
-        [key: ColumnName]: string;
-    };
-    handlenan?: boolean;
-    compare_datas?: string;
-};
-export type RequestDataEntry = {
-    options: RequestDataOptions;
-    receivedData: unknown;
-};
-declare class DataManager {
-    _data: Record<string, unknown>;
-    _init_data: ModuleData;
-    _requested_data: Record<string, Record<string, RequestDataEntry>>;
-    constructor(variableModuleData: ModuleData);
-    init(variableModuleData: ModuleData): ModuleData;
-    getEncodedName(varName: string, module: string): string | undefined;
-    getName(encodedName: string): [string, string] | undefined;
-    get(encodedName: string, dataEventKey?: string): unknown;
-    addRequestDataOptions(encodedName: string, dataEventKey: string, options: RequestDataOptions): void;
-    getInfo(encodedName: string): VarData | undefined;
-    getDataTree(): ModuleData;
-    getAllData(): Record<string, unknown>;
-    update(encodedName: string, value: unknown, dataEventKey?: string): void;
-    deleteRequestedData(encodedName: string, dataEventKey: string): void;
-}
-export type WsMessageType =
-    | "A"
-    | "U"
-    | "DU"
-    | "MU"
-    | "RU"
-    | "AL"
-    | "BL"
-    | "NA"
-    | "ID"
-    | "MS"
-    | "DF"
-    | "PR"
-    | "ACK"
-    | "GMC"
-    | "GDT"
-    | "GA"
-    | "GR"
-    | "FV"
-    | "BC"
-    | "LS"
-export interface WsMessage {
-    type: WsMessageType | string;
-    name: string;
-    payload: Record<string, unknown> | unknown;
-    propagate: boolean;
-    client_id: string;
-    module_context: string;
-    ack_id?: string;
-}
-export declare abstract class WsAdapter {
-    abstract supportedMessageTypes: string[];
-    abstract handleWsMessage(message: WsMessage, app: TaipyApp): boolean;
-}
-declare class CookieHandler {
-    resourceHandlerId: string;
-    constructor();
-    init(socket: Socket, taipyApp: TaipyApp): Promise<void>;
-    verifyCookieStatus(): Promise<boolean>;
-    addBeforeUnloadListener(): void;
-    deleteCookie(): Promise<void>;
-}
-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 Route = [string, string];
-export type RequestDataCallback = (
-    taipyApp: TaipyApp,
-    encodedName: string,
-    dataEventKey: string,
-    value: unknown,
-) => void;
-export declare class TaipyApp {
-    socket: Socket;
-    _onInit: OnInitHandler | undefined;
-    _onChange: OnChangeHandler | undefined;
-    _onNotify: OnNotifyHandler | undefined;
-    _onReload: OnReloadHandler | undefined;
-    _onWsMessage: OnWsMessage | undefined;
-    _onWsStatusUpdate: OnWsStatusUpdate | undefined;
-    _ackList: string[];
-    _rdc: Record<string, Record<string, RequestDataCallback>>;
-    _cookieHandler: CookieHandler | undefined;
-    variableData: DataManager | undefined;
-    functionData: DataManager | undefined;
-    guiAddr: string;
-    clientId: string;
-    context: string;
-    metadata: Record<string, unknown>;
-    path: string | undefined;
-    routes: Route[] | undefined;
-    wsAdapters: WsAdapter[];
-    constructor(
-        onInit?: OnInitHandler | undefined,
-        onChange?: OnChangeHandler | undefined,
-        path?: string | undefined,
-        socket?: Socket | undefined,
-        handleCookie?: boolean,
-    );
-    get onInit(): OnInitHandler | undefined;
-    set onInit(handler: OnInitHandler | undefined);
-    onInitEvent(): void;
-    get onChange(): OnChangeHandler | undefined;
-    set onChange(handler: OnChangeHandler | undefined);
-    onChangeEvent(encodedName: string, value: unknown, dataEventKey?: string): void;
-    get onNotify(): OnNotifyHandler | undefined;
-    set onNotify(handler: OnNotifyHandler | undefined);
-    onNotifyEvent(type: string, message: string): void;
-    get onReload(): OnReloadHandler | undefined;
-    set onReload(handler: OnReloadHandler | undefined);
-    onReloadEvent(removedChanges: ModuleData): void;
-    get onWsMessage(): OnWsMessage | undefined;
-    set onWsMessage(handler: OnWsMessage | undefined);
-    onWsMessageEvent(event: string, payload: unknown): void;
-    get onWsStatusUpdate(): OnWsStatusUpdate | undefined;
-    set onWsStatusUpdate(handler: OnWsStatusUpdate | undefined);
-    onWsStatusUpdateEvent(messageQueue: string[]): void;
-    init(): void;
-    initApp(): void;
-    sendWsMessage(type: WsMessageType | string, id: string, payload: unknown, context?: string | undefined): void;
-    registerWsAdapter(wsAdapter: WsAdapter): void;
-    getEncodedName(varName: string, module: string): string | undefined;
-    getName(encodedName: string): [string, string] | undefined;
-    get(encodedName: string, dataEventKey?: string): unknown;
-    getInfo(encodedName: string): VarData | undefined;
-    getDataTree(): ModuleData | undefined;
-    getAllData(): Record<string, unknown> | undefined;
-    getFunctionList(): string[];
-    getRoutes(): Route[] | undefined;
-    deleteRequestedData(encodedName: string, dataEventKey: string): void;
-    update(encodedName: string, value: unknown): void;
-    requestData(encodedName: string, cb: RequestDataCallback, options?: RequestDataOptions): void;
-    getContext(): string;
-    updateContext(path?: string | undefined): void;
-    trigger(actionName: string, triggerId: string, payload?: Record<string, unknown>): void;
-    upload(encodedName: string, files: FileList, progressCallback: (val: number) => void): Promise<string>;
-    getPageMetadata(): Record<string, unknown>;
-    getWsStatus(): string[];
-    getBaseUrl(): string;
-}
-export declare const createApp: (
-    onInit?: OnInitHandler,
-    onChange?: OnChangeHandler,
-    path?: string,
-    socket?: Socket,
-    handleCookie?: boolean,
-) => TaipyApp;
-
-export { TaipyApp as default };

+ 0 - 51
frontend/taipy-gui/base/src/socket.ts

@@ -1,51 +0,0 @@
-import { Socket } from "socket.io-client";
-import { WsMessage } from "../../src/context/wsUtils";
-import { TaipyApp } from "./app";
-
-export const initSocket = (socket: Socket, taipyApp: TaipyApp) => {
-    socket.on("connect", () => {
-        taipyApp.onWsMessageEvent("connect", null);
-        if (taipyApp.clientId === "" || taipyApp.guiAddr === "") {
-            taipyApp.init();
-        }
-    });
-    // Send a request to get App ID to verify that the app has not been reloaded
-    socket.io.on("reconnect", () => {
-        taipyApp.onWsMessageEvent("reconnect", null);
-        console.log("WebSocket reconnected");
-        taipyApp.sendWsMessage("GA", "reconnect", taipyApp.guiAddr);
-    });
-    // try to reconnect on connect_error
-    socket.on("connect_error", (err) => {
-        taipyApp.onWsMessageEvent("connect_error", { err });
-        console.log("Error connecting WebSocket: ", err);
-        setTimeout(() => {
-            socket && socket.connect();
-        }, 500);
-    });
-    // try to reconnect on server disconnection
-    socket.on("disconnect", (reason, details) => {
-        taipyApp.onWsMessageEvent("disconnect", { reason, details });
-        console.log("WebSocket disconnected due to: ", reason, details);
-        if (reason === "io server disconnect") {
-            socket && socket.connect();
-        }
-    });
-    // handle message data from backend
-    socket.on("message", (message: WsMessage) => {
-        taipyApp.onWsMessageEvent("message", message);
-        // handle messages with registered websocket adapters
-        for (const adapter of taipyApp.wsAdapters) {
-            if (adapter.supportedMessageTypes.includes(message.type)) {
-                const messageResolved = adapter.handleWsMessage(message, taipyApp);
-                if (messageResolved) {
-                    return;
-                }
-            }
-        }
-    });
-    // only now does the socket tries to open/connect
-    if (!socket.connected) {
-        socket.connect();
-    }
-};

+ 0 - 3
frontend/taipy-gui/base/src/utils.ts

@@ -1,3 +0,0 @@
-export const getBase = () => {
-    return document.getElementsByTagName("base")[0].getAttribute("href") || "/";
-};

+ 0 - 124
frontend/taipy-gui/base/src/wsAdapter.ts

@@ -1,124 +0,0 @@
-import merge from "lodash/merge";
-import { TaipyApp } from "./app";
-import { IdMessage, storeClientId } from "../../src/context/utils";
-import { WsMessage } from "../../src/context/wsUtils";
-import { DataManager, getRequestedDataKey, ModuleData } from "./dataManager";
-
-export abstract class WsAdapter {
-    abstract supportedMessageTypes: string[];
-
-    abstract handleWsMessage(message: WsMessage, app: TaipyApp): boolean;
-}
-
-interface MultipleUpdatePayload {
-    name: string;
-    payload: { value: unknown };
-}
-
-interface NotificationMessage extends WsMessage {
-    nType: string;
-    message: string;
-}
-
-export class TaipyWsAdapter extends WsAdapter {
-    supportedMessageTypes: string[];
-    initWsMessageTypes: string[];
-    constructor() {
-        super();
-        this.supportedMessageTypes = ["MU", "ID", "GMC", "GDT", "GA", "GR", "AL", "ACK"];
-        this.initWsMessageTypes = ["ID", "GA", "GMC"];
-    }
-    handleWsMessage(message: WsMessage, taipyApp: TaipyApp): boolean {
-        if (message.type) {
-            if (message.type === "MU" && Array.isArray(message.payload)) {
-                for (const muPayload of message.payload as [MultipleUpdatePayload]) {
-                    const encodedName = muPayload.name;
-                    const { value } = muPayload.payload;
-                    if (value && (value as any).__taipy_refresh !== undefined) {
-                        // refresh all requested data for this encodedName var
-                        const requestDataOptions = taipyApp.variableData?._requested_data[encodedName];
-                        for (const dataKey in requestDataOptions) {
-                            const requestDataEntry = requestDataOptions[dataKey];
-                            const { options } = requestDataEntry;
-                            taipyApp.sendWsMessage("DU", encodedName, options);
-                        }
-                        return true;
-                    }
-                    const dataKey = getRequestedDataKey(muPayload.payload);
-                    taipyApp.variableData?.update(encodedName, value, dataKey);
-                    // call the callback if it exists for request data
-                    if (dataKey && (encodedName in taipyApp._rdc && dataKey in taipyApp._rdc[encodedName])) {
-                        const cb = taipyApp._rdc[encodedName]?.[dataKey];
-                        cb(taipyApp, encodedName, dataKey, value);
-                        delete taipyApp._rdc[encodedName][dataKey];
-                    }
-                    taipyApp.onChangeEvent(encodedName, value, dataKey);
-                }
-            } else if (message.type === "ID") {
-                const { id } = message as unknown as IdMessage;
-                storeClientId(id);
-                taipyApp.clientId = id;
-                taipyApp.initApp();
-                taipyApp.updateContext(taipyApp.path);
-            } else if (message.type === "GMC") {
-                const payload = message.payload as Record<string, unknown>;
-                taipyApp.context = payload.context as string;
-                if (payload?.metadata) {
-                    try {
-                        taipyApp.metadata = JSON.parse((payload.metadata as string) || "{}");
-                    } catch (e) {
-                        console.error("Error parsing metadata from Taipy Designer", e);
-                    }
-                }
-            } else if (message.type === "GDT") {
-                const payload = message.payload as Record<string, ModuleData>;
-                const variableData = payload.variable;
-                const functionData = payload.function;
-                if (taipyApp.variableData && taipyApp.functionData) {
-                    const varChanges = taipyApp.variableData.init(variableData);
-                    const functionChanges = taipyApp.functionData.init(functionData);
-                    const changes = merge(varChanges, functionChanges);
-                    if (varChanges || functionChanges) {
-                        taipyApp.onReloadEvent(changes);
-                    }
-                } else {
-                    taipyApp.variableData = new DataManager(variableData);
-                    taipyApp.functionData = new DataManager(functionData);
-                    taipyApp.onInitEvent();
-                }
-            } else if (message.type === "GA") {
-                const payload = message.payload as Record<string, unknown>;
-                if (payload.name === "reconnect") {
-                    taipyApp.init();
-                    return true;
-                }
-                taipyApp.guiAddr = payload.id as string;
-            } else if (message.type === "GR") {
-                const payload = message.payload as [string, string][];
-                taipyApp.routes = payload;
-            } else if (message.type === "AL") {
-                const payload = message as NotificationMessage;
-                taipyApp.onNotifyEvent(payload.nType, payload.message);
-            } else if (message.type === "ACK") {
-                const { id } = message as unknown as Record<string, string>;
-                taipyApp._ackList = taipyApp._ackList.filter((v) => v !== id);
-                taipyApp.onWsStatusUpdateEvent(taipyApp._ackList);
-            }
-            this.postWsMessageProcessing(message, taipyApp);
-            return true;
-        }
-        return false;
-    }
-    postWsMessageProcessing(message: WsMessage, taipyApp: TaipyApp) {
-        // perform data population only when all necessary metadata is ready
-        if (
-            this.initWsMessageTypes.includes(message.type) &&
-            taipyApp.clientId !== "" &&
-            taipyApp.guiAddr !== "" &&
-            taipyApp.context !== "" &&
-            taipyApp.routes !== undefined
-        ) {
-            taipyApp.sendWsMessage("GDT", "get_data_tree", {});
-        }
-    }
-}

+ 0 - 28
frontend/taipy-gui/base/tsconfig.json

@@ -1,28 +0,0 @@
-{
-    "compilerOptions": {
-      "target": "es2015",
-      "lib": [
-        "dom",
-        "dom.iterable",
-        "esnext"
-      ],
-      "outDir": "./dist/",
-      "sourceMap": true,
-      "allowJs": true,
-      "skipLibCheck": true,
-      "esModuleInterop": true,
-      "allowSyntheticDefaultImports": true,
-      "strict": true,
-      "forceConsistentCasingInFileNames": true,
-      "noFallthroughCasesInSwitch": true,
-      "module": "esnext",
-      "moduleResolution": "node",
-      "resolveJsonModule": true,
-      "isolatedModules": true,
-      "noEmit": false,
-      "jsx": "react-jsx",
-    },
-    "include": [
-      "src"
-    ]
-  }

+ 0 - 88
frontend/taipy-gui/base/webpack.config.js

@@ -1,88 +0,0 @@
-const path = require("path");
-const webpack = require("webpack");
-const CopyWebpackPlugin = require("copy-webpack-plugin");
-const resolveApp = (relativePath) => path.resolve(__dirname, relativePath);
-
-const moduleName = "TaipyGuiBase";
-const basePath = "../../../taipy/gui/webapp";
-const webAppPath = resolveApp(basePath);
-const taipyGuiBaseExportPath = resolveApp(basePath + "/taipy-gui-base-export");
-
-module.exports = [
-    {
-        target: "web",
-        entry: {
-            default: "./base/src/index.ts",
-        },
-        output: {
-            filename: (arg) => {
-                if (arg.chunk.name === "default") {
-                    return "taipy-gui-base.js";
-                }
-                return "[name].taipy-gui-base.js";
-            },
-            chunkFilename: "[name].taipy-gui-base.js",
-            path: webAppPath,
-            globalObject: "this",
-            library: {
-                name: moduleName,
-                type: "umd",
-            },
-        },
-        optimization: {
-            splitChunks: {
-                chunks: "all",
-                name: "shared",
-            },
-        },
-        module: {
-            rules: [
-                {
-                    test: /\.tsx?$/,
-                    use: "ts-loader",
-                    exclude: /node_modules/,
-                },
-            ],
-        },
-        resolve: {
-            extensions: [".tsx", ".ts", ".js", ".tsx"],
-        },
-        // externals: {
-        //     "socket.io-client": {
-        //         commonjs: "socket.io-client",
-        //         commonjs2: "socket.io-client",
-        //         amd: "socket.io-client",
-        //         root: "_",
-        //     },
-        // },
-    },
-    {
-        entry: "./base/src/exports.ts",
-        output: {
-            filename: "taipy-gui-base.js",
-            path: taipyGuiBaseExportPath,
-            library: {
-                name: moduleName,
-                type: "umd",
-            },
-            publicPath: "",
-        },
-        module: {
-            rules: [
-                {
-                    test: /\.tsx?$/,
-                    use: "ts-loader",
-                    exclude: /node_modules/,
-                },
-            ],
-        },
-        resolve: {
-            extensions: [".tsx", ".ts", ".js", ".tsx"],
-        },
-        plugins: [
-            new CopyWebpackPlugin({
-                patterns: [{ from: "./base/src/packaging", to: taipyGuiBaseExportPath }],
-            }),
-        ],
-    },
-];

+ 1 - 0
frontend/taipy-gui/dom/package-lock.json

@@ -14,6 +14,7 @@
       }
     },
     "../packaging": {
+      "name": "taipy-gui",
       "version": "4.1.0"
     },
     "node_modules/js-tokens": {

+ 0 - 2
frontend/taipy-gui/package.json

@@ -41,13 +41,11 @@
     "start": "echo no start see python",
     "build:dev": "webpack --mode development",
     "build": "webpack --mode production",
-    "build-base": "webpack --mode production --config ./base/webpack.config.js",
     "test": "cross-env TZ=UTC jest",
     "lint": "eslint --ext .ts,.tsx",
     "lint:fix": "npm run lint -- --fix",
     "coverage": "npm test -- --coverage",
     "types": "dts-bundle-generator -o packaging/taipy-gui.gen.d.ts src/extensions/exports.ts",
-    "types-base": "dts-bundle-generator -o base/src/packaging/taipy-gui-base.gen.d.ts base/src/exports.ts",
     "doc": "typedoc --plugin typedoc-plugin-markdown --excludeNotDocumented --disableSources src/extensions/exports.ts",
     "doc.json": "typedoc --json docs/taipy-gui.json src/extensions/exports.ts",
     "mkdocs": "typedoc --options typedoc-mkdocs.json"

+ 433 - 241
frontend/taipy-gui/packaging/taipy-gui.d.ts

@@ -11,8 +11,40 @@
  * specific language governing permissions and limitations under the License.
  */
 
-import * as React from "react";
+import * as React from "react"
 
+import { PaletteMode } from "@mui/material";
+import { Theme } from "@mui/material/styles";
+import { ComponentType, Dispatch, ReactNode } from "react";
+import { Socket } from "socket.io-client";
+
+export interface TaipyBaseProps {
+    id?: string;
+    libClassName?: string;
+    className?: string;
+    dynamicClassName?: string;
+    privateClassName?: string;
+    children?: ReactNode;
+}
+export interface TaipyDynamicProps extends TaipyBaseProps {
+    updateVarName?: string;
+    propagate?: boolean;
+    updateVars?: string;
+}
+export interface TaipyHoverProps {
+    hoverText?: string;
+    defaultHoverText?: string;
+}
+export interface TaipyActiveProps extends TaipyDynamicProps, TaipyHoverProps {
+    defaultActive?: boolean;
+    active?: boolean;
+}
+export interface TaipyMultiSelectProps {
+    selected?: number[];
+}
+export interface TaipyChangeProps {
+    onChange?: string;
+}
 /**
  * Extracts the backend name of a property.
  *
@@ -21,7 +53,6 @@ import * as React from "react";
  * @returns The backend-generated variable name.
  */
 export declare const getUpdateVar: (updateVars: string, name: string) => string | undefined;
-
 /**
  * Appends a suffix to the class names.
  *
@@ -30,165 +61,6 @@ export declare const getUpdateVar: (updateVars: string, name: string) => string
  * @returns The new list of class names.
  */
 export declare const getSuffixedClassNames: (names: string | undefined, suffix: string) => string;
-
-export interface TaipyActiveProps extends TaipyDynamicProps, TaipyHoverProps {
-    defaultActive?: boolean;
-    active?: boolean;
-}
-export interface TaipyHoverProps {
-    hoverText?: string;
-    defaultHoverText?: string;
-}
-export interface TaipyDynamicProps extends TaipyBaseProps {
-    updateVarName?: string;
-    propagate?: boolean;
-    updateVars?: string;
-}
-export interface TaipyBaseProps {
-    id?: string;
-    libClassName?: string;
-    className?: string;
-    dynamicClassName?: string;
-}
-export interface DialogProps extends TaipyActiveProps {
-    title: string;
-    onAction?: string;
-    closeLabel?: string;
-    labels?: string;
-    page?: string;
-    partial?: boolean;
-    open?: boolean;
-    defaultOpen?: string | boolean;
-    children?: React.ReactNode;
-    height?: string | number;
-    width?: string | number;
-    localAction?: (idx: number) => void;
-}
-export declare const Dialog: (props: DialogProps) => JSX.Element;
-
-export interface ChartProp extends TaipyActiveProps, TaipyChangeProps {
-    title?: string;
-    width?: string | number;
-    height?: string | number;
-    defaultConfig: string;
-    config?: string;
-    data?: Record<string, TraceValueType>;
-    defaultLayout?: string;
-    layout?: string;
-    plotConfig?: string;
-    onRangeChange?: string;
-    testId?: string;
-    render?: boolean;
-    defaultRender?: boolean;
-    template?: string;
-    template_Dark_?: string;
-    template_Light_?: string;
-}
-export type TraceValueType = Record<string, (string | number)[]>;
-export declare const Chart: (props: ChartProp) => JSX.Element;
-
-export interface TaipyMultiSelectProps {
-    selected?: number[];
-}
-export interface TaipyChangeProps {
-    onChange?: string;
-}
-
-export type TableValueType = Record<string, Record<string, any>>;
-export interface TaipyTableProps extends TaipyActiveProps, TaipyMultiSelectProps {
-    data?: TableValueType;
-    columns?: string;
-    defaultColumns: string;
-    height?: string;
-    width?: string;
-    pageSize?: number;
-    onEdit?: string;
-    onDelete?: string;
-    onAdd?: string;
-    onAction?: string;
-    editable?: boolean;
-    defaultEditable?: boolean;
-    lineStyle?: string;
-    tooltip?: string;
-    cellTooltip?: string;
-    nanValue?: string;
-    filter?: boolean;
-    size?: "small" | "medium";
-    userData?: unknown;
-}
-export interface TaipyPaginatedTableProps extends TaipyTableProps {
-    pageSizeOptions?: string;
-    allowAllRows?: boolean;
-    showAll?: boolean;
-}
-export interface TableProps extends TaipyPaginatedTableProps {
-    autoLoading?: boolean;
-}
-export declare const Table: (props: TableProps) => JSX.Element;
-
-export interface FilterColumnDesc extends ColumnDesc {
-    params?: number[];
-}
-export interface FilterDesc {
-    col: string;
-    action: string;
-    value: string | number | boolean | Date;
-    type: string;
-    params?: number[];
-}
-export interface TableFilterProps {
-    fieldHeader?: string;
-    fieldHeaderTooltip?: string;
-    columns: Record<string, FilterColumnDesc>;
-    colsOrder?: Array<string>;
-    onValidate: (data: Array<FilterDesc>) => void;
-    appliedFilters?: Array<FilterDesc>;
-    className?: string;
-    filteredCount: number;
-}
-export declare const TableFilter: (props: TableFilterProps) => JSX.Element;
-
-export interface SortColumnDesc extends ColumnDesc {
-    params?: number[];
-}
-export interface SortDesc {
-    col: string;
-    order: boolean;
-    params?: number[];
-}
-
-export interface TableSortProps {
-    fieldHeader?: string;
-    fieldHeaderTooltip?: string;
-    columns: Record<string, SortColumnDesc>;
-    colsOrder?: Array<string>;
-    onValidate: (data: Array<SortDesc>) => void;
-    appliedSorts?: Array<SortDesc>;
-    className?: string;
-}
-
-export declare const TableSort: (props: TableSortProps) => JSX.Element;
-
-export interface FileSelectorProps extends TaipyActiveProps {
-    onAction?: string;
-    defaultLabel?: string;
-    label?: string;
-    multiple?: boolean;
-    selectionType?: string;
-    extensions?: string;
-    dropMessage?: string;
-    notify?: boolean;
-    width?: string | number;
-    icon?: React.ReactNode;
-    withBorder?: boolean;
-    onUploadAction?: string;
-    uploadData?: string;
-}
-
-export declare const FileSelector: (props: FileSelectorProps) => JSX.Element;
-
-export declare const Router: () => JSX.Element;
-
 /**
  * An Icon representation.
  */
@@ -205,7 +77,7 @@ export interface Icon {
 /**
  * A string or an icon.
  */
-export declare type stringIcon = string | Icon;
+export type stringIcon = string | Icon;
 /**
  * An item in a List of Values (LoV).
  */
@@ -217,43 +89,140 @@ export interface LovItem {
     /** The array of child items. */
     children?: LovItem[];
 }
-/**
- * A LoV (list of value) element.
- *
- * Each `LoVElt` holds:
- *
- * - Its identifier as a string;
- * - Its label (or icon) as a `stringIcon`;
- * - Potential child elements as an array of `LoVElt`s.
- */
-export declare type LoVElt = [
-    /** The identifier. */
-    string,
-    /** The label or icon. */
-    stringIcon,
-    /** The list of children. */
-    LoVElt[]?
-];
-/**
- * A series of LoV elements.
- */
-export declare type LoV = LoVElt[];
-/**
- * A React hook that returns a LoV list from the LoV default value and the LoV bound value.
- * @param lov - The bound lov value.
- * @param defaultLov - The JSON-stringified default LoV value.
- * @param tree - This flag indicates if the LoV list is a tree or a flat list (default is false).
- * @returns A list of LoV items.
- */
-export declare const useLovListMemo: (lov: LoV | undefined, defaultLov: string, tree?: boolean) => LovItem[];
-/**
- * The state of the underlying Taipy application.
- */
-export interface State {}
-/**
- * Application actions as used by the application reducer.
- */
-export interface Action {}
+export interface MenuProps extends TaipyBaseProps {
+    label?: string;
+    width?: string;
+    onAction?: string;
+    inactiveIds?: string[];
+    lov?: LovItem[];
+    active?: boolean;
+    selected?: string[];
+}
+export declare const getLocalStorageValue: <T = string>(key: string, defaultValue: T, values?: T[]) => T;
+export declare const storeClientId: (id: string) => void;
+export interface IdMessage {
+    id: string;
+}
+export declare const TAIPY_CLIENT_ID = "TaipyClientId";
+export type WsMessageType =
+    | "A"
+    | "U"
+    | "DU"
+    | "MU"
+    | "RU"
+    | "AL"
+    | "BL"
+    | "NA"
+    | "ID"
+    | "MS"
+    | "DF"
+    | "PR"
+    | "ACK"
+    | "GA"
+    | "FV"
+    | "BC"
+    | "LS";
+export interface WsMessage {
+    type: WsMessageType;
+    name: string;
+    payload: Record<string, unknown> | unknown;
+    propagate: boolean;
+    client_id: string;
+    module_context: string;
+    ack_id?: string;
+}
+export declare const sendWsMessage: (
+    socket: Socket | undefined,
+    type: WsMessageType,
+    name: string,
+    payload: Record<string, unknown> | unknown,
+    id: string,
+    moduleContext?: string,
+    propagate?: boolean,
+    serverAck?: (val: unknown) => void
+) => string;
+declare enum Types {
+    SocketConnected = "SOCKET_CONNECTED",
+    Update = "UPDATE",
+    MultipleUpdate = "MULTIPLE_UPDATE",
+    SendUpdate = "SEND_UPDATE_ACTION",
+    Action = "SEND_ACTION_ACTION",
+    RequestDataUpdate = "REQUEST_DATA_UPDATE",
+    RequestUpdate = "REQUEST_UPDATE",
+    SetLocations = "SET_LOCATIONS",
+    SetTheme = "SET_THEME",
+    SetTimeZone = "SET_TIMEZONE",
+    SetNotification = "SET_NOTIFICATION",
+    DeleteNotification = "DELETE_NOTIFICATION",
+    SetBlock = "SET_BLOCK",
+    Navigate = "NAVIGATE",
+    ClientId = "CLIENT_ID",
+    MultipleMessages = "MULTIPLE_MESSAGES",
+    SetMenu = "SET_MENU",
+    DownloadFile = "DOWNLOAD_FILE",
+    Partial = "PARTIAL",
+    Acknowledgement = "ACKNOWLEDGEMENT",
+    Broadcast = "BROADCAST",
+    LocalStorage = "LOCAL_STORAGE",
+    RefreshThemes = "REFRESH_THEMES",
+}
+interface TaipyState {
+    socket?: Socket;
+    isSocketConnected?: boolean;
+    data: Record<string, unknown>;
+    themes: Record<PaletteMode, Theme>;
+    theme: Theme;
+    locations: Record<string, string>;
+    timeZone?: string;
+    dateFormat?: string;
+    dateTimeFormat?: string;
+    numberFormat?: string;
+    notifications: NotificationMessage[];
+    block?: BlockMessage;
+    navigateTo?: string;
+    navigateParams?: Record<string, string>;
+    navigateTab?: string;
+    navigateForce?: boolean;
+    id: string;
+    menu: MenuProps;
+    download?: FileDownloadProps;
+    ackList: string[];
+}
+interface TaipyBaseAction {
+    type: Types;
+}
+export interface NamePayload {
+    name: string;
+    payload: Record<string, unknown>;
+}
+export interface NotificationMessage {
+    nType: string;
+    message: string;
+    system: boolean;
+    duration: number;
+    notificationId?: string;
+    snackbarId: string;
+}
+export interface TaipyAction extends NamePayload, TaipyBaseAction {
+    propagate?: boolean;
+    context?: string;
+}
+export interface BlockMessage {
+    action: string;
+    noCancel: boolean;
+    close: boolean;
+    message: string;
+}
+export interface FileDownloadProps {
+    content?: string;
+    name?: string;
+    onAction?: string;
+    context?: string;
+}
+export declare const INITIAL_STATE: TaipyState;
+export declare const taipyInitialize: (initialState: TaipyState) => TaipyState;
+export declare const initializeWebSocket: (socket: Socket | undefined, dispatch: Dispatch<TaipyBaseAction>) => void;
+export declare const taipyReducer: (state: TaipyState, baseAction: TaipyBaseAction) => TaipyState;
 /**
  * Create a *send update* `Action` that will be used to update `Context`.
  *
@@ -262,7 +231,7 @@ export interface Action {}
  * @param name - The name of the variable holding the requested data
  *    as received as a property.
  * @param value - The new value for the variable named *name*.
- * @param context - The execution context (property `context`).
+ * @param context - The execution context.
  * @param onChange - The name of the `on_change` Python function to
  *   invoke on the backend (default is "on_change").
  * @param propagate - A flag indicating that the variable should be
@@ -278,14 +247,14 @@ export declare const createSendUpdateAction: (
     onChange?: string,
     propagate?: boolean,
     relName?: string
-) => Action;
+) => TaipyAction;
 /**
  * Create an *action* `Action` that will be used to update `Context`.
  *
  * This action will trigger the invocation of the `on_action` Python function on the backend,
  * providing all the parameters as a payload.
  * @param name - The name of the action function on the backend.
- * @param context - The execution context (property `context`).
+ * @param context - The execution context.
  * @param value - The value associated with the action. This can be an object or
  *   any type of value.
  * @param args - Additional information associated to the action.
@@ -296,17 +265,17 @@ export declare const createSendActionNameAction: (
     context: string | undefined,
     value: unknown,
     ...args: unknown[]
-) => Action;
+) => TaipyAction;
 /**
  * Create a *request data update* `Action` that will be used to update the `Context`.
  *
  * This action will provoke the invocation of the `get_data()` method of the backend
  * library. That invocation generates an update of the elements holding the data named
- * *name* on the frontend.
+ * *name* on the front-end.
  * @param name - The name of the variable holding the requested data as received as
  *   a property.
  * @param id - The identifier of the visual element.
- * @param context - The execution context (property `context`).
+ * @param context - The execution context.
  * @param columns - The list of the columns needed by the element that emitted this
  *   action.
  * @param pageKey - The unique identifier of the data that will be received from
@@ -326,16 +295,16 @@ export declare const createRequestDataUpdateAction: (
     payload: Record<string, unknown>,
     allData?: boolean,
     library?: string
-) => Action;
+) => TaipyAction;
 /**
  * Create a *request update* `Action` that will be used to update the `Context`.
  *
  * This action will generate an update of the elements holding the variables named
  * *names* on the front-end.
  * @param id - The identifier of the visual element.
- * @param context - The execution context (property `context`).
+ * @param context - The execution context.
  * @param names - The names of the requested variables as received in updateVarName and/or updateVars properties.
- * @param forceRefresh - Should Taipy re-evaluate the variables or use the current values.
+ * @param forceRefresh - Should Taipy re-evaluate the variables or use the current values
  * @returns The action fed to the reducer.
  */
 export declare const createRequestUpdateAction: (
@@ -344,8 +313,8 @@ export declare const createRequestUpdateAction: (
     names: string[],
     forceRefresh?: boolean,
     stateContext?: Record<string, unknown>
-) => Action;
-
+) => TaipyAction;
+export declare const createRefreshThemesAction: () => TaipyBaseAction;
 /**
  * A column description as received by the backend.
  */
@@ -383,6 +352,7 @@ export interface ColumnDesc {
     apply?: string;
     /** The flag that allows the user to aggregate the column. */
     groupBy?: boolean;
+    widthHint?: number;
     /** The list of values that can be used on edit. */
     lov?: string[];
     /** If true the user can enter any value besides the lov values. */
@@ -393,34 +363,232 @@ export interface ColumnDesc {
     headers?: string[];
     /** The index of the multi index if exists. */
     multi?: number;
-    /** If true or not set, line breaks are transformed into <BR>. */
-    lineBreak?: boolean;
 }
 /**
  * A cell value type.
  */
-export declare type RowValue = string | number | boolean | null;
+export type RowValue = string | number | boolean | null;
 /**
  * The definition of a table row.
  *
  * A row definition associates a name (a string) to a type (a {@link RowValue}).
  */
-export declare type RowType = Record<string, RowValue>;
+export type RowType = Record<string, RowValue>;
+export type TableValueType = Record<string, Record<string, any>>;
+export interface TaipyTableProps extends TaipyActiveProps, TaipyMultiSelectProps {
+    data?: TableValueType;
+    columns?: string;
+    defaultColumns: string;
+    height?: string;
+    width?: string;
+    pageSize?: number;
+    onEdit?: string;
+    onDelete?: string;
+    onAdd?: string;
+    onAction?: string;
+    editable?: boolean;
+    defaultEditable?: boolean;
+    rowClassName?: string;
+    tooltip?: string;
+    cellTooltip?: string;
+    nanValue?: string;
+    filter?: boolean;
+    size?: "small" | "medium";
+    defaultKey?: string;
+    userData?: unknown;
+    downloadable?: boolean;
+    onCompare?: string;
+    compare?: boolean;
+    useCheckbox?: boolean;
+    sortable?: boolean;
+}
+export interface TaipyPaginatedTableProps extends TaipyTableProps {
+    pageSizeOptions?: string;
+    allowAllRows?: boolean;
+    showAll?: boolean;
+}
+export interface FilterDesc {
+    col: string;
+    action: string;
+    value: string | number | boolean | Date;
+    type: string;
+    matchcase?: boolean;
+    params?: number[];
+}
+export interface ChartProp extends TaipyActiveProps, TaipyChangeProps {
+    title?: string;
+    width?: string | number;
+    height?: string | number;
+    defaultConfig: string;
+    config?: string;
+    data?: Record<string, TraceValueType>;
+    defaultLayout?: string;
+    layout?: string;
+    plotConfig?: string;
+    onRangeChange?: string;
+    render?: boolean;
+    defaultRender?: boolean;
+    template?: string;
+    template_Dark_?: string;
+    template_Light_?: string;
+    figure?: Array<Record<string, unknown>>;
+    onClick?: string;
+    dataVarNames?: string;
+}
+export type TraceValueType = Record<string, (string | number)[]>;
+export declare const Chart: (props: ChartProp) => React.JSX.Element | null;
+export interface DialogProps extends TaipyActiveProps {
+    title: string;
+    onAction?: string;
+    closeLabel?: string;
+    labels?: string;
+    page?: string;
+    partial?: boolean;
+    open?: boolean;
+    defaultOpen?: string | boolean;
+    children?: ReactNode;
+    height?: string | number;
+    width?: string | number;
+    localAction?: (idx: number) => void;
+    refId?: string;
+    defaultRefId?: string;
+    popup?: boolean;
+}
+export declare const Dialog: (props: DialogProps) => React.JSX.Element;
+export interface FileSelectorProps extends TaipyActiveProps {
+    onAction?: string;
+    defaultLabel?: string;
+    label?: string;
+    multiple?: boolean;
+    selectionType?: string;
+    extensions?: string;
+    dropMessage?: string;
+    notify?: boolean;
+    width?: string | number;
+    icon?: ReactNode;
+    withBorder?: boolean;
+    onUploadAction?: string;
+    uploadData?: string;
+}
+export declare const FileSelector: (props: FileSelectorProps) => React.JSX.Element;
+export interface LoginProps extends TaipyBaseProps {
+    title?: string;
+    onAction?: string;
+    defaultMessage?: string;
+    message?: string;
+    labels?: string;
+}
+export declare const Login: (props: LoginProps) => React.JSX.Element | null;
+export declare const Router: () => React.JSX.Element;
+export interface TableProps extends TaipyPaginatedTableProps {
+    autoLoading?: boolean;
+}
+export declare const Table: ({ autoLoading, ...rest }: TableProps) => React.JSX.Element;
+export interface FilterColumnDesc extends ColumnDesc {
+    params?: number[];
+}
+export interface TableFilterProps {
+    fieldHeader?: string;
+    fieldHeaderTooltip?: string;
+    columns: Record<string, FilterColumnDesc>;
+    colsOrder?: Array<string>;
+    onValidate: (data: Array<FilterDesc>) => void;
+    appliedFilters?: Array<FilterDesc>;
+    className?: string;
+    filteredCount: number;
+}
+export declare const TableFilter: (props: TableFilterProps) => React.JSX.Element;
+export interface SortDesc {
+    col: string;
+    order: boolean;
+    params?: number[];
+}
+export interface SortColumnDesc extends ColumnDesc {
+    params?: number[];
+}
+export interface TableSortProps {
+    fieldHeader?: string;
+    fieldHeaderTooltip?: string;
+    columns: Record<string, SortColumnDesc>;
+    colsOrder?: Array<string>;
+    onValidate: (data: Array<SortDesc>) => void;
+    appliedSorts?: Array<SortDesc>;
+    className?: string;
+}
+export declare const TableSort: (props: TableSortProps) => React.JSX.Element;
+/**
+ * A function that retrieves the dynamic className associated
+ * to an instance of component through the style property
+ *
+ * @param children - The react children of the component
+ * @returns The associated className.
+ */
+export declare const getComponentClassName: (children: ReactNode) => string;
+export interface MetricProps extends TaipyBaseProps, TaipyHoverProps {
+    value?: number;
+    defaultValue?: number;
+    delta?: number;
+    defaultDelta?: number;
+    type?: string;
+    min?: number;
+    max?: number;
+    deltaColor?: string;
+    negativeDeltaColor?: string;
+    threshold?: number;
+    defaultThreshold?: number;
+    format?: string;
+    deltaFormat?: string;
+    barColor?: string;
+    showValue?: boolean;
+    colorMap?: string;
+    title?: string;
+    layout?: string;
+    defaultLayout?: string;
+    width?: string | number;
+    height?: string | number;
+    template?: string;
+    template_Dark_?: string;
+    template_Light_?: string;
+}
+export declare const Metric: (props: MetricProps) => React.JSX.Element;
+/**
+ * A LoV (list of value) element.
+ *
+ * Each `LoVElt` holds:
+ *
+ * - Its identifier as a string;
+ * - Its label (or icon) as a `stringIcon`;
+ * - Potential child elements as an array of `LoVElt`s.
+ */
+export type LoVElt = [string, stringIcon, LoVElt[]?];
 /**
- * The Taipy Store.
+ * A series of LoV elements.
+ */
+export type LoV = LoVElt[];
+/**
+ * A React hook that returns a LoV list from the LoV default value and the LoV bound value.
+ * @param lov - The bound lov value.
+ * @param defaultLov - The JSON-stringified default LoV value.
+ * @param tree - This flag indicates if the LoV list is a tree or a flat list (default is false).
+ * @returns A list of LoV items.
  */
-export interface Store {
+export declare const useLovListMemo: (lov: LoV | undefined, defaultLov: string, tree?: boolean) => LovItem[];
+interface TaipyStore {
     /** The State of the Taipy application. */
-    state: State;
+    state: TaipyState;
     /** The React *dispatch* function. */
-    dispatch: React.Dispatch<Action>;
+    dispatch: Dispatch<TaipyBaseAction>;
 }
 /**
  * The Taipy-specific React context.
  *
  * The type of this variable is `React.Context<Store>`.
  */
-export declare const Context: React.Context<Store>;
+export declare const TaipyContext: React.Context<TaipyStore>;
+export interface PageStore {
+    module?: string;
+}
+export declare const PageContext: React.Context<PageStore>;
 /**
  * A React hook to manage a dynamic scalar property.
  *
@@ -431,39 +599,39 @@ export declare const Context: React.Context<Store>;
  * @param defaultStatic - The default static value.
  * @returns The latest updated value.
  */
-export declare const useDynamicProperty: <T>(value: T, defaultValue: T, defaultStatic: T) => T;
-/**
- * A React hook to manage classNames (dynamic and static).
- * cf. useDynamicProperty
- *
- * @param libClassName - The default static className.
- * @param dynamicClassName - The bound className.
- * @param className - The default user set className.
- * @returns The complete list of applicable classNames.
- */
-export declare const useClassNames: (libClassName?: string, dynamicClassName?: string, className?: string) => string;
+export declare const useDynamicProperty: <T>(
+    value: T,
+    defaultValue: T,
+    defaultStatic: T,
+    checkType?: string,
+    nullToDefault?: boolean
+) => T;
 /**
  * A React hook to manage a dynamic json property.
  *
- * A dynamic scalar property  is defined by a default property and a bound property.
+ * A dynamic json property  is defined by a default property and a bound property.
  * @typeParam T - The dynamic property type.
  * @param value - The bound value.
  * @param defaultValue - The default value.
  * @param defaultStatic - The default static value.
  * @returns The latest updated value.
  */
-export const useDynamicJsonProperty: <T>(value: string | T, defaultValue: string, defaultStatic: T) => T;
+export declare const useDynamicJsonProperty: <T>(
+    value: string | undefined,
+    defaultValue: string,
+    defaultStatic: T
+) => T;
 /**
  * A React hook that requests an update for every dynamic property of the element.
  * @param dispatch - The React dispatcher associated to `TaipyContext`.
  * @param id - The identifier of the element.
- * @param context - The execution context (property `context`).
+ * @param context - The execution context.
  * @param updateVars - The content of the property `updateVars`.
  * @param varName - The default property backend provided variable (through property `updateVarName`).
  * @param forceRefresh - Should Taipy re-evaluate the variables or use the current values.
  */
 export declare const useDispatchRequestUpdateOnFirstRender: (
-    dispatch: React.Dispatch<Action>,
+    dispatch: Dispatch<TaipyBaseAction>,
     id?: string,
     context?: string,
     updateVars?: string,
@@ -477,7 +645,7 @@ export declare const useDispatchRequestUpdateOnFirstRender: (
  * communications.
  * @returns The *dispatch* function.
  */
-export declare const useDispatch: () => React.Dispatch<Action>;
+export declare const useDispatch: () => Dispatch<TaipyBaseAction>;
 /**
  * A React hook that returns the page module.
  *
@@ -485,12 +653,36 @@ export declare const useDispatch: () => React.Dispatch<Action>;
  * @returns The page module.
  */
 export declare const useModule: () => string | undefined;
-
 /**
- * A function that retrieves the dynamic className associated
- * to an instance of component through the style property
+ * A React hook to manage classNames (dynamic and static).
+ * cf. useDynamicProperty
  *
- * @param children - The react children of the component
- * @returns The associated className.
+ * @param libClassName - The default static className.
+ * @param dynamicClassName - The bound className.
+ * @param className - The default user set className.
+ * @returns The complete list of applicable classNames.
  */
-export declare const getComponentClassName: (children: React.ReactNode) => string;
+export declare const useClassNames: (libClassName?: string, dynamicClassName?: string, className?: string) => string;
+export declare const uploadFile: (
+    varName: string,
+    context: string | undefined,
+    onAction: string | undefined,
+    uploadData: string | undefined,
+    files: FileList,
+    progressCallback: (val: number) => void,
+    id: string,
+    uploadUrl?: string
+) => Promise<string>;
+export declare const emptyArray: never[];
+export interface ErrorFallBackProps {
+    error: Error;
+    resetErrorBoundary: () => void;
+}
+export declare const ErrorFallback: (props: ErrorFallBackProps) => React.JSX.Element;
+export declare const getRegisteredComponents: () => Record<string, ComponentType<object>>;
+export declare const unregisteredRender: (tagName?: string, error?: string) => React.JSX.Element;
+export declare const renderError: (props: { error: string }) => React.JSX.Element;
+
+export { TaipyBaseAction as Action, TaipyContext as Context, TaipyState as State, TaipyStore as Store };
+
+export {};

+ 22 - 4
frontend/taipy-gui/src/context/taipyReducers.ts

@@ -49,6 +49,7 @@ export enum Types {
     Acknowledgement = "ACKNOWLEDGEMENT",
     Broadcast = "BROADCAST",
     LocalStorage = "LOCAL_STORAGE",
+    RefreshThemes = "REFRESH_THEMES",
 }
 
 /**
@@ -58,6 +59,7 @@ export interface TaipyState {
     socket?: Socket;
     isSocketConnected?: boolean;
     data: Record<string, unknown>;
+    themes: Record<PaletteMode, Theme>;
     theme: Theme;
     locations: Record<string, string>;
     timeZone?: string;
@@ -167,8 +169,8 @@ export interface FormatConfig {
 }
 
 const getUserTheme = (mode: PaletteMode) => {
-    const tkTheme = (window.taipyConfig?.stylekit && stylekitTheme) || {};
-    const tkModeTheme = (window.taipyConfig?.stylekit && stylekitModeThemes[mode]) || {};
+    const tkTheme = (window.taipyConfig?.stylekit && stylekitTheme()) || {};
+    const tkModeTheme = (window.taipyConfig?.stylekit && stylekitModeThemes()[mode]) || {};
     const userTheme = window.taipyConfig?.themes?.base || {};
     const modeTheme = (window.taipyConfig?.themes && window.taipyConfig.themes[mode]) || {};
     return createTheme(
@@ -183,7 +185,7 @@ const getUserTheme = (mode: PaletteMode) => {
                     },
                 },
             },
-        })
+        }),
     );
 };
 
@@ -194,6 +196,7 @@ const themes = {
 
 export const INITIAL_STATE: TaipyState = {
     data: {},
+    themes: themes,
     theme: window.taipyConfig?.darkMode ? themes.dark : themes.light,
     locations: {},
     timeZone: window.taipyConfig?.timeZone
@@ -471,11 +474,22 @@ export const taipyReducer = (state: TaipyState, baseAction: TaipyBaseAction): Ta
             if (mode !== state.theme.palette.mode) {
                 return {
                     ...state,
-                    theme: themes[mode],
+                    theme: state.themes[mode],
                 };
             }
             return state;
         }
+        case Types.RefreshThemes: {
+            const tempThemes = {
+                light: getUserTheme("light"),
+                dark: getUserTheme("dark"),
+            };
+            return {
+                ...state,
+                themes: tempThemes,
+                theme: tempThemes[state.theme.palette.mode]
+            };
+        }
         case Types.SetTimeZone: {
             let timeZone = (action.payload.timeZone as string) || "client";
             if (!action.payload.fromBackend) {
@@ -929,3 +943,7 @@ export const createLocalStorageAction = (localStorageData: Record<string, string
     name: "",
     payload: localStorageData,
 });
+
+export const createRefreshThemesAction = (): TaipyBaseAction => ({
+    type: Types.RefreshThemes,
+});

+ 0 - 3
frontend/taipy-gui/src/context/wsUtils.ts

@@ -18,10 +18,7 @@ export type WsMessageType =
     | "DF"
     | "PR"
     | "ACK"
-    | "GMC"
-    | "GDT"
     | "GA"
-    | "GR"
     | "FV"
     | "BC"
     | "LS";

+ 41 - 1
frontend/taipy-gui/src/extensions/exports.ts

@@ -26,7 +26,7 @@ import { useLovListMemo, LoV, LoVElt } from "../components/Taipy/lovUtils";
 import { LovItem } from "../utils/lov";
 import { getUpdateVar, getSuffixedClassNames } from "../components/Taipy/utils";
 import { ColumnDesc, RowType, RowValue } from "../components/Taipy/tableUtils";
-import { TaipyContext, TaipyStore } from "../context/taipyContext";
+import { TaipyContext, TaipyStore, PageContext } from "../context/taipyContext";
 import { TaipyBaseAction, TaipyState } from "../context/taipyReducers";
 import {
     useClassNames,
@@ -85,3 +85,43 @@ export type {
     TaipyState as State,
     TaipyBaseAction as Action,
 };
+
+// For Taipy Custom Package (Designer)
+
+import { sendWsMessage, TAIPY_CLIENT_ID } from "../context/wsUtils";
+import { uploadFile } from "../workers/fileupload";
+import { WsMessage, WsMessageType } from "../context/wsUtils";
+import { IdMessage, storeClientId, getLocalStorageValue } from "../context/utils";
+
+import { emptyArray } from "../utils";
+import ErrorFallback from "../utils/ErrorBoundary";
+import { getRegisteredComponents } from "../components/Taipy";
+import { renderError, unregisteredRender } from "../components/Taipy/Unregistered";
+import {
+    createRefreshThemesAction,
+    INITIAL_STATE,
+    initializeWebSocket,
+    taipyInitialize,
+    taipyReducer,
+} from "../context/taipyReducers";
+
+export {
+    getLocalStorageValue,
+    sendWsMessage,
+    TAIPY_CLIENT_ID,
+    uploadFile,
+    storeClientId,
+    PageContext,
+    TaipyContext,
+    emptyArray,
+    ErrorFallback,
+    getRegisteredComponents,
+    renderError,
+    unregisteredRender,
+    createRefreshThemesAction,
+    INITIAL_STATE,
+    initializeWebSocket,
+    taipyInitialize,
+    taipyReducer,
+};
+export type { WsMessage, WsMessageType, IdMessage };

+ 4 - 4
frontend/taipy-gui/src/themes/stylekit.ts

@@ -13,7 +13,7 @@
 
 import { lighten } from "@mui/material";
 
-export const stylekitTheme = {
+export const stylekitTheme = () => ({
     palette: {
         // Primary and secondary colors
         primary: {
@@ -165,9 +165,9 @@ export const stylekitTheme = {
             },
         },
     },
-};
+});
 
-export const stylekitModeThemes = {
+export const stylekitModeThemes = () => ({
     light: {
         palette: {
             background: {
@@ -232,4 +232,4 @@ export const stylekitModeThemes = {
             },
         },
     },
-};
+});

+ 5 - 86
frontend/taipy-gui/webpack.config.js

@@ -25,16 +25,14 @@ const resolveApp = relativePath => path.resolve(__dirname, relativePath);
 const reactBundle = "taipy-gui-deps";
 const taipyBundle = "taipy-gui";
 
-const reactBundleName = "TaipyGuiDependencies";
-const taipyBundleName = "TaipyGui";
-const taipyGuiBaseBundleName = "TaipyGuiBase";
+const reactBundleName = "TaipyGuiDependencies"
+const taipyBundleName = "TaipyGui"
 
 const basePath = "../../taipy/gui/webapp";
 const webAppPath = resolveApp(basePath);
 const reactManifestPath = resolveApp(basePath + "/" + reactBundle + "-manifest.json");
-const reactDllPath = resolveApp(basePath + "/" + reactBundle + ".dll.js");
-const taipyDllPath = resolveApp(basePath + "/" + taipyBundle + ".js");
-const taipyGuiBaseExportPath = resolveApp(basePath + "/taipy-gui-base-export");
+const reactDllPath = resolveApp(basePath + "/" + reactBundle + ".dll.js")
+const taipyDllPath = resolveApp(basePath + "/" + taipyBundle + ".js")
 
 module.exports = (env, options) => {
     const envVariables = {
@@ -169,84 +167,5 @@ module.exports = (env, options) => {
                     hash: true,
                 }]),
             ],
-        },
-        {
-            mode: options.mode,
-            target: "web",
-            entry: {
-                "default": "./base/src/index.ts",
-            },
-            output: {
-                filename: (arg) => {
-                    if (arg.chunk.name === "default") {
-                        return "taipy-gui-base.js";
-                    }
-                    return "[name].taipy-gui-base.js";
-                },
-                chunkFilename: "[name].taipy-gui-base.js",
-                path: webAppPath,
-                globalObject: "this",
-                library: {
-                    name: taipyGuiBaseBundleName,
-                    type: "umd",
-                },
-            },
-            optimization: {
-                splitChunks: {
-                    chunks: "all",
-                    name: "shared",
-                },
-            },
-            module: {
-                rules: [
-                    {
-                        test: /\.tsx?$/,
-                        use: "ts-loader",
-                        exclude: /node_modules/,
-                    },
-                ],
-            },
-            resolve: {
-                extensions: [".tsx", ".ts", ".js", ".tsx"],
-            },
-            // externals: {
-            //     "socket.io-client": {
-            //         commonjs: "socket.io-client",
-            //         commonjs2: "socket.io-client",
-            //         amd: "socket.io-client",
-            //         root: "_",
-            //     },
-            // },
-        },
-        {
-            entry: "./base/src/exports.ts",
-            output: {
-                filename: "taipy-gui-base.js",
-                path: taipyGuiBaseExportPath,
-                library: {
-                    name: taipyGuiBaseBundleName,
-                    type: "umd",
-                },
-                publicPath: "",
-            },
-            module: {
-                rules: [
-                    {
-                        test: /\.tsx?$/,
-                        use: "ts-loader",
-                        exclude: /node_modules/,
-                    },
-                ],
-            },
-            resolve: {
-                extensions: [".tsx", ".ts", ".js", ".tsx"],
-            },
-            plugins: [
-                new CopyWebpackPlugin({
-                    patterns: [
-                        { from: "./base/src/packaging", to: taipyGuiBaseExportPath },
-                    ],
-                }),
-            ],
-        }];
+    }];
 };

+ 1 - 1
frontend/taipy/package-lock.json

@@ -54,7 +54,7 @@
     },
     "../../taipy/gui/webapp": {
       "name": "taipy-gui",
-      "version": "4.2.0"
+      "version": "4.1.0"
     },
     "node_modules/@adobe/css-tools": {
       "version": "4.4.2",

+ 0 - 1461
package-lock.json

@@ -1,1461 +0,0 @@
-{
-  "name": "taipy",
-  "lockfileVersion": 3,
-  "requires": true,
-  "packages": {
-    "": {
-      "dependencies": {
-        "@emotion/react": "^11.13.3",
-        "@emotion/styled": "^11.13.0",
-        "@mui/material": "^6.1.6",
-        "@textea/json-viewer": "^4.0.0",
-        "react-json-tree": "^0.19.0",
-        "react-json-view": "^1.21.3"
-      }
-    },
-    "node_modules/@babel/code-frame": {
-      "version": "7.26.2",
-      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
-      "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
-      "license": "MIT",
-      "dependencies": {
-        "@babel/helper-validator-identifier": "^7.25.9",
-        "js-tokens": "^4.0.0",
-        "picocolors": "^1.0.0"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@babel/generator": {
-      "version": "7.26.2",
-      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz",
-      "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==",
-      "license": "MIT",
-      "dependencies": {
-        "@babel/parser": "^7.26.2",
-        "@babel/types": "^7.26.0",
-        "@jridgewell/gen-mapping": "^0.3.5",
-        "@jridgewell/trace-mapping": "^0.3.25",
-        "jsesc": "^3.0.2"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@babel/helper-module-imports": {
-      "version": "7.25.9",
-      "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz",
-      "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==",
-      "license": "MIT",
-      "dependencies": {
-        "@babel/traverse": "^7.25.9",
-        "@babel/types": "^7.25.9"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@babel/helper-string-parser": {
-      "version": "7.25.9",
-      "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
-      "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@babel/helper-validator-identifier": {
-      "version": "7.25.9",
-      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
-      "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@babel/parser": {
-      "version": "7.26.2",
-      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz",
-      "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==",
-      "license": "MIT",
-      "dependencies": {
-        "@babel/types": "^7.26.0"
-      },
-      "bin": {
-        "parser": "bin/babel-parser.js"
-      },
-      "engines": {
-        "node": ">=6.0.0"
-      }
-    },
-    "node_modules/@babel/runtime": {
-      "version": "7.26.0",
-      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz",
-      "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==",
-      "license": "MIT",
-      "dependencies": {
-        "regenerator-runtime": "^0.14.0"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@babel/template": {
-      "version": "7.25.9",
-      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz",
-      "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==",
-      "license": "MIT",
-      "dependencies": {
-        "@babel/code-frame": "^7.25.9",
-        "@babel/parser": "^7.25.9",
-        "@babel/types": "^7.25.9"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@babel/traverse": {
-      "version": "7.25.9",
-      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz",
-      "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==",
-      "license": "MIT",
-      "dependencies": {
-        "@babel/code-frame": "^7.25.9",
-        "@babel/generator": "^7.25.9",
-        "@babel/parser": "^7.25.9",
-        "@babel/template": "^7.25.9",
-        "@babel/types": "^7.25.9",
-        "debug": "^4.3.1",
-        "globals": "^11.1.0"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@babel/types": {
-      "version": "7.26.0",
-      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz",
-      "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==",
-      "license": "MIT",
-      "dependencies": {
-        "@babel/helper-string-parser": "^7.25.9",
-        "@babel/helper-validator-identifier": "^7.25.9"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@emotion/babel-plugin": {
-      "version": "11.12.0",
-      "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.12.0.tgz",
-      "integrity": "sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw==",
-      "license": "MIT",
-      "dependencies": {
-        "@babel/helper-module-imports": "^7.16.7",
-        "@babel/runtime": "^7.18.3",
-        "@emotion/hash": "^0.9.2",
-        "@emotion/memoize": "^0.9.0",
-        "@emotion/serialize": "^1.2.0",
-        "babel-plugin-macros": "^3.1.0",
-        "convert-source-map": "^1.5.0",
-        "escape-string-regexp": "^4.0.0",
-        "find-root": "^1.1.0",
-        "source-map": "^0.5.7",
-        "stylis": "4.2.0"
-      }
-    },
-    "node_modules/@emotion/cache": {
-      "version": "11.13.1",
-      "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.13.1.tgz",
-      "integrity": "sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw==",
-      "license": "MIT",
-      "dependencies": {
-        "@emotion/memoize": "^0.9.0",
-        "@emotion/sheet": "^1.4.0",
-        "@emotion/utils": "^1.4.0",
-        "@emotion/weak-memoize": "^0.4.0",
-        "stylis": "4.2.0"
-      }
-    },
-    "node_modules/@emotion/hash": {
-      "version": "0.9.2",
-      "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz",
-      "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==",
-      "license": "MIT"
-    },
-    "node_modules/@emotion/is-prop-valid": {
-      "version": "1.3.1",
-      "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz",
-      "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==",
-      "license": "MIT",
-      "dependencies": {
-        "@emotion/memoize": "^0.9.0"
-      }
-    },
-    "node_modules/@emotion/memoize": {
-      "version": "0.9.0",
-      "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz",
-      "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==",
-      "license": "MIT"
-    },
-    "node_modules/@emotion/react": {
-      "version": "11.13.3",
-      "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.13.3.tgz",
-      "integrity": "sha512-lIsdU6JNrmYfJ5EbUCf4xW1ovy5wKQ2CkPRM4xogziOxH1nXxBSjpC9YqbFAP7circxMfYp+6x676BqWcEiixg==",
-      "license": "MIT",
-      "dependencies": {
-        "@babel/runtime": "^7.18.3",
-        "@emotion/babel-plugin": "^11.12.0",
-        "@emotion/cache": "^11.13.0",
-        "@emotion/serialize": "^1.3.1",
-        "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0",
-        "@emotion/utils": "^1.4.0",
-        "@emotion/weak-memoize": "^0.4.0",
-        "hoist-non-react-statics": "^3.3.1"
-      },
-      "peerDependencies": {
-        "react": ">=16.8.0"
-      },
-      "peerDependenciesMeta": {
-        "@types/react": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/@emotion/serialize": {
-      "version": "1.3.2",
-      "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.2.tgz",
-      "integrity": "sha512-grVnMvVPK9yUVE6rkKfAJlYZgo0cu3l9iMC77V7DW6E1DUIrU68pSEXRmFZFOFB1QFo57TncmOcvcbMDWsL4yA==",
-      "license": "MIT",
-      "dependencies": {
-        "@emotion/hash": "^0.9.2",
-        "@emotion/memoize": "^0.9.0",
-        "@emotion/unitless": "^0.10.0",
-        "@emotion/utils": "^1.4.1",
-        "csstype": "^3.0.2"
-      }
-    },
-    "node_modules/@emotion/sheet": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz",
-      "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==",
-      "license": "MIT"
-    },
-    "node_modules/@emotion/styled": {
-      "version": "11.13.0",
-      "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.13.0.tgz",
-      "integrity": "sha512-tkzkY7nQhW/zC4hztlwucpT8QEZ6eUzpXDRhww/Eej4tFfO0FxQYWRyg/c5CCXa4d/f174kqeXYjuQRnhzf6dA==",
-      "license": "MIT",
-      "dependencies": {
-        "@babel/runtime": "^7.18.3",
-        "@emotion/babel-plugin": "^11.12.0",
-        "@emotion/is-prop-valid": "^1.3.0",
-        "@emotion/serialize": "^1.3.0",
-        "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0",
-        "@emotion/utils": "^1.4.0"
-      },
-      "peerDependencies": {
-        "@emotion/react": "^11.0.0-rc.0",
-        "react": ">=16.8.0"
-      },
-      "peerDependenciesMeta": {
-        "@types/react": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/@emotion/unitless": {
-      "version": "0.10.0",
-      "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz",
-      "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==",
-      "license": "MIT"
-    },
-    "node_modules/@emotion/use-insertion-effect-with-fallbacks": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.1.0.tgz",
-      "integrity": "sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw==",
-      "license": "MIT",
-      "peerDependencies": {
-        "react": ">=16.8.0"
-      }
-    },
-    "node_modules/@emotion/utils": {
-      "version": "1.4.1",
-      "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.1.tgz",
-      "integrity": "sha512-BymCXzCG3r72VKJxaYVwOXATqXIZ85cuvg0YOUDxMGNrKc1DJRZk8MgV5wyXRyEayIMd4FuXJIUgTBXvDNW5cA==",
-      "license": "MIT"
-    },
-    "node_modules/@emotion/weak-memoize": {
-      "version": "0.4.0",
-      "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz",
-      "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==",
-      "license": "MIT"
-    },
-    "node_modules/@jridgewell/gen-mapping": {
-      "version": "0.3.5",
-      "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
-      "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
-      "license": "MIT",
-      "dependencies": {
-        "@jridgewell/set-array": "^1.2.1",
-        "@jridgewell/sourcemap-codec": "^1.4.10",
-        "@jridgewell/trace-mapping": "^0.3.24"
-      },
-      "engines": {
-        "node": ">=6.0.0"
-      }
-    },
-    "node_modules/@jridgewell/resolve-uri": {
-      "version": "3.1.2",
-      "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
-      "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=6.0.0"
-      }
-    },
-    "node_modules/@jridgewell/set-array": {
-      "version": "1.2.1",
-      "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
-      "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=6.0.0"
-      }
-    },
-    "node_modules/@jridgewell/sourcemap-codec": {
-      "version": "1.5.0",
-      "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
-      "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
-      "license": "MIT"
-    },
-    "node_modules/@jridgewell/trace-mapping": {
-      "version": "0.3.25",
-      "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
-      "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
-      "license": "MIT",
-      "dependencies": {
-        "@jridgewell/resolve-uri": "^3.1.0",
-        "@jridgewell/sourcemap-codec": "^1.4.14"
-      }
-    },
-    "node_modules/@mui/core-downloads-tracker": {
-      "version": "6.1.6",
-      "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.1.6.tgz",
-      "integrity": "sha512-nz1SlR9TdBYYPz4qKoNasMPRiGb4PaIHFkzLzhju0YVYS5QSuFF2+n7CsiHMIDcHv3piPu/xDWI53ruhOqvZwQ==",
-      "license": "MIT",
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/mui-org"
-      }
-    },
-    "node_modules/@mui/material": {
-      "version": "6.1.6",
-      "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.1.6.tgz",
-      "integrity": "sha512-1yvejiQ/601l5AK3uIdUlAVElyCxoqKnl7QA+2oFB/2qYPWfRwDgavW/MoywS5Y2gZEslcJKhe0s2F3IthgFgw==",
-      "license": "MIT",
-      "dependencies": {
-        "@babel/runtime": "^7.26.0",
-        "@mui/core-downloads-tracker": "^6.1.6",
-        "@mui/system": "^6.1.6",
-        "@mui/types": "^7.2.19",
-        "@mui/utils": "^6.1.6",
-        "@popperjs/core": "^2.11.8",
-        "@types/react-transition-group": "^4.4.11",
-        "clsx": "^2.1.1",
-        "csstype": "^3.1.3",
-        "prop-types": "^15.8.1",
-        "react-is": "^18.3.1",
-        "react-transition-group": "^4.4.5"
-      },
-      "engines": {
-        "node": ">=14.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/mui-org"
-      },
-      "peerDependencies": {
-        "@emotion/react": "^11.5.0",
-        "@emotion/styled": "^11.3.0",
-        "@mui/material-pigment-css": "^6.1.6",
-        "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
-        "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
-        "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
-      },
-      "peerDependenciesMeta": {
-        "@emotion/react": {
-          "optional": true
-        },
-        "@emotion/styled": {
-          "optional": true
-        },
-        "@mui/material-pigment-css": {
-          "optional": true
-        },
-        "@types/react": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/@mui/private-theming": {
-      "version": "6.1.6",
-      "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.1.6.tgz",
-      "integrity": "sha512-ioAiFckaD/fJSnTrUMWgjl9HYBWt7ixCh7zZw7gDZ+Tae7NuprNV6QJK95EidDT7K0GetR2rU3kAeIR61Myttw==",
-      "license": "MIT",
-      "dependencies": {
-        "@babel/runtime": "^7.26.0",
-        "@mui/utils": "^6.1.6",
-        "prop-types": "^15.8.1"
-      },
-      "engines": {
-        "node": ">=14.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/mui-org"
-      },
-      "peerDependencies": {
-        "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
-        "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
-      },
-      "peerDependenciesMeta": {
-        "@types/react": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/@mui/styled-engine": {
-      "version": "6.1.6",
-      "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.1.6.tgz",
-      "integrity": "sha512-I+yS1cSuSvHnZDBO7e7VHxTWpj+R7XlSZvTC4lS/OIbUNJOMMSd3UDP6V2sfwzAdmdDNBi7NGCRv2SZ6O9hGDA==",
-      "license": "MIT",
-      "dependencies": {
-        "@babel/runtime": "^7.26.0",
-        "@emotion/cache": "^11.13.1",
-        "@emotion/serialize": "^1.3.2",
-        "@emotion/sheet": "^1.4.0",
-        "csstype": "^3.1.3",
-        "prop-types": "^15.8.1"
-      },
-      "engines": {
-        "node": ">=14.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/mui-org"
-      },
-      "peerDependencies": {
-        "@emotion/react": "^11.4.1",
-        "@emotion/styled": "^11.3.0",
-        "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
-      },
-      "peerDependenciesMeta": {
-        "@emotion/react": {
-          "optional": true
-        },
-        "@emotion/styled": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/@mui/system": {
-      "version": "6.1.6",
-      "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.1.6.tgz",
-      "integrity": "sha512-qOf1VUE9wK8syiB0BBCp82oNBAVPYdj4Trh+G1s+L+ImYiKlubWhhqlnvWt3xqMevR+D2h1CXzA1vhX2FvA+VQ==",
-      "license": "MIT",
-      "dependencies": {
-        "@babel/runtime": "^7.26.0",
-        "@mui/private-theming": "^6.1.6",
-        "@mui/styled-engine": "^6.1.6",
-        "@mui/types": "^7.2.19",
-        "@mui/utils": "^6.1.6",
-        "clsx": "^2.1.1",
-        "csstype": "^3.1.3",
-        "prop-types": "^15.8.1"
-      },
-      "engines": {
-        "node": ">=14.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/mui-org"
-      },
-      "peerDependencies": {
-        "@emotion/react": "^11.5.0",
-        "@emotion/styled": "^11.3.0",
-        "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
-        "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
-      },
-      "peerDependenciesMeta": {
-        "@emotion/react": {
-          "optional": true
-        },
-        "@emotion/styled": {
-          "optional": true
-        },
-        "@types/react": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/@mui/types": {
-      "version": "7.2.19",
-      "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.19.tgz",
-      "integrity": "sha512-6XpZEM/Q3epK9RN8ENoXuygnqUQxE+siN/6rGRi2iwJPgBUR25mphYQ9ZI87plGh58YoZ5pp40bFvKYOCDJ3tA==",
-      "license": "MIT",
-      "peerDependencies": {
-        "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0"
-      },
-      "peerDependenciesMeta": {
-        "@types/react": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/@mui/utils": {
-      "version": "6.1.6",
-      "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.6.tgz",
-      "integrity": "sha512-sBS6D9mJECtELASLM+18WUcXF6RH3zNxBRFeyCRg8wad6NbyNrdxLuwK+Ikvc38sTZwBzAz691HmSofLqHd9sQ==",
-      "license": "MIT",
-      "dependencies": {
-        "@babel/runtime": "^7.26.0",
-        "@mui/types": "^7.2.19",
-        "@types/prop-types": "^15.7.13",
-        "clsx": "^2.1.1",
-        "prop-types": "^15.8.1",
-        "react-is": "^18.3.1"
-      },
-      "engines": {
-        "node": ">=14.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/mui-org"
-      },
-      "peerDependencies": {
-        "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
-        "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
-      },
-      "peerDependenciesMeta": {
-        "@types/react": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/@popperjs/core": {
-      "version": "2.11.8",
-      "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
-      "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
-      "license": "MIT",
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/popperjs"
-      }
-    },
-    "node_modules/@textea/json-viewer": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/@textea/json-viewer/-/json-viewer-4.0.0.tgz",
-      "integrity": "sha512-TOrvWJ3E1Qg8pRu2eTdMSYJl/JwaYtMGD6EIzYsaicFIA0jICZa/WH4D7/0anH5At8eYfubPTfjjBqnair5WIA==",
-      "license": "MIT",
-      "dependencies": {
-        "clsx": "^2.1.1",
-        "copy-to-clipboard": "^3.3.3",
-        "zustand": "^4.5.5"
-      },
-      "peerDependencies": {
-        "@emotion/react": "^11",
-        "@emotion/styled": "^11",
-        "@mui/material": "^6",
-        "react": "^17 || ^18",
-        "react-dom": "^17 || ^18"
-      }
-    },
-    "node_modules/@types/lodash": {
-      "version": "4.17.13",
-      "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.13.tgz",
-      "integrity": "sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==",
-      "license": "MIT"
-    },
-    "node_modules/@types/parse-json": {
-      "version": "4.0.2",
-      "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
-      "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==",
-      "license": "MIT"
-    },
-    "node_modules/@types/prop-types": {
-      "version": "15.7.13",
-      "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz",
-      "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==",
-      "license": "MIT"
-    },
-    "node_modules/@types/react": {
-      "version": "18.3.12",
-      "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz",
-      "integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==",
-      "license": "MIT",
-      "dependencies": {
-        "@types/prop-types": "*",
-        "csstype": "^3.0.2"
-      }
-    },
-    "node_modules/@types/react-transition-group": {
-      "version": "4.4.11",
-      "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.11.tgz",
-      "integrity": "sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==",
-      "license": "MIT",
-      "dependencies": {
-        "@types/react": "*"
-      }
-    },
-    "node_modules/asap": {
-      "version": "2.0.6",
-      "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
-      "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
-      "license": "MIT"
-    },
-    "node_modules/babel-plugin-macros": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
-      "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==",
-      "license": "MIT",
-      "dependencies": {
-        "@babel/runtime": "^7.12.5",
-        "cosmiconfig": "^7.0.0",
-        "resolve": "^1.19.0"
-      },
-      "engines": {
-        "node": ">=10",
-        "npm": ">=6"
-      }
-    },
-    "node_modules/base16": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz",
-      "integrity": "sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ==",
-      "license": "MIT"
-    },
-    "node_modules/callsites": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
-      "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=6"
-      }
-    },
-    "node_modules/clsx": {
-      "version": "2.1.1",
-      "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
-      "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=6"
-      }
-    },
-    "node_modules/color": {
-      "version": "4.2.3",
-      "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
-      "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
-      "license": "MIT",
-      "dependencies": {
-        "color-convert": "^2.0.1",
-        "color-string": "^1.9.0"
-      },
-      "engines": {
-        "node": ">=12.5.0"
-      }
-    },
-    "node_modules/color-convert": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-      "license": "MIT",
-      "dependencies": {
-        "color-name": "~1.1.4"
-      },
-      "engines": {
-        "node": ">=7.0.0"
-      }
-    },
-    "node_modules/color-name": {
-      "version": "1.1.4",
-      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
-      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
-      "license": "MIT"
-    },
-    "node_modules/color-string": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
-      "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
-      "license": "MIT",
-      "dependencies": {
-        "color-name": "^1.0.0",
-        "simple-swizzle": "^0.2.2"
-      }
-    },
-    "node_modules/convert-source-map": {
-      "version": "1.9.0",
-      "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
-      "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
-      "license": "MIT"
-    },
-    "node_modules/copy-to-clipboard": {
-      "version": "3.3.3",
-      "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz",
-      "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==",
-      "license": "MIT",
-      "dependencies": {
-        "toggle-selection": "^1.0.6"
-      }
-    },
-    "node_modules/cosmiconfig": {
-      "version": "7.1.0",
-      "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
-      "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
-      "license": "MIT",
-      "dependencies": {
-        "@types/parse-json": "^4.0.0",
-        "import-fresh": "^3.2.1",
-        "parse-json": "^5.0.0",
-        "path-type": "^4.0.0",
-        "yaml": "^1.10.0"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/cross-fetch": {
-      "version": "3.1.8",
-      "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz",
-      "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==",
-      "license": "MIT",
-      "dependencies": {
-        "node-fetch": "^2.6.12"
-      }
-    },
-    "node_modules/csstype": {
-      "version": "3.1.3",
-      "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
-      "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
-      "license": "MIT"
-    },
-    "node_modules/debug": {
-      "version": "4.3.7",
-      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
-      "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
-      "license": "MIT",
-      "dependencies": {
-        "ms": "^2.1.3"
-      },
-      "engines": {
-        "node": ">=6.0"
-      },
-      "peerDependenciesMeta": {
-        "supports-color": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/dom-helpers": {
-      "version": "5.2.1",
-      "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
-      "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
-      "license": "MIT",
-      "dependencies": {
-        "@babel/runtime": "^7.8.7",
-        "csstype": "^3.0.2"
-      }
-    },
-    "node_modules/error-ex": {
-      "version": "1.3.2",
-      "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
-      "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
-      "license": "MIT",
-      "dependencies": {
-        "is-arrayish": "^0.2.1"
-      }
-    },
-    "node_modules/escape-string-regexp": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
-      "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/fbemitter": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/fbemitter/-/fbemitter-3.0.0.tgz",
-      "integrity": "sha512-KWKaceCwKQU0+HPoop6gn4eOHk50bBv/VxjJtGMfwmJt3D29JpN4H4eisCtIPA+a8GVBam+ldMMpMjJUvpDyHw==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "fbjs": "^3.0.0"
-      }
-    },
-    "node_modules/fbjs": {
-      "version": "3.0.5",
-      "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-3.0.5.tgz",
-      "integrity": "sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg==",
-      "license": "MIT",
-      "dependencies": {
-        "cross-fetch": "^3.1.5",
-        "fbjs-css-vars": "^1.0.0",
-        "loose-envify": "^1.0.0",
-        "object-assign": "^4.1.0",
-        "promise": "^7.1.1",
-        "setimmediate": "^1.0.5",
-        "ua-parser-js": "^1.0.35"
-      }
-    },
-    "node_modules/fbjs-css-vars": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz",
-      "integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==",
-      "license": "MIT"
-    },
-    "node_modules/find-root": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
-      "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==",
-      "license": "MIT"
-    },
-    "node_modules/flux": {
-      "version": "4.0.4",
-      "resolved": "https://registry.npmjs.org/flux/-/flux-4.0.4.tgz",
-      "integrity": "sha512-NCj3XlayA2UsapRpM7va6wU1+9rE5FIL7qoMcmxWHRzbp0yujihMBm9BBHZ1MDIk5h5o2Bl6eGiCe8rYELAmYw==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "fbemitter": "^3.0.0",
-        "fbjs": "^3.0.1"
-      },
-      "peerDependencies": {
-        "react": "^15.0.2 || ^16.0.0 || ^17.0.0"
-      }
-    },
-    "node_modules/function-bind": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
-      "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
-      "license": "MIT",
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/globals": {
-      "version": "11.12.0",
-      "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
-      "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=4"
-      }
-    },
-    "node_modules/hasown": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
-      "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
-      "license": "MIT",
-      "dependencies": {
-        "function-bind": "^1.1.2"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      }
-    },
-    "node_modules/hoist-non-react-statics": {
-      "version": "3.3.2",
-      "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
-      "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "react-is": "^16.7.0"
-      }
-    },
-    "node_modules/hoist-non-react-statics/node_modules/react-is": {
-      "version": "16.13.1",
-      "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
-      "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
-      "license": "MIT"
-    },
-    "node_modules/import-fresh": {
-      "version": "3.3.0",
-      "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
-      "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
-      "license": "MIT",
-      "dependencies": {
-        "parent-module": "^1.0.0",
-        "resolve-from": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=6"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/is-arrayish": {
-      "version": "0.2.1",
-      "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
-      "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
-      "license": "MIT"
-    },
-    "node_modules/is-core-module": {
-      "version": "2.15.1",
-      "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz",
-      "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==",
-      "license": "MIT",
-      "dependencies": {
-        "hasown": "^2.0.2"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/js-tokens": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
-      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
-      "license": "MIT"
-    },
-    "node_modules/jsesc": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz",
-      "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==",
-      "license": "MIT",
-      "bin": {
-        "jsesc": "bin/jsesc"
-      },
-      "engines": {
-        "node": ">=6"
-      }
-    },
-    "node_modules/json-parse-even-better-errors": {
-      "version": "2.3.1",
-      "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
-      "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
-      "license": "MIT"
-    },
-    "node_modules/lines-and-columns": {
-      "version": "1.2.4",
-      "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
-      "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
-      "license": "MIT"
-    },
-    "node_modules/lodash-es": {
-      "version": "4.17.21",
-      "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
-      "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
-      "license": "MIT"
-    },
-    "node_modules/lodash.curry": {
-      "version": "4.1.1",
-      "resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz",
-      "integrity": "sha512-/u14pXGviLaweY5JI0IUzgzF2J6Ne8INyzAZjImcryjgkZ+ebruBxy2/JaOOkTqScddcYtakjhSaeemV8lR0tA==",
-      "license": "MIT"
-    },
-    "node_modules/lodash.flow": {
-      "version": "3.5.0",
-      "resolved": "https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz",
-      "integrity": "sha512-ff3BX/tSioo+XojX4MOsOMhJw0nZoUEF011LX8g8d3gvjVbxd89cCio4BCXronjxcTUIJUoqKEUA+n4CqvvRPw==",
-      "license": "MIT"
-    },
-    "node_modules/loose-envify": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
-      "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
-      "license": "MIT",
-      "dependencies": {
-        "js-tokens": "^3.0.0 || ^4.0.0"
-      },
-      "bin": {
-        "loose-envify": "cli.js"
-      }
-    },
-    "node_modules/ms": {
-      "version": "2.1.3",
-      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
-      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
-      "license": "MIT"
-    },
-    "node_modules/node-fetch": {
-      "version": "2.7.0",
-      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
-      "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
-      "license": "MIT",
-      "dependencies": {
-        "whatwg-url": "^5.0.0"
-      },
-      "engines": {
-        "node": "4.x || >=6.0.0"
-      },
-      "peerDependencies": {
-        "encoding": "^0.1.0"
-      },
-      "peerDependenciesMeta": {
-        "encoding": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/object-assign": {
-      "version": "4.1.1",
-      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
-      "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/parent-module": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
-      "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
-      "license": "MIT",
-      "dependencies": {
-        "callsites": "^3.0.0"
-      },
-      "engines": {
-        "node": ">=6"
-      }
-    },
-    "node_modules/parse-json": {
-      "version": "5.2.0",
-      "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
-      "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
-      "license": "MIT",
-      "dependencies": {
-        "@babel/code-frame": "^7.0.0",
-        "error-ex": "^1.3.1",
-        "json-parse-even-better-errors": "^2.3.0",
-        "lines-and-columns": "^1.1.6"
-      },
-      "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/path-parse": {
-      "version": "1.0.7",
-      "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
-      "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
-      "license": "MIT"
-    },
-    "node_modules/path-type": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
-      "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/picocolors": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
-      "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
-      "license": "ISC"
-    },
-    "node_modules/promise": {
-      "version": "7.3.1",
-      "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
-      "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
-      "license": "MIT",
-      "dependencies": {
-        "asap": "~2.0.3"
-      }
-    },
-    "node_modules/prop-types": {
-      "version": "15.8.1",
-      "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
-      "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
-      "license": "MIT",
-      "dependencies": {
-        "loose-envify": "^1.4.0",
-        "object-assign": "^4.1.1",
-        "react-is": "^16.13.1"
-      }
-    },
-    "node_modules/prop-types/node_modules/react-is": {
-      "version": "16.13.1",
-      "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
-      "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
-      "license": "MIT"
-    },
-    "node_modules/pure-color": {
-      "version": "1.3.0",
-      "resolved": "https://registry.npmjs.org/pure-color/-/pure-color-1.3.0.tgz",
-      "integrity": "sha512-QFADYnsVoBMw1srW7OVKEYjG+MbIa49s54w1MA1EDY6r2r/sTcKKYqRX1f4GYvnXP7eN/Pe9HFcX+hwzmrXRHA==",
-      "license": "MIT"
-    },
-    "node_modules/react": {
-      "version": "17.0.2",
-      "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
-      "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==",
-      "license": "MIT",
-      "peer": true,
-      "dependencies": {
-        "loose-envify": "^1.1.0",
-        "object-assign": "^4.1.1"
-      },
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/react-base16-styling": {
-      "version": "0.10.0",
-      "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.10.0.tgz",
-      "integrity": "sha512-H1k2eFB6M45OaiRru3PBXkuCcn2qNmx+gzLb4a9IPMR7tMH8oBRXU5jGbPDYG1Hz+82d88ED0vjR8BmqU3pQdg==",
-      "license": "MIT",
-      "dependencies": {
-        "@types/lodash": "^4.17.0",
-        "color": "^4.2.3",
-        "csstype": "^3.1.3",
-        "lodash-es": "^4.17.21"
-      }
-    },
-    "node_modules/react-dom": {
-      "version": "17.0.2",
-      "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
-      "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==",
-      "license": "MIT",
-      "peer": true,
-      "dependencies": {
-        "loose-envify": "^1.1.0",
-        "object-assign": "^4.1.1",
-        "scheduler": "^0.20.2"
-      },
-      "peerDependencies": {
-        "react": "17.0.2"
-      }
-    },
-    "node_modules/react-is": {
-      "version": "18.3.1",
-      "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
-      "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
-      "license": "MIT"
-    },
-    "node_modules/react-json-tree": {
-      "version": "0.19.0",
-      "resolved": "https://registry.npmjs.org/react-json-tree/-/react-json-tree-0.19.0.tgz",
-      "integrity": "sha512-PqT1WRVcWP+RROsZPQfNEKIC1iM/ZMfY4g5jN6oDnXp5593PPRAYgoHcgYCDjflAHQMtxl8XGdlTwIBdEGUXvw==",
-      "license": "MIT",
-      "dependencies": {
-        "@types/lodash": "^4.17.0",
-        "react-base16-styling": "^0.10.0"
-      },
-      "peerDependencies": {
-        "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
-        "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
-      }
-    },
-    "node_modules/react-json-view": {
-      "version": "1.21.3",
-      "resolved": "https://registry.npmjs.org/react-json-view/-/react-json-view-1.21.3.tgz",
-      "integrity": "sha512-13p8IREj9/x/Ye4WI/JpjhoIwuzEgUAtgJZNBJckfzJt1qyh24BdTm6UQNGnyTq9dapQdrqvquZTo3dz1X6Cjw==",
-      "license": "MIT",
-      "dependencies": {
-        "flux": "^4.0.1",
-        "react-base16-styling": "^0.6.0",
-        "react-lifecycles-compat": "^3.0.4",
-        "react-textarea-autosize": "^8.3.2"
-      },
-      "peerDependencies": {
-        "react": "^17.0.0 || ^16.3.0 || ^15.5.4",
-        "react-dom": "^17.0.0 || ^16.3.0 || ^15.5.4"
-      }
-    },
-    "node_modules/react-json-view/node_modules/react-base16-styling": {
-      "version": "0.6.0",
-      "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.6.0.tgz",
-      "integrity": "sha512-yvh/7CArceR/jNATXOKDlvTnPKPmGZz7zsenQ3jUwLzHkNUR0CvY3yGYJbWJ/nnxsL8Sgmt5cO3/SILVuPO6TQ==",
-      "license": "MIT",
-      "dependencies": {
-        "base16": "^1.0.0",
-        "lodash.curry": "^4.0.1",
-        "lodash.flow": "^3.3.0",
-        "pure-color": "^1.2.0"
-      }
-    },
-    "node_modules/react-lifecycles-compat": {
-      "version": "3.0.4",
-      "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
-      "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==",
-      "license": "MIT"
-    },
-    "node_modules/react-textarea-autosize": {
-      "version": "8.5.4",
-      "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.4.tgz",
-      "integrity": "sha512-eSSjVtRLcLfFwFcariT77t9hcbVJHQV76b51QjQGarQIHml2+gM2lms0n3XrhnDmgK5B+/Z7TmQk5OHNzqYm/A==",
-      "license": "MIT",
-      "dependencies": {
-        "@babel/runtime": "^7.20.13",
-        "use-composed-ref": "^1.3.0",
-        "use-latest": "^1.2.1"
-      },
-      "engines": {
-        "node": ">=10"
-      },
-      "peerDependencies": {
-        "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
-      }
-    },
-    "node_modules/react-transition-group": {
-      "version": "4.4.5",
-      "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
-      "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "@babel/runtime": "^7.5.5",
-        "dom-helpers": "^5.0.1",
-        "loose-envify": "^1.4.0",
-        "prop-types": "^15.6.2"
-      },
-      "peerDependencies": {
-        "react": ">=16.6.0",
-        "react-dom": ">=16.6.0"
-      }
-    },
-    "node_modules/regenerator-runtime": {
-      "version": "0.14.1",
-      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
-      "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
-      "license": "MIT"
-    },
-    "node_modules/resolve": {
-      "version": "1.22.8",
-      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
-      "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
-      "license": "MIT",
-      "dependencies": {
-        "is-core-module": "^2.13.0",
-        "path-parse": "^1.0.7",
-        "supports-preserve-symlinks-flag": "^1.0.0"
-      },
-      "bin": {
-        "resolve": "bin/resolve"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/resolve-from": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
-      "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=4"
-      }
-    },
-    "node_modules/scheduler": {
-      "version": "0.20.2",
-      "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz",
-      "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==",
-      "license": "MIT",
-      "peer": true,
-      "dependencies": {
-        "loose-envify": "^1.1.0",
-        "object-assign": "^4.1.1"
-      }
-    },
-    "node_modules/setimmediate": {
-      "version": "1.0.5",
-      "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
-      "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
-      "license": "MIT"
-    },
-    "node_modules/simple-swizzle": {
-      "version": "0.2.2",
-      "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
-      "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
-      "license": "MIT",
-      "dependencies": {
-        "is-arrayish": "^0.3.1"
-      }
-    },
-    "node_modules/simple-swizzle/node_modules/is-arrayish": {
-      "version": "0.3.2",
-      "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
-      "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
-      "license": "MIT"
-    },
-    "node_modules/source-map": {
-      "version": "0.5.7",
-      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
-      "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
-      "license": "BSD-3-Clause",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/stylis": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
-      "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==",
-      "license": "MIT"
-    },
-    "node_modules/supports-preserve-symlinks-flag": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
-      "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
-      "license": "MIT",
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/toggle-selection": {
-      "version": "1.0.6",
-      "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
-      "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==",
-      "license": "MIT"
-    },
-    "node_modules/tr46": {
-      "version": "0.0.3",
-      "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
-      "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
-      "license": "MIT"
-    },
-    "node_modules/ua-parser-js": {
-      "version": "1.0.39",
-      "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.39.tgz",
-      "integrity": "sha512-k24RCVWlEcjkdOxYmVJgeD/0a1TiSpqLg+ZalVGV9lsnr4yqu0w7tX/x2xX6G4zpkgQnRf89lxuZ1wsbjXM8lw==",
-      "funding": [
-        {
-          "type": "opencollective",
-          "url": "https://opencollective.com/ua-parser-js"
-        },
-        {
-          "type": "paypal",
-          "url": "https://paypal.me/faisalman"
-        },
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/faisalman"
-        }
-      ],
-      "license": "MIT",
-      "bin": {
-        "ua-parser-js": "script/cli.js"
-      },
-      "engines": {
-        "node": "*"
-      }
-    },
-    "node_modules/use-composed-ref": {
-      "version": "1.3.0",
-      "resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.3.0.tgz",
-      "integrity": "sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ==",
-      "license": "MIT",
-      "peerDependencies": {
-        "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
-      }
-    },
-    "node_modules/use-isomorphic-layout-effect": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz",
-      "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==",
-      "license": "MIT",
-      "peerDependencies": {
-        "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
-      },
-      "peerDependenciesMeta": {
-        "@types/react": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/use-latest": {
-      "version": "1.2.1",
-      "resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.2.1.tgz",
-      "integrity": "sha512-xA+AVm/Wlg3e2P/JiItTziwS7FK92LWrDB0p+hgXloIMuVCeJJ8v6f0eeHyPZaJrM+usM1FkFfbNCrJGs8A/zw==",
-      "license": "MIT",
-      "dependencies": {
-        "use-isomorphic-layout-effect": "^1.1.1"
-      },
-      "peerDependencies": {
-        "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
-      },
-      "peerDependenciesMeta": {
-        "@types/react": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/use-sync-external-store": {
-      "version": "1.2.2",
-      "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz",
-      "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==",
-      "license": "MIT",
-      "peerDependencies": {
-        "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
-      }
-    },
-    "node_modules/webidl-conversions": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
-      "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
-      "license": "BSD-2-Clause"
-    },
-    "node_modules/whatwg-url": {
-      "version": "5.0.0",
-      "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
-      "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
-      "license": "MIT",
-      "dependencies": {
-        "tr46": "~0.0.3",
-        "webidl-conversions": "^3.0.0"
-      }
-    },
-    "node_modules/yaml": {
-      "version": "1.10.2",
-      "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
-      "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
-      "license": "ISC",
-      "engines": {
-        "node": ">= 6"
-      }
-    },
-    "node_modules/zustand": {
-      "version": "4.5.5",
-      "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.5.tgz",
-      "integrity": "sha512-+0PALYNJNgK6hldkgDq2vLrw5f6g/jCInz52n9RTpropGgeAf/ioFUCdtsjCqu4gNhW9D01rUQBROoRjdzyn2Q==",
-      "license": "MIT",
-      "dependencies": {
-        "use-sync-external-store": "1.2.2"
-      },
-      "engines": {
-        "node": ">=12.7.0"
-      },
-      "peerDependencies": {
-        "@types/react": ">=16.8",
-        "immer": ">=9.0.6",
-        "react": ">=16.8"
-      },
-      "peerDependenciesMeta": {
-        "@types/react": {
-          "optional": true
-        },
-        "immer": {
-          "optional": true
-        },
-        "react": {
-          "optional": true
-        }
-      }
-    }
-  }
-}

+ 0 - 12
taipy/gui/custom/__init__.py

@@ -1,12 +0,0 @@
-# Copyright 2021-2025 Avaiga Private Limited
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-#        http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
-# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations under the License.
-
-from ._page import Page, ResourceHandler

+ 0 - 98
taipy/gui/custom/_page.py

@@ -1,98 +0,0 @@
-# Copyright 2021-2025 Avaiga Private Limited
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-#        http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
-# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations under the License.
-
-from __future__ import annotations
-
-import typing as t
-from abc import ABC, abstractmethod
-
-import pandas as pd
-
-from ..page import Page as BasePage
-from ..utils import _TaipyData
-from ..utils.singleton import _Singleton
-
-
-class Page(BasePage):
-    """NOT DOCUMENTED
-    A custom page for external application that can be added to Taipy GUI"""
-
-    def __init__(
-        self,
-        resource_handler: ResourceHandler,
-        binding_variables: t.Optional[t.List[str]] = None,
-        metadata: t.Optional[t.Dict[str, t.Any]] = None,
-        **kwargs,
-    ) -> None:
-        if binding_variables is None:
-            binding_variables = []
-        if metadata is None:
-            metadata = {}
-        super().__init__(**kwargs)
-        self._resource_handler = resource_handler
-        self._binding_variables = binding_variables
-        self._metadata: t.Dict[str, t.Any] = metadata
-
-
-class ResourceHandler(ABC):
-    """NOT DOCUMENTED
-    Resource handler for custom pages.
-
-    User can implement this class to provide custom resources for the custom pages
-    """
-
-    rh_id: str = ""
-
-    data_layer_supported_types: t.Tuple[t.Type, ...] = (_TaipyData, pd.DataFrame, pd.Series)
-
-    def __init__(self) -> None:
-        _ExternalResourceHandlerManager().register(self)
-
-    def get_id(self) -> str:
-        return self.rh_id if self.rh_id != "" else str(id(self))
-
-    @abstractmethod
-    def get_resources(self, path: str, taipy_resource_path: str, base_url: str) -> t.Any:
-        raise NotImplementedError
-
-
-class _ExternalResourceHandlerManager(object, metaclass=_Singleton):
-    """NOT DOCUMENTED
-    Manager for resource handlers.
-
-    This class is used to manage resource handlers for custom pages
-    """
-
-    def __init__(self) -> None:
-        self.__handlers: t.Dict[str, ResourceHandler] = {}
-
-    def register(self, handler: ResourceHandler) -> None:
-        """Register a resource handler
-        Arguments:
-            handler (ResourceHandler): The resource handler to register
-        """
-        self.__handlers[handler.get_id()] = handler
-
-    def get(self, id: str) -> t.Optional[ResourceHandler]:
-        """Get a resource handler by its id
-        Arguments:
-            id (str): The id of the resource handler
-        Returns:
-            ResourceHandler: The resource handler
-        """
-        return self.__handlers.get(id, None)
-
-    def get_all(self) -> t.List[ResourceHandler]:
-        """Get all resource handlers
-        Returns:
-            List[ResourceHandler]: The list of resource handlers
-        """
-        return list(self.__handlers.values())

+ 0 - 37
taipy/gui/custom/utils.py

@@ -1,37 +0,0 @@
-# Copyright 2021-2025 Avaiga Private Limited
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-#        http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
-# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations under the License.
-
-import contextlib
-import typing as t
-
-from flask import has_request_context
-from flask.globals import request
-
-from ..server import _Server
-from ._page import ResourceHandler, _ExternalResourceHandlerManager
-
-
-def is_in_custom_page_context() -> bool:
-    "NOT DOCUMENTED"
-    resource_handler_id = None
-    with contextlib.suppress(Exception):
-        if has_request_context():
-            resource_handler_id = request.cookies.get(_Server._RESOURCE_HANDLER_ARG, None)
-    return resource_handler_id is not None
-
-
-def get_current_resource_handler() -> t.Optional[ResourceHandler]:
-    "NOT DOCUMENTED"
-    resource_handler_id = None
-    with contextlib.suppress(Exception):
-        if has_request_context():
-            resource_handler_id = request.cookies.get(_Server._RESOURCE_HANDLER_ARG, None)
-    return _ExternalResourceHandlerManager().get(resource_handler_id) if resource_handler_id else None

+ 34 - 142
taipy/gui/gui.py

@@ -27,7 +27,7 @@ from importlib.util import find_spec
 from inspect import currentframe, getabsfile, iscoroutinefunction, ismethod, ismodule
 from pathlib import Path
 from threading import Thread, Timer
-from types import FrameType, FunctionType, LambdaType, ModuleType, SimpleNamespace
+from types import FrameType, LambdaType, SimpleNamespace
 from urllib.parse import unquote, urlencode, urlparse
 
 import markdown as md_lib
@@ -36,7 +36,6 @@ from flask import (
     Blueprint,
     Flask,
     g,
-    has_app_context,
     jsonify,
     request,
     send_file,
@@ -62,8 +61,6 @@ from ._renderers.utils import _get_columns_dict
 from ._warnings import TaipyGuiWarning, _warn
 from .builder._api_generator import _ElementApiGenerator
 from .config import Config, ConfigParameter, _Config
-from .custom import Page as CustomPage
-from .custom.utils import get_current_resource_handler, is_in_custom_page_context
 from .data.content_accessor import _ContentAccessor
 from .data.data_accessor import _DataAccessors
 from .data.data_format import _DataFormat
@@ -727,14 +724,8 @@ class Gui:
                         self.__request_data_update(str(message.get("name")), message.get("payload"))
                     elif msg_type == _WsType.REQUEST_UPDATE.value:
                         self.__request_var_update(message.get("payload"))
-                    elif msg_type == _WsType.GET_MODULE_CONTEXT.value:
-                        self.__handle_ws_get_module_context(payload)
-                    elif msg_type == _WsType.GET_DATA_TREE.value:
-                        self.__handle_ws_get_data_tree()
                     elif msg_type == _WsType.GUI_ADDR.value:
                         self.__handle_ws_gui_addr(message)
-                    elif msg_type == _WsType.GET_ROUTES.value:
-                        self.__handle_ws_get_routes()
                     elif msg_type == _WsType.LOCAL_STORAGE.value:
                         self.__handle_ws_local_storage(message)
                     else:
@@ -1118,7 +1109,7 @@ class Gui:
         front_var: t.Optional[str] = None,
     ):
         ws_dict = {}
-        is_custom_page = is_in_custom_page_context()
+        is_custom_page = _Hooks()._is_in_custom_page_context()
         values = {v: _getscopeattr_drill(self, v) for v in modified_vars if is_custom_page or _is_moduled_variable(v)}  # type: ignore[arg-type]
         if not values:
             return
@@ -1129,9 +1120,10 @@ class Gui:
                 modified_vars.remove(k)
         for _var in modified_vars:
             newvalue = values.get(_var)
-            resource_handler = get_current_resource_handler()
-            custom_page_filtered_types = resource_handler.data_layer_supported_types if resource_handler else ()
-            if isinstance(newvalue, (_TaipyData)) or isinstance(newvalue, custom_page_filtered_types):
+            custom_page_filtered_types = _Hooks()._get_resource_handler_data_layer_supported_types()
+            if isinstance(newvalue, (_TaipyData)) or (
+                custom_page_filtered_types and isinstance(newvalue, custom_page_filtered_types)
+            ):  # type: ignore
                 newvalue = {"__taipy_refresh": True}
             else:
                 if isinstance(newvalue, (_TaipyContent, _TaipyContentImage)):
@@ -1153,7 +1145,7 @@ class Gui:
                 elif isinstance(newvalue, _TaipyBase):
                     newvalue = newvalue.get()
                 # Skip in taipy-gui, available in custom frontend
-                if isinstance(newvalue, (dict, _MapDict)) and not is_in_custom_page_context():
+                if isinstance(newvalue, (dict, _MapDict)) and not _Hooks()._is_in_custom_page_context():
                     continue
                 if isinstance(newvalue, float) and math.isnan(newvalue):
                     # do not let NaN go through json, it is not handle well (dies silently through websocket)
@@ -1224,9 +1216,12 @@ class Gui:
     def __request_data_update(self, var_name: str, payload: t.Any) -> None:
         # Use custom attrgetter function to allow value binding for _MapDict
         newvalue = _getscopeattr_drill(self, var_name)  # type: ignore[arg-type]
-        resource_handler = get_current_resource_handler()
-        custom_page_filtered_types = resource_handler.data_layer_supported_types if resource_handler else ()
-        if not isinstance(newvalue, _TaipyData) and isinstance(newvalue, custom_page_filtered_types):
+        custom_page_filtered_types = _Hooks()._get_resource_handler_data_layer_supported_types()
+        if (
+            not isinstance(newvalue, _TaipyData)
+            and custom_page_filtered_types
+            and isinstance(newvalue, custom_page_filtered_types)
+        ):  # type: ignore
             newvalue = _TaipyData(newvalue, "")
         if isinstance(newvalue, _TaipyData):
             ret_payload = None
@@ -1266,80 +1261,6 @@ class Gui:
                     )
             self.__send_var_list_update(payload["names"])
 
-    def __handle_ws_get_module_context(self, payload: t.Any):
-        if isinstance(payload, dict):
-            page_path = str(payload.get("path"))
-            if page_path in {"/", ""}:
-                page_path = Gui.__root_page_name
-            # Get Module Context
-            if mc := self._get_page_context(page_path):
-                page_renderer = t.cast(_Page, self._get_page(page_path))._renderer
-                self._bind_custom_page_variables(t.cast(t.Any, page_renderer), self._get_client_id())
-                # get metadata if there is one
-                metadata: t.Dict[str, t.Any] = {}
-                if hasattr(page_renderer, "_metadata"):
-                    metadata = getattr(page_renderer, "_metadata", {})
-                meta_return = json.dumps(metadata, cls=_TaipyJsonEncoder) if metadata else None
-                self.__send_ws(
-                    {
-                        "type": _WsType.GET_MODULE_CONTEXT.value,
-                        "payload": {"context": mc, "metadata": meta_return},
-                    },
-                    send_back_only=True,
-                )
-
-    def __get_variable_tree(self, data: t.Dict[str, t.Any]):
-        # Module Context -> Variable -> Variable data (name, type, initial_value)
-        variable_tree: t.Dict[str, t.Dict[str, t.Dict[str, t.Any]]] = {}
-        # Types of data to be handled by the data layer and filtered out here
-        resource_handler = get_current_resource_handler()
-        filtered_value_types = resource_handler.data_layer_supported_types if resource_handler else ()
-        for k, v in data.items():
-            if isinstance(v, _TaipyBase):
-                data[k] = v.get()
-            var_name, var_module_name = _variable_decode(k)
-            if var_module_name == "" or var_module_name is None:
-                var_module_name = "__main__"
-            if var_module_name not in variable_tree:
-                variable_tree[var_module_name] = {}
-            data_update = isinstance(v, filtered_value_types)
-            value = None if data_update else data[k]
-            # if _is_moduled_variable(k):
-            variable_tree[var_module_name][var_name] = {
-                "type": type(v).__name__,
-                "value": value,
-                "encoded_name": k,
-                "data_update": data_update,
-            }
-        return variable_tree
-
-    def __handle_ws_get_data_tree(self):
-        # Get Variables
-        self.__pre_render_pages()
-        data = {
-            k: v
-            for k, v in vars(self._get_data_scope()).items()
-            if not k.startswith("_")
-            and not callable(v)
-            and "TpExPr" not in k
-            and not isinstance(v, (ModuleType, FunctionType, LambdaType, type, Page))
-        }
-        function_data = {
-            k: v
-            for k, v in vars(self._get_data_scope()).items()
-            if not k.startswith("_") and "TpExPr" not in k and isinstance(v, (FunctionType, LambdaType))
-        }
-        self.__send_ws(
-            {
-                "type": _WsType.GET_DATA_TREE.value,
-                "payload": {
-                    "variable": self.__get_variable_tree(data),
-                    "function": self.__get_variable_tree(function_data),
-                },
-            },
-            send_back_only=True,
-        )
-
     def __handle_ws_gui_addr(self, message: t.Any):
         if not isinstance(message, dict):
             return
@@ -1356,25 +1277,6 @@ class Gui:
             send_back_only=True,
         )
 
-    def __handle_ws_get_routes(self):
-        routes = (
-            [[self._config.root_page._route, t.cast(t.Any, self._config.root_page._renderer).page_type]]
-            if self._config.root_page
-            else []
-        )
-        routes += [
-            [page._route, t.cast(t.Any, page._renderer).page_type]
-            for page in self._config.pages
-            if page._route != Gui.__root_page_name
-        ]
-        self.__send_ws(
-            {
-                "type": _WsType.GET_ROUTES.value,
-                "payload": routes,
-            },
-            send_back_only=True,
-        )
-
     def __handle_ws_local_storage(self, message: t.Any):
         if not isinstance(message, dict):
             return
@@ -2548,16 +2450,24 @@ class Gui:
         for page in self._config.pages:
             if page is not None:
                 with contextlib.suppress(Exception):
-                    if isinstance(page._renderer, CustomPage):
-                        self._bind_custom_page_variables(page._renderer, self._get_client_id())
+                    if (
+                        page._renderer is not None
+                        and _Hooks()._get_custom_page_type()
+                        and isinstance(page._renderer, _Hooks()._get_custom_page_type())  # type: ignore[arg-type]
+                    ):
+                        _Hooks()._bind_custom_page_variables(self, page._renderer, self._get_client_id())
                     else:
                         page.render(self, silent=True)  # type: ignore[arg-type]
         if additional_pages := _Hooks()._get_additional_pages():
             for page in additional_pages:
                 if isinstance(page, Page):
                     with contextlib.suppress(Exception):
-                        if isinstance(page, CustomPage):
-                            self._bind_custom_page_variables(page, self._get_client_id())
+                        if (
+                            page._renderer is not None
+                            and _Hooks()._get_custom_page_type()
+                            and isinstance(page, _Hooks()._get_custom_page_type())  # type: ignore[arg-type]
+                        ):
+                            _Hooks()._bind_custom_page_variables(self, page, self._get_client_id())
                         else:
                             new_page = _Page()
                             new_page._renderer = page
@@ -2605,21 +2515,6 @@ class Gui:
     def _get_page(self, page_name: str):
         return next((page_i for page_i in self._config.pages if page_i._route == page_name), None)
 
-    def _bind_custom_page_variables(self, page: CustomPage, client_id: t.Optional[str]):
-        """Handle the bindings of custom page variables"""
-        if not isinstance(page, CustomPage):
-            return
-        with self.get_flask_app().app_context() if has_app_context() else contextlib.nullcontext():  # type: ignore[attr-defined]
-            self.__set_client_id_in_context(client_id)
-            with self._set_locals_context(page._get_module_name()):
-                for k, v in self._get_locals_bind().items():
-                    if (
-                        (not page._binding_variables or k in page._binding_variables)
-                        and not k.startswith("_")
-                        and not isinstance(v, ModuleType)
-                    ):
-                        self._bind_var(k)
-
     def __render_page(self, page_name: str) -> t.Any:
         self.__set_client_id_in_context()
         nav_page = self._get_navigated_page(page_name)
@@ -2637,17 +2532,12 @@ class Gui:
                 {"Content-Type": "application/json; charset=utf-8"},
             )
         # Handle custom pages
-        if (pr := page._renderer) is not None and isinstance(pr, CustomPage):
-            if self._navigate(
-                to=page_name,
-                params={
-                    _Server._RESOURCE_HANDLER_ARG: pr._resource_handler.get_id(),
-                },
-            ):
-                # Proactively handle the bindings of custom page variables
-                self._bind_custom_page_variables(pr, self._get_client_id())
-                return ("Successfully redirect to custom resource handler", 200)
-            return ("Failed to navigate to custom resource handler", 500)
+        if (
+            (pr := page._renderer) is not None
+            and _Hooks()._get_custom_page_type()
+            and isinstance(pr, _Hooks()._get_custom_page_type())  # type: ignore[arg-type]
+        ):
+            return _Hooks()._handle_custom_page_render(self, page_name, pr)
         # Handle page rendering
         context = page.render(self)  # type: ignore[arg-type]
         if (
@@ -2809,7 +2699,9 @@ class Gui:
             if additional_pages := _Hooks()._get_additional_pages():
                 # add page context for additional pages so that they can be managed by the variable directory
                 for page in additional_pages:
-                    if isinstance(page, Page) and not isinstance(page, CustomPage):
+                    if isinstance(page, Page) and not (
+                        _Hooks()._get_custom_page_type() and isinstance(page, _Hooks()._get_custom_page_type())  # type: ignore[arg-type]
+                    ):
                         self._add_page_context(page)
             self.__var_dir.process_imported_var()
             # bind on_* function if available

+ 3 - 6
taipy/gui/page.py

@@ -40,7 +40,7 @@ class Page:
     page_type: str = "Taipy"
 
     def __init__(self, **kwargs) -> None:
-        from .custom import Page as CustomPage
+        from ._hook import _Hooks
 
         self._class_module_name = ""
         self._class_locals: t.Dict[str, t.Any] = {}
@@ -56,11 +56,8 @@ class Page:
             self._frame = kwargs.get("frame")
         elif self._renderer:
             self._frame = self._renderer._frame
-        elif isinstance(self, CustomPage):
-            self._frame = t.cast(FrameType, t.cast(FrameType, inspect.stack()[2].frame))
-            # Allow CustomPage class to be inherited
-            if len(inspect.stack()) > 3 and inspect.stack()[2].function != "<module>":
-                self._frame = t.cast(FrameType, t.cast(FrameType, inspect.stack()[3].frame))
+        elif _Hooks()._get_custom_page_type() and isinstance(self, _Hooks()._get_custom_page_type()):  # type: ignore[union-attr]
+            _Hooks()._assign_custom_page_frame(self)
         elif len(inspect.stack()) < 4:
             raise RuntimeError(f"Can't resolve module. Page '{type(self).__name__}' is not registered.")
         else:

+ 14 - 21
taipy/gui/server.py

@@ -28,10 +28,7 @@ from flask import (
     Flask,
     json,
     jsonify,
-    make_response,
     render_template,
-    render_template_string,
-    request,
     send_from_directory,
 )
 from flask_cors import CORS
@@ -45,7 +42,6 @@ from taipy.common.logger._taipy_logger import _TaipyLogger
 
 from ._renderers.json import _TaipyJsonProvider
 from .config import ServerConfig
-from .custom._page import _ExternalResourceHandlerManager
 from .utils import _is_in_notebook, _is_port_open, _RuntimeManager
 from .utils._css import get_style
 
@@ -58,7 +54,6 @@ class _Server:
     __RE_CLOSING_CURLY = re.compile(r"(\})([^\"])")
     __OPENING_CURLY = r"\1&#x7B;"
     __CLOSING_CURLY = r"&#x7D;\2"
-    _RESOURCE_HANDLER_ARG = "tprh"
 
     def __init__(
         self,
@@ -166,20 +161,13 @@ class _Server:
         @taipy_bp.route("/", defaults={"path": ""})
         @taipy_bp.route("/<path:path>")
         def my_index(path):
-            resource_handler_id = request.cookies.get(_Server._RESOURCE_HANDLER_ARG, None)
-            if resource_handler_id is not None:
-                resource_handler = _ExternalResourceHandlerManager().get(resource_handler_id)
-                if resource_handler is None:
-                    reload_html = "<html><head><style>body {background-color: black; margin: 0;}</style></head><body><script>location.reload();</script></body></html>"  # noqa: E501
-                    response = make_response(render_template_string(reload_html), 400)
-                    response.set_cookie(
-                        _Server._RESOURCE_HANDLER_ARG, "", secure=request.is_secure, httponly=True, expires=0, path="/"
-                    )
-                    return response
-                try:
-                    return resource_handler.get_resources(path, static_folder, base_url)
-                except Exception as e:
-                    raise RuntimeError("Can't get resources from custom resource handler") from e
+            from ._hook import _Hooks
+
+            custom_page_resource = _Hooks()._resolve_custom_page_resource_handler(
+                path, base_url, static_folder, client_config, css_vars, scripts, styles
+            )
+            if custom_page_resource is not None:
+                return custom_page_resource
             if path == "" or path == "index.html" or "." not in path:
                 try:
                     return render_template(
@@ -239,12 +227,17 @@ class _Server:
 
         return taipy_bp
 
-    # Update to render as JSX
-    def _render(self, html_fragment, script_paths, style, head, context):
+    @staticmethod
+    def _render_jsx_fragment(html_fragment):
         template_str = _Server.__RE_OPENING_CURLY.sub(_Server.__OPENING_CURLY, html_fragment)
         template_str = _Server.__RE_CLOSING_CURLY.sub(_Server.__CLOSING_CURLY, template_str)
         template_str = template_str.replace('"{!', "{")
         template_str = template_str.replace('!}"', "}")
+        return template_str
+
+    # Update to render as JSX
+    def _render(self, html_fragment, script_paths, style, head, context):
+        template_str = _Server._render_jsx_fragment(html_fragment)
         style = get_style(style)
         return self._direct_render_json(
             {

+ 0 - 3
taipy/gui/types.py

@@ -48,9 +48,6 @@ class _WsType(Enum):
     DOWNLOAD_FILE = "DF"
     PARTIAL = "PR"
     ACKNOWLEDGEMENT = "ACK"
-    GET_MODULE_CONTEXT = "GMC"
-    GET_DATA_TREE = "GDT"
-    GET_ROUTES = "GR"
     FAVICON = "FV"
     BROADCAST = "BC"
     LOCAL_STORAGE = "LS"