@etsoo/materialui 1.4.60 → 1.4.62

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,29 @@
1
+ import React from "react";
2
+ /**
3
+ * Button popover props
4
+ */
5
+ export type ButtonPopoverProps<T> = {
6
+ /**
7
+ * Button component
8
+ * @param callback Button click callback
9
+ * @returns Layout
10
+ */
11
+ button: (callback: (handler: HTMLElement | null) => void) => React.ReactNode;
12
+ /**
13
+ * Children component
14
+ * @param data Data
15
+ * @returns Layout
16
+ */
17
+ children: (data: T | null) => React.ReactNode;
18
+ /**
19
+ * Load data
20
+ * @returns Data promise
21
+ */
22
+ loadData?: () => Promise<T | undefined>;
23
+ };
24
+ /**
25
+ * Button popover component
26
+ * @param props Props
27
+ * @returns Component
28
+ */
29
+ export declare function ButtonPopover<T>(props: ButtonPopoverProps<T>): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,57 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Popover } from "@mui/material";
3
+ import React from "react";
4
+ /**
5
+ * Button popover component
6
+ * @param props Props
7
+ * @returns Component
8
+ */
9
+ export function ButtonPopover(props) {
10
+ // Destruct
11
+ const { button, children, loadData } = props;
12
+ // States
13
+ const [anchorEl, setAnchorEl] = React.useState(null);
14
+ const [data, setData] = React.useState(null);
15
+ const isLoadded = React.useRef(false);
16
+ const open = Boolean(anchorEl);
17
+ // Load data
18
+ React.useEffect(() => {
19
+ if (loadData && (!isLoadded.current || open)) {
20
+ // First time or when open
21
+ loadData().then((d) => {
22
+ if (d == null)
23
+ return;
24
+ setData(d);
25
+ isLoadded.current = true;
26
+ });
27
+ }
28
+ }, [loadData, open]);
29
+ // Children
30
+ const currentChildren = React.useMemo(() => children(data), [children, data]);
31
+ const handleClose = () => {
32
+ setAnchorEl(null);
33
+ };
34
+ // Layout
35
+ return (_jsxs(React.Fragment, { children: [button((handler) => setAnchorEl(handler)), _jsx(Popover, { anchorEl: anchorEl, open: open, onClose: handleClose, onClick: handleClose, transformOrigin: { horizontal: "right", vertical: "top" }, anchorOrigin: { horizontal: "right", vertical: "bottom" }, slotProps: {
36
+ paper: {
37
+ elevation: 0,
38
+ sx: {
39
+ overflow: "visible",
40
+ filter: "drop-shadow(0px 2px 8px rgba(0,0,0,0.32))",
41
+ mt: 1,
42
+ "&::before": {
43
+ content: '""',
44
+ display: "block",
45
+ position: "absolute",
46
+ top: 0,
47
+ right: 14,
48
+ width: 10,
49
+ height: 10,
50
+ bgcolor: "background.paper",
51
+ transform: "translateY(-50%) rotate(45deg)",
52
+ zIndex: 0
53
+ }
54
+ }
55
+ }
56
+ }, children: currentChildren })] }));
57
+ }
package/lib/NotifierMU.js CHANGED
@@ -63,7 +63,7 @@ export class NotificationMU extends NotificationReact {
63
63
  await this.returnValue(undefined);
64
64
  return true;
65
65
  };
