From 8a444b6cffae877ce3872a5390991cb924374fe9 Mon Sep 17 00:00:00 2001 From: Dinh Long Nguyen Date: Sun, 23 Jun 2024 02:11:42 +0700 Subject: [PATCH] Feature/wsadapter (#1423) (#1391) * WS adapter * revert bundle * add taipy designer entry * extract bundle * use ws adapter properly * export wssendmessage * dont export main app -> reduce bundle size * ws message type to include string in d.ts * manage external ws message * add msg_type * export correctly * per Fred * per Fabien --- frontend/taipy-gui/base/src/app.ts | 12 +- frontend/taipy-gui/base/src/dataManager.ts | 2 +- frontend/taipy-gui/base/src/exports.ts | 9 ++ .../taipy-gui/base/src/packaging/package.json | 7 ++ .../base/src/packaging/taipy-gui-base.d.ts | 113 +++++++++++++++++ frontend/taipy-gui/base/src/socket.ts | 46 +++++++ frontend/taipy-gui/base/src/utils.ts | 117 ----------------- frontend/taipy-gui/base/src/wsAdapter.ts | 96 ++++++++++++++ frontend/taipy-gui/base/webpack.config.js | 119 +++++++++++------- frontend/taipy-gui/package-lock.json | 10 +- frontend/taipy-gui/package.json | 3 +- frontend/taipy-gui/webpack.config.js | 32 +++++ taipy/__init__.py | 3 + taipy/gui/gui.py | 13 ++ taipy/gui/server.py | 8 ++ 15 files changed, 422 insertions(+), 168 deletions(-) create mode 100644 frontend/taipy-gui/base/src/exports.ts create mode 100644 frontend/taipy-gui/base/src/packaging/package.json create mode 100644 frontend/taipy-gui/base/src/packaging/taipy-gui-base.d.ts create mode 100644 frontend/taipy-gui/base/src/socket.ts delete mode 100644 frontend/taipy-gui/base/src/utils.ts create mode 100644 frontend/taipy-gui/base/src/wsAdapter.ts diff --git a/frontend/taipy-gui/base/src/app.ts b/frontend/taipy-gui/base/src/app.ts index f600d2571a..3368757a95 100644 --- a/frontend/taipy-gui/base/src/app.ts +++ b/frontend/taipy-gui/base/src/app.ts @@ -4,7 +4,8 @@ import { uploadFile } from "../../src/workers/fileupload"; import { Socket, io } from "socket.io-client"; import { DataManager, ModuleData } from "./dataManager"; -import { initSocket } from "./utils"; +import { initSocket } from "./socket"; +import { TaipyWsAdapter, WsAdapter } from "./wsAdapter"; export type OnInitHandler = (taipyApp: TaipyApp) => void; export type OnChangeHandler = (taipyApp: TaipyApp, encodedName: string, value: unknown) => void; @@ -25,6 +26,7 @@ export class TaipyApp { context: string; path: string | undefined; routes: Route[] | undefined; + wsAdapters: WsAdapter[]; constructor( onInit: OnInitHandler | undefined = undefined, @@ -43,6 +45,7 @@ export class TaipyApp { this.routes = undefined; this.path = path; this.socket = socket; + this.wsAdapters = [new TaipyWsAdapter()]; // Init socket io connection initSocket(socket, this); } @@ -51,6 +54,7 @@ export class TaipyApp { get onInit() { return this._onInit; } + set onInit(handler: OnInitHandler | undefined) { if (handler !== undefined && handler.length !== 1) { throw new Error("onInit() requires one parameter"); @@ -61,6 +65,7 @@ export class TaipyApp { get onChange() { return this._onChange; } + set onChange(handler: OnChangeHandler | undefined) { if (handler !== undefined && handler.length !== 3) { throw new Error("onChange() requires three parameters"); @@ -71,6 +76,7 @@ export class TaipyApp { get onNotify() { return this._onNotify; } + set onNotify(handler: OnNotifyHandler | undefined) { if (handler !== undefined && handler.length !== 3) { throw new Error("onNotify() requires three parameters"); @@ -105,6 +111,10 @@ export class TaipyApp { } // Public methods + registerWsAdapter(wsAdapter: WsAdapter) { + this.wsAdapters.unshift(wsAdapter); + } + getEncodedName(varName: string, module: string) { return this.variableData?.getEncodedName(varName, module); } diff --git a/frontend/taipy-gui/base/src/dataManager.ts b/frontend/taipy-gui/base/src/dataManager.ts index b16775df5b..c445b91a93 100644 --- a/frontend/taipy-gui/base/src/dataManager.ts +++ b/frontend/taipy-gui/base/src/dataManager.ts @@ -2,7 +2,7 @@ export type ModuleData = Record; export type VarName = Record; -interface VarData { +export interface VarData { type: string; value: unknown; encoded_name: string; diff --git a/frontend/taipy-gui/base/src/exports.ts b/frontend/taipy-gui/base/src/exports.ts new file mode 100644 index 0000000000..3343eb83ae --- /dev/null +++ b/frontend/taipy-gui/base/src/exports.ts @@ -0,0 +1,9 @@ +import { WsAdapter } from "./wsAdapter"; +import { sendWsMessage } from "../../src/context/wsUtils"; +// import { TaipyApp } from "./app"; + +export { + WsAdapter, + sendWsMessage, + // TaipyApp, +}; diff --git a/frontend/taipy-gui/base/src/packaging/package.json b/frontend/taipy-gui/base/src/packaging/package.json new file mode 100644 index 0000000000..96039a7e7a --- /dev/null +++ b/frontend/taipy-gui/base/src/packaging/package.json @@ -0,0 +1,7 @@ +{ + "name": "taipy-gui-base", + "version": "3.2.0", + "private": true, + "main": "./taipy-gui-base.js", + "types": "./taipy-gui-base.d.ts" +} diff --git a/frontend/taipy-gui/base/src/packaging/taipy-gui-base.d.ts b/frontend/taipy-gui/base/src/packaging/taipy-gui-base.d.ts new file mode 100644 index 0000000000..4568e91566 --- /dev/null +++ b/frontend/taipy-gui/base/src/packaging/taipy-gui-base.d.ts @@ -0,0 +1,113 @@ +import { Socket } from "socket.io-client"; + +export type ModuleData = Record; +export type VarName = Record; +export interface VarData { + type: string; + value: unknown; + encoded_name: string; +} +declare class DataManager { + _data: Record; + _init_data: ModuleData; + constructor(variableModuleData: ModuleData); + init(variableModuleData: ModuleData): ModuleData; + getEncodedName(varName: string, module: string): string | undefined; + getName(encodedName: string): [string, string] | undefined; + get(encodedName: string): unknown; + getInfo(encodedName: string): VarData | undefined; + getDataTree(): ModuleData; + getAllData(): Record; + update(encodedName: string, value: unknown): void; +} +export type OnInitHandler = (taipyApp: TaipyApp) => void; +export type OnChangeHandler = (taipyApp: TaipyApp, encodedName: string, value: unknown) => void; +export type OnNotifyHandler = (taipyApp: TaipyApp, type: string, message: string) => void; +export type OnReloadHandler = (taipyApp: TaipyApp, removedChanges: ModuleData) => void; +export type Route = [string, string]; +export declare class TaipyApp { + socket: Socket; + _onInit: OnInitHandler | undefined; + _onChange: OnChangeHandler | undefined; + _onNotify: OnNotifyHandler | undefined; + _onReload: OnReloadHandler | undefined; + variableData: DataManager | undefined; + functionData: DataManager | undefined; + appId: string; + clientId: string; + context: string; + path: string | undefined; + routes: Route[] | undefined; + wsAdapters: WsAdapter[]; + constructor( + onInit?: OnInitHandler | undefined, + onChange?: OnChangeHandler | undefined, + path?: string | undefined, + socket?: Socket | undefined + ); + get onInit(): OnInitHandler | undefined; + set onInit(handler: OnInitHandler | undefined); + get onChange(): OnChangeHandler | undefined; + set onChange(handler: OnChangeHandler | undefined); + get onNotify(): OnNotifyHandler | undefined; + set onNotify(handler: OnNotifyHandler | undefined); + get onReload(): OnReloadHandler | undefined; + set onReload(handler: OnReloadHandler | undefined); + init(): void; + registerWsAdapter(wsAdapter: WsAdapter): void; + getEncodedName(varName: string, module: string): string | undefined; + getName(encodedName: string): [string, string] | undefined; + get(encodedName: string): unknown; + getInfo(encodedName: string): VarData | undefined; + getDataTree(): ModuleData | undefined; + getAllData(): Record | undefined; + getFunctionList(): string[]; + getRoutes(): Route[] | undefined; + update(encodedName: string, value: unknown): void; + getContext(): string; + updateContext(path?: string | undefined): void; + trigger(actionName: string, triggerId: string, payload?: Record): void; + upload(encodedName: string, files: FileList, progressCallback: (val: number) => void): Promise; + getPageMetadata(): any; +} +export type WsMessageType = + | "A" + | "U" + | "DU" + | "MU" + | "RU" + | "AL" + | "BL" + | "NA" + | "ID" + | "MS" + | "DF" + | "PR" + | "ACK" + | "GMC" + | "GDT" + | "AID" + | "GR"; +export interface WsMessage { + type: WsMessageType | str; + name: string; + payload: Record | unknown; + propagate: boolean; + client_id: string; + module_context: string; + ack_id?: string; +} +export declare const sendWsMessage: ( + socket: Socket | undefined, + type: WsMessageType | str, + name: string, + payload: Record | unknown, + id: string, + moduleContext?: string, + propagate?: boolean, + serverAck?: (val: unknown) => void +) => string; +export declare abstract class WsAdapter { + abstract supportedMessageTypes: string[]; + abstract handleWsMessage(message: WsMessage, app: TaipyApp): boolean; +} diff --git a/frontend/taipy-gui/base/src/socket.ts b/frontend/taipy-gui/base/src/socket.ts new file mode 100644 index 0000000000..194f73e772 --- /dev/null +++ b/frontend/taipy-gui/base/src/socket.ts @@ -0,0 +1,46 @@ +import { Socket } from "socket.io-client"; +import { WsMessage, sendWsMessage } from "../../src/context/wsUtils"; +import { TaipyApp } from "./app"; + +export const initSocket = (socket: Socket, taipyApp: TaipyApp) => { + socket.on("connect", () => { + if (taipyApp.clientId === "" || taipyApp.appId === "") { + taipyApp.init(); + } + }); + // Send a request to get App ID to verify that the app has not been reloaded + socket.io.on("reconnect", () => { + console.log("WebSocket reconnected"); + sendWsMessage(socket, "AID", "reconnect", taipyApp.appId, taipyApp.clientId, taipyApp.context); + }); + // try to reconnect on connect_error + socket.on("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) => { + 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) => { + // 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(); + } +}; diff --git a/frontend/taipy-gui/base/src/utils.ts b/frontend/taipy-gui/base/src/utils.ts deleted file mode 100644 index 9f943efa93..0000000000 --- a/frontend/taipy-gui/base/src/utils.ts +++ /dev/null @@ -1,117 +0,0 @@ -import merge from "lodash/merge"; -import { Socket } from "socket.io-client"; -import { IdMessage, storeClientId } from "../../src/context/utils"; -import { WsMessage, sendWsMessage } from "../../src/context/wsUtils"; -import { TaipyApp } from "./app"; -import { DataManager, ModuleData } from "./dataManager"; - -interface MultipleUpdatePayload { - name: string; - payload: { value: unknown }; -} - -interface AlertMessage extends WsMessage { - atype: string; - message: string; -} - -const initWsMessageTypes = ["ID", "AID", "GMC"]; - -export const initSocket = (socket: Socket, taipyApp: TaipyApp) => { - socket.on("connect", () => { - if (taipyApp.clientId === "" || taipyApp.appId === "") { - taipyApp.init(); - } - }); - // Send a request to get App ID to verify that the app has not been reloaded - socket.io.on("reconnect", () => { - console.log("WebSocket reconnected") - sendWsMessage(socket, "AID", "reconnect", taipyApp.appId, taipyApp.clientId, taipyApp.context); - }); - // try to reconnect on connect_error - socket.on("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) => { - 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) => { - processWsMessage(message, taipyApp); - }); - // only now does the socket tries to open/connect - if (!socket.connected) { - socket.connect(); - } -}; - -const processWsMessage = (message: WsMessage, taipyApp: TaipyApp) => { - 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; - taipyApp.variableData?.update(encodedName, value); - taipyApp.onChange && taipyApp.onChange(taipyApp, encodedName, value); - } - } else if (message.type === "ID") { - const { id } = message as unknown as IdMessage; - storeClientId(id); - taipyApp.clientId = id; - taipyApp.updateContext(taipyApp.path); - } else if (message.type === "GMC") { - const mc = (message.payload as Record).data as string; - window.localStorage.setItem("ModuleContext", mc); - taipyApp.context = mc; - } else if (message.type === "GDT") { - const payload = message.payload as Record; - 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.onReload && taipyApp.onReload(taipyApp, changes); - } - } else { - taipyApp.variableData = new DataManager(variableData); - taipyApp.functionData = new DataManager(functionData); - taipyApp.onInit && taipyApp.onInit(taipyApp); - } - } else if (message.type === "AID") { - const payload = message.payload as Record; - if (payload.name === "reconnect") { - return taipyApp.init(); - } - taipyApp.appId = payload.id as string; - } else if (message.type === "GR") { - const payload = message.payload as [string, string][]; - taipyApp.routes = payload; - } else if (message.type === "AL" && taipyApp.onNotify) { - const payload = message as AlertMessage; - taipyApp.onNotify(taipyApp, payload.atype, payload.message); - } - postWsMessageProcessing(message, taipyApp); - } -}; - -const postWsMessageProcessing = (message: WsMessage, taipyApp: TaipyApp) => { - // perform data population only when all necessary metadata is ready - if ( - initWsMessageTypes.includes(message.type) && - taipyApp.clientId !== "" && - taipyApp.appId !== "" && - taipyApp.context !== "" && - taipyApp.routes !== undefined - ) { - sendWsMessage(taipyApp.socket, "GDT", "get_data_tree", {}, taipyApp.clientId, taipyApp.context); - } -}; diff --git a/frontend/taipy-gui/base/src/wsAdapter.ts b/frontend/taipy-gui/base/src/wsAdapter.ts new file mode 100644 index 0000000000..cad2ca718a --- /dev/null +++ b/frontend/taipy-gui/base/src/wsAdapter.ts @@ -0,0 +1,96 @@ +import merge from "lodash/merge"; +import { TaipyApp } from "./app"; +import { IdMessage, storeClientId } from "../../src/context/utils"; +import { WsMessage, sendWsMessage } from "../../src/context/wsUtils"; +import { DataManager, 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 AlertMessage extends WsMessage { + atype: string; + message: string; +} + +export class TaipyWsAdapter extends WsAdapter { + supportedMessageTypes: string[]; + initWsMessageTypes: string[]; + constructor() { + super(); + this.supportedMessageTypes = ["MU", "ID", "GMC", "GDT", "AID", "GR", "AL"]; + this.initWsMessageTypes = ["ID", "AID", "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; + taipyApp.variableData?.update(encodedName, value); + taipyApp.onChange && taipyApp.onChange(taipyApp, encodedName, value); + } + } else if (message.type === "ID") { + const { id } = message as unknown as IdMessage; + storeClientId(id); + taipyApp.clientId = id; + taipyApp.updateContext(taipyApp.path); + } else if (message.type === "GMC") { + const mc = (message.payload as Record).data as string; + window.localStorage.setItem("ModuleContext", mc); + taipyApp.context = mc; + } else if (message.type === "GDT") { + const payload = message.payload as Record; + 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.onReload && taipyApp.onReload(taipyApp, changes); + } + } else { + taipyApp.variableData = new DataManager(variableData); + taipyApp.functionData = new DataManager(functionData); + taipyApp.onInit && taipyApp.onInit(taipyApp); + } + } else if (message.type === "AID") { + const payload = message.payload as Record; + if (payload.name === "reconnect") { + taipyApp.init(); + return true; + } + taipyApp.appId = payload.id as string; + } else if (message.type === "GR") { + const payload = message.payload as [string, string][]; + taipyApp.routes = payload; + } else if (message.type === "AL" && taipyApp.onNotify) { + const payload = message as AlertMessage; + taipyApp.onNotify(taipyApp, payload.atype, payload.message); + } + 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.appId !== "" && + taipyApp.context !== "" && + taipyApp.routes !== undefined + ) { + sendWsMessage(taipyApp.socket, "GDT", "get_data_tree", {}, taipyApp.clientId, taipyApp.context); + } + } +} diff --git a/frontend/taipy-gui/base/webpack.config.js b/frontend/taipy-gui/base/webpack.config.js index 20ac795672..6c4c509c5c 100644 --- a/frontend/taipy-gui/base/webpack.config.js +++ b/frontend/taipy-gui/base/webpack.config.js @@ -1,56 +1,89 @@ const path = require("path"); const webpack = require("webpack"); -const resolveApp = relativePath => path.resolve(__dirname, relativePath); +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", - "preview": "./base/src/index-preview.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", +module.exports = [ + { + target: "web", + entry: { + default: "./base/src/index.ts", + preview: "./base/src/index-preview.ts", }, - }, - optimization: { - splitChunks: { - chunks: 'all', - name: "shared", + 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: "_", + // }, + // }, }, - module: { - rules: [ - { - test: /\.tsx?$/, - use: "ts-loader", - exclude: /node_modules/, + { + 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 }], + }), ], }, - resolve: { - extensions: [".tsx", ".ts", ".js", ".tsx"], - }, - // externals: { - // "socket.io-client": { - // commonjs: "socket.io-client", - // commonjs2: "socket.io-client", - // amd: "socket.io-client", - // root: "_", - // }, - // }, -}; +]; diff --git a/frontend/taipy-gui/package-lock.json b/frontend/taipy-gui/package-lock.json index 8f2c84b498..3cc9f0c2dc 100644 --- a/frontend/taipy-gui/package-lock.json +++ b/frontend/taipy-gui/package-lock.json @@ -61,7 +61,7 @@ "css-loader": "^7.1.0", "css-mediaquery": "^0.1.2", "dotenv-webpack": "^8.0.0", - "dts-bundle-generator": "^9.2.1", + "dts-bundle-generator": "^7.2.0", "eslint": "^8.57.0", "eslint-plugin-react": "^7.26.1", "eslint-plugin-react-hooks": "^4.2.0", @@ -5918,12 +5918,12 @@ } }, "node_modules/dts-bundle-generator": { - "version": "9.5.1", - "resolved": "https://registry.npmjs.org/dts-bundle-generator/-/dts-bundle-generator-9.5.1.tgz", - "integrity": "sha512-DxpJOb2FNnEyOzMkG11sxO2dmxPjthoVWxfKqWYJ/bI/rT1rvTMktF5EKjAYrRZu6Z6t3NhOUZ0sZ5ZXevOfbA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/dts-bundle-generator/-/dts-bundle-generator-7.2.0.tgz", + "integrity": "sha512-pHjRo52hvvLDRijzIYRTS9eJR7vAOs3gd/7jx+7YVnLU8ay3yPUWGtHXPtuMBSlJYk/s4nq1SvXObDCZVguYMg==", "dev": true, "dependencies": { - "typescript": ">=5.0.2", + "typescript": ">=4.5.2", "yargs": "^17.6.0" }, "bin": { diff --git a/frontend/taipy-gui/package.json b/frontend/taipy-gui/package.json index 7c3492726e..fcbc259f68 100644 --- a/frontend/taipy-gui/package.json +++ b/frontend/taipy-gui/package.json @@ -46,6 +46,7 @@ "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" @@ -97,7 +98,7 @@ "css-loader": "^7.1.0", "css-mediaquery": "^0.1.2", "dotenv-webpack": "^8.0.0", - "dts-bundle-generator": "^9.2.1", + "dts-bundle-generator": "^7.2.0", "eslint": "^8.57.0", "eslint-plugin-react": "^7.26.1", "eslint-plugin-react-hooks": "^4.2.0", diff --git a/frontend/taipy-gui/webpack.config.js b/frontend/taipy-gui/webpack.config.js index b986bd542b..eaa49c601f 100644 --- a/frontend/taipy-gui/webpack.config.js +++ b/frontend/taipy-gui/webpack.config.js @@ -34,6 +34,7 @@ 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"); module.exports = (env, options) => { const envVariables = { @@ -217,5 +218,36 @@ module.exports = (env, options) => { // 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 }, + ], + }), + ], }]; }; diff --git a/taipy/__init__.py b/taipy/__init__.py index 75884936b5..474f3738bb 100644 --- a/taipy/__init__.py +++ b/taipy/__init__.py @@ -30,5 +30,8 @@ if find_spec("taipy.enterprise"): from taipy.enterprise._init import * + if find_spec("taipy.designer"): + from taipy.designer._init import * + if find_spec("taipy._run"): from taipy._run import _run as run diff --git a/taipy/gui/gui.py b/taipy/gui/gui.py index 0aec438dc7..0aa8c52f55 100644 --- a/taipy/gui/gui.py +++ b/taipy/gui/gui.py @@ -598,6 +598,12 @@ def __clean_vars_on_exit(self) -> t.Optional[t.Set[str]]: setattr(g, "update_count", update_count) # noqa: B010 return None + def _handle_connect(self): + pass + + def _handle_disconnect(self): + pass + def _manage_message(self, msg_type: _WsType, message: dict) -> None: try: client_id = None @@ -632,6 +638,8 @@ def _manage_message(self, msg_type: _WsType, message: dict) -> None: self.__handle_ws_app_id(message) elif msg_type == _WsType.GET_ROUTES.value: self.__handle_ws_get_routes() + else: + self._manage_external_message(msg_type, message) self.__send_ack(message.get("ack_id")) except Exception as e: # pragma: no cover if isinstance(e, AttributeError) and (name := message.get("name")): @@ -652,6 +660,11 @@ def _manage_message(self, msg_type: _WsType, message: dict) -> None: else: _warn(f"Decoding Message has failed: {message}", e) + # To be expanded by inheriting classes + # this will be used to handle ws messages that is not handled by the base Gui class + def _manage_external_message(self, msg_type: _WsType, message: dict) -> None: + pass + def __front_end_update( self, var_name: str, diff --git a/taipy/gui/server.py b/taipy/gui/server.py index 537083da10..6e7bfcca00 100644 --- a/taipy/gui/server.py +++ b/taipy/gui/server.py @@ -111,6 +111,14 @@ def handle_message(message, *args) -> None: elif "type" in message: gui._manage_message(message["type"], message) + @self._ws.on("connect") + def handle_connect(): + gui._handle_connect() + + @self._ws.on("disconnect") + def handle_disconnect(): + gui._handle_disconnect() + def __is_ignored(self, file_path: str) -> bool: if not hasattr(self, "_ignore_matches"): __IGNORE_FILE = ".taipyignore"