diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 000000000..39649ae48
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,7 @@
+{
+ "workbench.colorCustomizations": {
+ "activityBar.background": "#2A3012",
+ "titleBar.activeBackground": "#3B431A",
+ "titleBar.activeForeground": "#F9FAF2"
+ }
+}
\ No newline at end of file
diff --git a/client/packages/lowcoder/src/comps/hooks/hookComp.tsx b/client/packages/lowcoder/src/comps/hooks/hookComp.tsx
index dd2a6b1dc..8002436e1 100644
--- a/client/packages/lowcoder/src/comps/hooks/hookComp.tsx
+++ b/client/packages/lowcoder/src/comps/hooks/hookComp.tsx
@@ -29,6 +29,7 @@ import { useInterval, useTitle, useWindowSize } from "react-use";
import { useCurrentUser } from "util/currentUser";
import { LocalStorageComp } from "./localStorageComp";
import { MessageComp } from "./messageComp";
+import { ToastComp } from "./toastComp";
import { ThemeComp } from "./themeComp";
import UrlParamsHookComp from "./UrlParamsHookComp";
import { UtilsComp } from "./utilsComp";
@@ -94,6 +95,7 @@ const HookMap: HookCompMapRawType = {
momentJsLib: DayJsLib, // old components use this hook
utils: UtilsComp,
message: MessageComp,
+ toast: ToastComp,
localStorage: LocalStorageComp,
modal: ModalComp,
meeting: VideoMeetingControllerComp,
diff --git a/client/packages/lowcoder/src/comps/hooks/hookCompTypes.tsx b/client/packages/lowcoder/src/comps/hooks/hookCompTypes.tsx
index a985a8e72..617f2f6c1 100644
--- a/client/packages/lowcoder/src/comps/hooks/hookCompTypes.tsx
+++ b/client/packages/lowcoder/src/comps/hooks/hookCompTypes.tsx
@@ -12,6 +12,7 @@ const AllHookComp = [
"momentJsLib",
"utils",
"message",
+ "toast",
"localStorage",
"currentUser",
"screenInfo",
@@ -58,6 +59,7 @@ const HookCompConfig: Record<
},
utils: { category: "hide" },
message: { category: "hide" },
+ toast: { category: "hide" },
};
// Get hook component category
diff --git a/client/packages/lowcoder/src/comps/hooks/hookListComp.tsx b/client/packages/lowcoder/src/comps/hooks/hookListComp.tsx
index 3f28e2d1a..7b267ec19 100644
--- a/client/packages/lowcoder/src/comps/hooks/hookListComp.tsx
+++ b/client/packages/lowcoder/src/comps/hooks/hookListComp.tsx
@@ -19,6 +19,7 @@ const defaultHookListValue = [
{ compType: "lodashJsLib", name: "_" },
{ compType: "utils", name: "utils" },
{ compType: "message", name: "message" },
+ { compType: "toast", name: "toast" },
{ compType: "localStorage", name: "localStorage" },
{ compType: "currentUser", name: "currentUser" },
{ compType: "screenInfo", name: "screenInfo" },
diff --git a/client/packages/lowcoder/src/comps/hooks/messageComp.ts b/client/packages/lowcoder/src/comps/hooks/messageComp.ts
index e0e6451cb..028c37691 100644
--- a/client/packages/lowcoder/src/comps/hooks/messageComp.ts
+++ b/client/packages/lowcoder/src/comps/hooks/messageComp.ts
@@ -11,7 +11,7 @@ const params: ParamsConfig = [
{ name: "options", type: "JSON" },
];
-const showMessage = (params: EvalParamType[], level: "info" | "success" | "warning" | "error") => {
+const showMessage = (params: EvalParamType[], level: "info" | "success" | "loading" | "warning" | "error") => {
const text = params?.[0];
const options = params?.[1] as JSONObject;
const duration = options?.["duration"] ?? 3;
@@ -35,6 +35,12 @@ MessageComp = withMethodExposing(MessageComp, [
showMessage(params, "success");
},
},
+ {
+ method: { name: "loading", description: trans("messageComp.loading"), params: params },
+ execute: (comp, params) => {
+ showMessage(params, "loading");
+ },
+ },
{
method: { name: "warn", description: trans("messageComp.warn"), params: params },
execute: (comp, params) => {
diff --git a/client/packages/lowcoder/src/comps/hooks/toastComp.ts b/client/packages/lowcoder/src/comps/hooks/toastComp.ts
new file mode 100644
index 000000000..8b80e89b9
--- /dev/null
+++ b/client/packages/lowcoder/src/comps/hooks/toastComp.ts
@@ -0,0 +1,95 @@
+import { withMethodExposing } from "../generators/withMethodExposing";
+import { simpleMultiComp } from "../generators";
+import { withExposingConfigs } from "../generators/withExposing";
+import { EvalParamType, ParamsConfig } from "../controls/actionSelector/executeCompTypes";
+import { JSONObject } from "../../util/jsonTypes";
+import { trans } from "i18n";
+import { notificationInstance } from "lowcoder-design";
+import type { ArgsProps, NotificationPlacement } from 'antd/es/notification/interface';
+
+const params: ParamsConfig = [
+ { name: "text", type: "string" },
+ { name: "options", type: "JSON" },
+];
+
+const showNotification = (
+ params: EvalParamType[],
+ level: "open" | "info" | "success" | "warning" | "error"
+) => {
+ const text = params?.[0] as string;
+ const options = params?.[1] as JSONObject;
+
+ const { message , duration, id, placement, dismissible } = options;
+
+ const closeIcon: boolean | undefined = dismissible === true ? undefined : (dismissible === false ? false : undefined);
+
+ const durationNumberOrNull: number | null = typeof duration === 'number' ? duration : null;
+
+ const notificationArgs: ArgsProps = {
+ message: text,
+ description: message as React.ReactNode,
+ duration: durationNumberOrNull ?? 3,
+ key: id as React.Key,
+ placement: placement as NotificationPlacement ?? "bottomRight",
+ closeIcon: closeIcon as boolean,
+ };
+
+ // Use notificationArgs to trigger the notification
+
+ text && notificationInstance[level](notificationArgs);
+};
+
+const destroy = (
+ params: EvalParamType[]
+) => {
+ // Extract the id from the params
+ const id = params[0] as React.Key;
+
+ // Call notificationInstance.destroy with the provided id
+ notificationInstance.destroy(id);
+};
+
+//what we would like to expose: title, text, duration, id, btn-obj, onClose, placement
+
+const ToastCompBase = simpleMultiComp({});
+
+export let ToastComp = withExposingConfigs(ToastCompBase, []);
+
+ToastComp = withMethodExposing(ToastComp, [
+ {
+ method: { name: "destroy", description: trans("toastComp.destroy"), params: params },
+ execute: (comp, params) => destroy(params),
+ },
+ {
+ method: { name: "open", description: trans("toastComp.info"), params: params },
+ execute: (comp, params) => {
+ showNotification(params, "open");
+ },
+ },
+ {
+ method: { name: "info", description: trans("toastComp.info"), params: params },
+ execute: (comp, params) => {
+ showNotification(params, "info");
+ },
+ },
+ {
+ method: { name: "success", description: trans("toastComp.success"), params: params },
+ execute: (comp, params) => {
+ showNotification(params, "success");
+ },
+ },
+ {
+ method: { name: "warn", description: trans("toastComp.warn"), params: params },
+ execute: (comp, params) => {
+ showNotification(params, "warning");
+ },
+ },
+ {
+ method: { name: "error", description: trans("toastComp.error"), params: params },
+ execute: (comp, params) => {
+ showNotification(params, "error");
+ },
+ },
+]);
+
+
diff --git a/client/packages/lowcoder/src/i18n/locales/de.ts b/client/packages/lowcoder/src/i18n/locales/de.ts
index a6bcf9f00..feda98419 100644
--- a/client/packages/lowcoder/src/i18n/locales/de.ts
+++ b/client/packages/lowcoder/src/i18n/locales/de.ts
@@ -1615,10 +1615,18 @@ export const de = {
},
"messageComp": {
"info": "Eine Benachrichtigung senden",
+ "loading": "Ladebestätigung senden",
"success": "Erfolgsbenachrichtigung senden",
"warn": "Eine Warnmeldung senden",
"error": "Eine Fehlerbenachrichtigung senden"
},
+ "tostComp": {
+ "info": "Eine Benachrichtigung senden",
+ "loading": "Ladebestätigung senden",
+ "success": "Erfolgsbenachrichtigung senden",
+ "warn": "Eine Warnmeldung senden",
+ "error": "Eine Fehlerbenachrichtigung senden"
+},
"themeComp": {
"switchTo": "Thema wechseln"
},
diff --git a/client/packages/lowcoder/src/i18n/locales/en.ts b/client/packages/lowcoder/src/i18n/locales/en.ts
index 7e97eaad9..2ac3be091 100644
--- a/client/packages/lowcoder/src/i18n/locales/en.ts
+++ b/client/packages/lowcoder/src/i18n/locales/en.ts
@@ -1778,6 +1778,15 @@ export const en = {
},
"messageComp": {
"info": "Send a Notification",
+ "loading": "Send a Loading Notification",
+ "success": "Send a Success Notification",
+ "warn": "Send a Warning Notification",
+ "error": "Send an Error Notification"
+ },
+ "toastComp": {
+ "destroy": "close a Notification",
+ "info": "Send a Notification",
+ "loading": "Send a Loading Notification",
"success": "Send a Success Notification",
"warn": "Send a Warning Notification",
"error": "Send an Error Notification"
diff --git a/client/packages/lowcoder/src/i18n/locales/zh.ts b/client/packages/lowcoder/src/i18n/locales/zh.ts
index e50500fb4..921a09e14 100644
--- a/client/packages/lowcoder/src/i18n/locales/zh.ts
+++ b/client/packages/lowcoder/src/i18n/locales/zh.ts
@@ -1686,6 +1686,14 @@ utilsComp: {
},
messageComp: {
info: "发送通知",
+ loading: "发送加载通知",
+ success: "发送成功通知",
+ warn: "发送警告通知",
+ error: "发送错误通知",
+},
+toastComp: {
+ info: "发送通知",
+ loading: "发送加载通知",
success: "发送成功通知",
warn: "发送警告通知",
error: "发送错误通知",
diff --git a/docs/.gitbook/assets/builtin-js-toasts.png b/docs/.gitbook/assets/builtin-js-toasts.png
new file mode 100644
index 000000000..cfaf58332
Binary files /dev/null and b/docs/.gitbook/assets/builtin-js-toasts.png differ
diff --git a/docs/build-apps/write-javascript/built-in-javascript-functions.md b/docs/build-apps/write-javascript/built-in-javascript-functions.md
index 757c30511..0bbbb6db0 100644
--- a/docs/build-apps/write-javascript/built-in-javascript-functions.md
+++ b/docs/build-apps/write-javascript/built-in-javascript-functions.md
@@ -109,18 +109,45 @@ utils.copyToClipboard( input1.value )
Use `message` methods to send a global alert notification, which displays at the top of the screen and lasts for 3 seconds by default. Each of the following four methods supports a unique display style.
```javascript
-// messageInstance.info( text: string, options?: {duration: number = 3 } )
-messageInstance.info("Please confirm your information", { duration: 10 })
-// messageInstance.success( text: string, options?: {duration: number = 3 } )
-messageInstance.success("Query runs successfully", { duration: 10 })
-// messageInstance.warning( text: string, options?: {duration: number = 3 } )
-messageInstance.warning("Warning", { duration: 10 })
-// messageInstance.error( text: string, options?: {duration: number = 3 } )
-messageInstance.error("Query runs with error", { duration: 10 })
+// message.info( text: string, options?: {duration: number = 3 } )
+message.info("Please confirm your information", { duration: 10 })
+// message.loading( text: string, options?: {duration: number = 3 } )
+message.loading("Query is running", { duration: 5 })
+// message.success( text: string, options?: {duration: number = 3 } )
+message.success("Query runs successfully", { duration: 10 })
+// message.warning( text: string, options?: {duration: number = 3 } )
+message.warning("Warning", { duration: 10 })
+// message.error( text: string, options?: {duration: number = 3 } )
+message.error("Query runs with error", { duration: 10 })
```
+## toast - dismissible stack-able notifications
+
+Use `toast` methods to send a notification, which displays at the top of the screen and lasts for 3 seconds by default. Each of the following five methods supports a unique display style. After 3 toasts they will be stacked.
+
+The id field can be used to update previous toasts. Or used to destroy the previous toast.
+
+Destroy function used without an id will remove all toast.
+
+```javascript
+// toast.open( title: string, options?: { message?: string, duration?: number = 3, id?: string, placement?: "top" | "topLeft" | "topRight" | "bottom" | "bottomRight", "bottomRight" = "bottomRight", dismissible?: boolean = true } )
+toast.open("This Is a Notification", {message: "I do not go away automatically.", duration: 0})
+// toast.info( title: string, options?: { message?: string, duration?: number = 3, id?: string, placement?: "top" | "topLeft" | "topRight" | "bottom" | "bottomRight", "bottomRight" = "bottomRight", dismissible?: boolean = true } )
+toast.info("Order #1519", {message: "Shipped out on Tuesday, Jan 3rd.", duration: 5})
+// toast.success( title: string, options?: { message?: string, duration?: number = 3, id?: string, placement?: "top" | "topLeft" | "topRight" | "bottom" | "bottomRight", "bottomRight" = "bottomRight", dismissible?: boolean = true } )
+toast.success("Query runs successfully", { duration: 10 })
+// toast.warn( title: string, options?: { message?: string, duration?: number = 3, id?: string, placement?: "top" | "topLeft" | "topRight" | "bottom" | "bottomRight", "bottomRight" = "bottomRight", dismissible?: boolean = true } )
+toast.warn("Duplicate Action", {message: "The email was previously sent on Jan 3rd. Click the button again to send.", duration: 5})
+// toast.error( title: string, options?: { message?: string, duration?: number = 3, id?: string, placement?: "top" | "topLeft" | "topRight" | "bottom" | "bottomRight", "bottomRight" = "bottomRight", dismissible?: boolean = true } )
+toast.error("Your credentials were invalid", {message: "You have 5 tries left", duration: 5})
+//toast.destroy(id?: string)
+toast.destroy()
+```
+
+
+
## localStorage
Use `localStorage` methods to store and manage key-value pair data locally, which is not reset when the app refreshes, and can be accessed in any app within the workspace using `localStorage.values`.