66
- return (_jsxs(Dialog, { open: this.open, PaperComponent: draggable ? DraggablePaperComponent : undefined, className: className, fullWidth: fullWidth, maxWidth: maxWidth, fullScreen: fullScreen, ...options, children: [_jsxs(IconDialogTitle, { draggable: draggable, className: "dialog-title", children: [icon, _jsx("span", { className: "dialogTitle", children: title }), closable && (_jsx(IconButton, { className: "MuiDialogContent-root-close-button", size: "small", onClick: () => this.returnValue("CLOSE"), children: _jsx(CloseIcon, {}) }))] }), _jsxs(DialogContent, { children: [typeof this.content === "string" ? (_jsx(DialogContentText, { children: this.content })) : (this.content), inputs] }), _jsx(DialogActions, { children: buttons
66
+ return (_jsxs(Dialog, { open: this.open, PaperComponent: draggable ? DraggablePaperComponent : undefined, className: className, fullWidth: fullWidth, maxWidth: maxWidth, fullScreen: fullScreen, ...options, children: [_jsxs(IconDialogTitle, { draggable: draggable, className: draggable ? "dialog-title draggable-dialog-title" : "dialog-title", children: [icon, _jsx("span", { className: "dialogTitle", children: title }), closable && (_jsx(IconButton, { className: "MuiDialogContent-root-close-button", size: "small", onClick: () => this.returnValue("CLOSE"), children: _jsx(CloseIcon, {}) }))] }), _jsxs(DialogContent, { children: [typeof this.content === "string" ? (_jsx(DialogContentText, { children: this.content })) : (this.content), inputs] }), _jsx(DialogActions, { children: buttons
67
67
  ? buttons(this, callback)
68
68
  : primaryButton && (_jsx(LoadingButton, { ...setupProps, onClick: callback, autoFocus: true, ...primaryButtonProps, children: okLabel })) })] }, this.id));
69
69
  }
@@ -78,7 +78,7 @@ export class NotificationMU extends NotificationReact {
78
78
  await this.returnValue(value);
79
79
  return true;
80
80
  };
81
- return (_jsxs(Dialog, { open: this.open, PaperComponent: draggable ? DraggablePaperComponent : undefined, className: className, fullWidth: fullWidth, maxWidth: maxWidth, fullScreen: fullScreen, ...options, children: [_jsxs(IconDialogTitle, { draggable: draggable, className: "dialog-title", children: [_jsx(Help, { color: "action" }), _jsx("span", { className: "dialogTitle", children: title }), closable && (_jsx(IconButton, { className: "MuiDialogContent-root-close-button", size: "small", onClick: () => this.returnValue("CLOSE"), children: _jsx(CloseIcon, {}) }))] }), _jsxs(DialogContent, { children: [typeof this.content === "string" ? (_jsx(DialogContentText, { children: this.content })) : (this.content), inputs] }), _jsx(DialogActions, { children: buttons ? (buttons(this, callback)) : (_jsxs(React.Fragment, { children: [cancelButton && (_jsx(LoadingButton, { color: "secondary", onClick: async (event) => await callback(event, false), children: cancelLabel })), primaryButton && (_jsx(LoadingButton, { color: "primary", onClick: async (event) => await callback(event, true), autoFocus: true, ...primaryButtonProps, children: okLabel }))] })) })] }, this.id));
81
+ return (_jsxs(Dialog, { open: this.open, PaperComponent: draggable ? DraggablePaperComponent : undefined, className: className, fullWidth: fullWidth, maxWidth: maxWidth, fullScreen: fullScreen, ...options, children: [_jsxs(IconDialogTitle, { draggable: draggable, className: draggable ? "dialog-title draggable-dialog-title" : "dialog-title", children: [_jsx(Help, { color: "action" }), _jsx("span", { className: "dialogTitle", children: title }), closable && (_jsx(IconButton, { className: "MuiDialogContent-root-close-button", size: "small", onClick: () => this.returnValue("CLOSE"), children: _jsx(CloseIcon, {}) }))] }), _jsxs(DialogContent, { children: [typeof this.content === "string" ? (_jsx(DialogContentText, { children: this.content })) : (this.content), inputs] }), _jsx(DialogActions, { children: buttons ? (buttons(this, callback)) : (_jsxs(React.Fragment, { children: [cancelButton && (_jsx(LoadingButton, { color: "secondary", onClick: async (event) => await callback(event, false), children: cancelLabel })), primaryButton && (_jsx(LoadingButton, { color: "primary", onClick: async (event) => await callback(event, true), autoFocus: true, ...primaryButtonProps, children: okLabel }))] })) })] }, this.id));
82
82
  }
83
83
  createMessageColor() {
84
84
  if (this.type === NotificationMessageType.Danger)
@@ -177,7 +177,7 @@ export class NotificationMU extends NotificationReact {
177
177
  event.preventDefault();
178
178
  event.currentTarget.elements.namedItem("okButton")?.click();
179
179
  return false;
180
- }, children: [_jsxs(IconDialogTitle, { draggable: draggable, className: "dialog-title", children: [_jsx(Info, { color: "primary" }), _jsx("span", { className: "dialogTitle", children: title }), closable && (_jsx(IconButton, { className: "MuiDialogContent-root-close-button", size: "small", onClick: () => this.returnValue("CLOSE"), children: _jsx(CloseIcon, {}) }))] }), _jsxs(DialogContent, { children: [typeof this.content === "string" ? (_jsx(DialogContentText, { children: this.content })) : (this.content), localInputs, _jsx(Typography, { variant: "caption", display: "block", ref: errorRef, color: "error" })] }), _jsx(DialogActions, { children: buttons ? (buttons(this, handleSubmit)) : (_jsxs(React.Fragment, { children: [cancelButton && (_jsx(Button, { color: "secondary", onClick: () => {
180
+ }, children: [_jsxs(IconDialogTitle, { draggable: draggable, className: draggable ? "dialog-title draggable-dialog-title" : "dialog-title", children: [_jsx(Info, { color: "primary" }), _jsx("span", { className: "dialogTitle", children: title }), closable && (_jsx(IconButton, { className: "MuiDialogContent-root-close-button", size: "small", onClick: () => this.returnValue("CLOSE"), children: _jsx(CloseIcon, {}) }))] }), _jsxs(DialogContent, { children: [typeof this.content === "string" ? (_jsx(DialogContentText, { children: this.content })) : (this.content), localInputs, _jsx(Typography, { variant: "caption", display: "block", ref: errorRef, color: "error" })] }), _jsx(DialogActions, { children: buttons ? (buttons(this, handleSubmit)) : (_jsxs(React.Fragment, { children: [cancelButton && (_jsx(Button, { color: "secondary", onClick: () => {
181
181
  if (this.onReturn)
182
182
  this.onReturn(undefined);
183
183
  this.dismiss();
@@ -233,7 +233,8 @@ export class ReactApp extends CoreApp {
233
233
  */
234
234
  async tryLogin(data) {
235
235
  // Destruct
236
- const { onFailure = () => {
236
+ const { onFailure = (type) => {
237
+ console.log(`Try login failed: ${type}.`);
237
238
  this.toLoginPage(rest);
238
239
  }, onSuccess, ...rest } = data ?? {};
239
240
  // Check status
@@ -67,6 +67,7 @@ export declare class ServiceApp<U extends IServiceUser = IServiceUser, P extends
67
67
  * @param payload Payload
68
68
  */
69
69
  switchOrg(organizationId: number, fromOrganizationId?: number, payload?: IApiPayload<IActionResult<U & ServiceUserToken>>): Promise<IActionResult<U & ServiceUserToken> | undefined>;
70
+ protected refreshTokenSucceed(user: U, token: string, callback?: (result?: boolean | IActionResult) => boolean | void): Promise<void>;
70
71
  /**
71
72
  * Try login
72
73
  * @param params Login parameters
@@ -180,6 +180,35 @@ export class ServiceApp extends ReactApp {
180
180
  this.userLoginEx(user, core, true);
181
181
  return result;
182
182
  }
183
+ async refreshTokenSucceed(user, token, callback) {
184
+ // Check core system token
185
+ const coreToken = this.storage.getData(coreTokenKey);
186
+ if (!coreToken) {
187
+ callback?.({ ok: false, type: "noData", title: "Core token is blank" });
188
+ return;
189
+ }
190
+ const coreTokenDecrypted = this.decrypt(coreToken);
191
+ if (!coreTokenDecrypted) {
192
+ callback?.({
193
+ ok: false,
194
+ type: "noData",
195
+ title: "Core token decrypted is blank"
196
+ });
197
+ return;
198
+ }
199
+ // Call the core system API refresh token
200
+ const data = await this.apiRefreshTokenData(this.coreApi, {
201
+ token: coreTokenDecrypted,
202
+ appId: this.settings.appId
203
+ });
204
+ if (data == null)
205
+ return;
206
+ // Cache the core system refresh token
207
+ // Follow similar logic in userLoginEx
208
+ this.saveCoreToken(data);
209
+ // Call the super
210
+ await super.refreshTokenSucceed(user, token, callback);
211
+ }
183
212
  /**
184
213
  * Try login
185
214
  * @param params Login parameters
@@ -187,37 +216,13 @@ export class ServiceApp extends ReactApp {
187
216
  async tryLogin(params) {
188
217
  // Destruct
189
218
  params ??= {};
190
- let { onFailure, onSuccess, ...rest } = params;
219
+ let { onFailure, ...rest } = params;
191
220
  if (onFailure == null) {
192
221
  onFailure = params.onFailure = (type) => {
193
222
  console.log(`Try login failed: ${type}.`);
194
223
  this.toLoginPage(rest);
195
224
  };
196
225
  }
197
- // Check core system token
198
- const coreToken = this.storage.getData(coreTokenKey);
199
- if (!coreToken) {
200
- onFailure("ServiceAppCoreTokenNoData");
201
- return false;
202
- }
203
- const coreTokenDecrypted = this.decrypt(coreToken);
204
- if (!coreTokenDecrypted) {
205
- onFailure("ServiceAppCoreTokenNoDecryptedData");
206
- return false;
207
- }
208
- params.onSuccess = () => {
209
- // Call the core system API refresh token
210
- this.apiRefreshTokenData(this.coreApi, {
211
- token: coreTokenDecrypted,
212
- appId: this.settings.appId
213
- }).then((data) => {
214
- if (data == null)
215
- return;
216
- // Cache the core system refresh token
217
- this.saveCoreToken(data);
218
- onSuccess?.();
219
- });
220
- };
221
226
  return await super.tryLogin(params);
222
227
  }
223
228
  }
package/lib/index.d.ts CHANGED
@@ -38,6 +38,7 @@ export * from "./AutocompleteExtendedProps";
38
38
  export * from "./BackButton";
39
39
  export * from "./BridgeCloseButton";
40
40
  export * from "./ButtonLink";
41
+ export * from "./ButtonPopover";
41
42
  export * from "./ComboBox";
42
43
  export * from "./ComboBoxMultiple";
43
44
  export * from "./ComboBoxPro";
package/lib/index.js CHANGED
@@ -38,6 +38,7 @@ export * from "./AutocompleteExtendedProps";
38
38
  export * from "./BackButton";
39
39
  export * from "./BridgeCloseButton";
40
40
  export * from "./ButtonLink";
41
+ export * from "./ButtonPopover";
41
42
  export * from "./ComboBox";
42
43
  export * from "./ComboBoxMultiple";
43
44
  export * from "./ComboBoxPro";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsoo/materialui",
3
- "version": "1.4.60",
3
+ "version": "1.4.62",
4
4
  "description": "TypeScript Material-UI Implementation",
5
5
  "main": "lib/index.js",
6
6
  "type": "module",
@@ -35,9 +35,9 @@
35
35
  "@emotion/css": "^11.13.5",
36
36
  "@emotion/react": "^11.14.0",
37
37
  "@emotion/styled": "^11.14.0",
38
- "@etsoo/appscript": "^1.5.84",
38
+ "@etsoo/appscript": "^1.5.87",
39
39
  "@etsoo/notificationbase": "^1.1.54",
40
- "@etsoo/react": "^1.8.17",
40
+ "@etsoo/react": "^1.8.20",
41
41
  "@etsoo/shared": "^1.2.55",
42
42
  "@mui/icons-material": "^6.3.1",
43
43
  "@mui/material": "^6.3.1",
@@ -72,7 +72,7 @@
72
72
  "@types/react-window": "^1.8.8",
73
73
  "@vitejs/plugin-react": "^4.3.4",
74
74
  "jsdom": "^25.0.1",
75
- "typescript": "^5.7.2",
75
+ "typescript": "^5.7.3",
76
76
  "vitest": "^2.1.8"
77
77
  }
78
78
  }
@@ -0,0 +1,102 @@
1
+ import { Popover } from "@mui/material";
2
+ import React from "react";
3
+
4
+ /**
5
+ * Button popover props
6
+ */
7
+ export type ButtonPopoverProps<T> = {
8
+ /**
9
+ * Button component
10
+ * @param callback Button click callback
11
+ * @returns Layout
12
+ */
13
+ button: (callback: (handler: HTMLElement | null) => void) => React.ReactNode;
14
+
15
+ /**
16
+ * Children component
17
+ * @param data Data
18
+ * @returns Layout
19
+ */
20
+ children: (data: T | null) => React.ReactNode;
21
+
22
+ /**
23
+ * Load data
24
+ * @returns Data promise
25
+ */
26
+ loadData?: () => Promise<T | undefined>;
27
+ };
28
+
29
+ /**
30
+ * Button popover component
31
+ * @param props Props
32
+ * @returns Component
33
+ */
34
+ export function ButtonPopover<T>(props: ButtonPopoverProps<T>) {
35
+ // Destruct
36
+ const { button, children, loadData } = props;
37
+
38
+ // States
39
+ const [anchorEl, setAnchorEl] = React.useState<HTMLElement | null>(null);
40
+ const [data, setData] = React.useState<T | null>(null);
41
+ const isLoadded = React.useRef(false);
42
+
43
+ const open = Boolean(anchorEl);
44
+
45
+ // Load data
46
+ React.useEffect(() => {
47
+ if (loadData && (!isLoadded.current || open)) {
48
+ // First time or when open
49
+ loadData().then((d) => {
50
+ if (d == null) return;
51
+ setData(d);
52
+ isLoadded.current = true;
53
+ });
54
+ }
55
+ }, [loadData, open]);
56
+
57
+ // Children
58
+ const currentChildren = React.useMemo(() => children(data), [children, data]);
59
+
60
+ const handleClose = () => {
61
+ setAnchorEl(null);
62
+ };
63
+
64
+ // Layout
65
+ return (
66
+ <React.Fragment>
67
+ {button((handler) => setAnchorEl(handler))}
68
+ <Popover
69
+ anchorEl={anchorEl}
70
+ open={open}
71
+ onClose={handleClose}
72
+ onClick={handleClose}
73
+ transformOrigin={{ horizontal: "right", vertical: "top" }}
74
+ anchorOrigin={{ horizontal: "right", vertical: "bottom" }}
75
+ slotProps={{
76
+ paper: {
77
+ elevation: 0,
78
+ sx: {
79
+ overflow: "visible",
80
+ filter: "drop-shadow(0px 2px 8px rgba(0,0,0,0.32))",
81
+ mt: 1,
82
+ "&::before": {
83
+ content: '""',
84
+ display: "block",
85
+ position: "absolute",
86
+ top: 0,
87
+ right: 14,
88
+ width: 10,
89
+ height: 10,
90
+ bgcolor: "background.paper",
91
+ transform: "translateY(-50%) rotate(45deg)",
92
+ zIndex: 0
93
+ }
94
+ }
95
+ }
96
+ }}
97
+ >
98
+ {currentChildren}
99
+ </Popover>
100
+ </React.Fragment>
101
+ );
102
+ }
@@ -123,7 +123,12 @@ export class NotificationMU extends NotificationReact {
123
123
  fullScreen={fullScreen}
124
124
  {...options}
125
125
  >
126
- <IconDialogTitle draggable={draggable} className="dialog-title">
126
+ <IconDialogTitle
127
+ draggable={draggable}
128
+ className={
129
+ draggable ? "dialog-title draggable-dialog-title" : "dialog-title"
130
+ }
131
+ >
127
132
  {icon}
128
133
  <span className="dialogTitle">{title}</span>
129
134
  {closable && (
@@ -204,7 +209,12 @@ export class NotificationMU extends NotificationReact {
204
209
  fullScreen={fullScreen}
205
210
  {...options}
206
211
  >
207
- <IconDialogTitle draggable={draggable} className="dialog-title">
212
+ <IconDialogTitle
213
+ draggable={draggable}
214
+ className={
215
+ draggable ? "dialog-title draggable-dialog-title" : "dialog-title"
216
+ }
217
+ >
208
218
  <Help color="action" />
209
219
  <span className="dialogTitle">{title}</span>
210
220
  {closable && (
@@ -438,7 +448,12 @@ export class NotificationMU extends NotificationReact {
438
448
  return false;
439
449
  }}
440
450
  >
441
- <IconDialogTitle draggable={draggable} className="dialog-title">
451
+ <IconDialogTitle
452
+ draggable={draggable}
453
+ className={
454
+ draggable ? "dialog-title draggable-dialog-title" : "dialog-title"
455
+ }
456
+ >
442
457
  <Info color="primary" />
443
458
  <span className="dialogTitle">{title}</span>
444
459
  {closable && (
@@ -427,7 +427,8 @@ export class ReactApp<
427
427
  override async tryLogin(data?: AppTryLoginParams) {
428
428
  // Destruct
429
429
  const {
430
- onFailure = () => {
430
+ onFailure = (type: string) => {
431
+ console.log(`Try login failed: ${type}.`);
431
432
  this.toLoginPage(rest);
432
433
  },
433
434
  onSuccess,
@@ -244,6 +244,44 @@ export class ServiceApp<
244
244
  return result;
245
245
  }
246
246
 
247
+ protected override async refreshTokenSucceed(
248
+ user: U,
249
+ token: string,
250
+ callback?: (result?: boolean | IActionResult) => boolean | void
251
+ ): Promise<void> {
252
+ // Check core system token
253
+ const coreToken = this.storage.getData<string>(coreTokenKey);
254
+ if (!coreToken) {
255
+ callback?.({ ok: false, type: "noData", title: "Core token is blank" });
256
+ return;
257
+ }
258
+
259
+ const coreTokenDecrypted = this.decrypt(coreToken);
260
+ if (!coreTokenDecrypted) {
261
+ callback?.({
262
+ ok: false,
263
+ type: "noData",
264
+ title: "Core token decrypted is blank"
265
+ });
266
+ return;
267
+ }
268
+
269
+ // Call the core system API refresh token
270
+ const data = await this.apiRefreshTokenData(this.coreApi, {
271
+ token: coreTokenDecrypted,
272
+ appId: this.settings.appId
273
+ });
274
+
275
+ if (data == null) return;
276
+
277
+ // Cache the core system refresh token
278
+ // Follow similar logic in userLoginEx
279
+ this.saveCoreToken(data);
280
+
281
+ // Call the super
282
+ await super.refreshTokenSucceed(user, token, callback);
283
+ }
284
+
247
285
  /**
248
286
  * Try login
249
287
  * @param params Login parameters
@@ -251,7 +289,7 @@ export class ServiceApp<
251
289
  override async tryLogin(params?: AppTryLoginParams) {
252
290
  // Destruct
253
291
  params ??= {};
254
- let { onFailure, onSuccess, ...rest } = params;
292
+ let { onFailure, ...rest } = params;
255
293
 
256
294
  if (onFailure == null) {
257
295
  onFailure = params.onFailure = (type) => {
@@ -260,34 +298,6 @@ export class ServiceApp<
260
298
  };
261
299
  }
262
300
 
263
- // Check core system token
264
- const coreToken = this.storage.getData<string>(coreTokenKey);
265
- if (!coreToken) {
266
- onFailure("ServiceAppCoreTokenNoData");
267
- return false;
268
- }
269
-
270
- const coreTokenDecrypted = this.decrypt(coreToken);
271
- if (!coreTokenDecrypted) {
272
- onFailure("ServiceAppCoreTokenNoDecryptedData");
273
- return false;
274
- }
275
-
276
- params.onSuccess = () => {
277
- // Call the core system API refresh token
278
- this.apiRefreshTokenData(this.coreApi, {
279
- token: coreTokenDecrypted,
280
- appId: this.settings.appId
281
- }).then((data) => {
282
- if (data == null) return;
283
-
284
- // Cache the core system refresh token
285
- this.saveCoreToken(data);
286
-
287
- onSuccess?.();
288
- });
289
- };
290
-
291
301
  return await super.tryLogin(params);
292
302
  }
293
303
  }
package/src/index.ts CHANGED
@@ -43,6 +43,7 @@ export * from "./AutocompleteExtendedProps";
43
43
  export * from "./BackButton";
44
44
  export * from "./BridgeCloseButton";
45
45
  export * from "./ButtonLink";
46
+ export * from "./ButtonPopover";
46
47
  export * from "./ComboBox";
47
48
  export * from "./ComboBoxMultiple";
48
49
  export * from "./ComboBoxPro";