@etsoo/react 1.5.13 → 1.5.17

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.
@@ -108,19 +108,6 @@ export declare class ReactApp<S extends IAppSettings, D extends IUser, P extends
108
108
  * @param name Application name
109
109
  */
110
110
  constructor(settings: S, name: string);
111
- /**
112
- * Get parsed Url under bridge host
113
- * @param url Url
114
- * @returns Parsed Url
115
- */
116
- protected getHostUrl(url: string): string;
117
- /**
118
- * Get parsed Url under bridge host
119
- * @param url Url
120
- * @param name App name in the host environment
121
- * @returns Parsed Url
122
- */
123
- protected getHostUrlBase(url: string, name: string): string;
124
111
  /**
125
112
  * Override alert action result
126
113
  * @param result Action result
@@ -1,6 +1,5 @@
1
1
  import { ActionResultError, BridgeUtils, CoreApp, createClient } from '@etsoo/appscript';
2
2
  import { WindowStorage } from '@etsoo/shared';
3
- import { navigate } from '@reach/router';
4
3
  import React from 'react';
5
4
  import { NotifierMU } from '../mu/NotifierMU';
6
5
  import { ProgressCount } from '../mu/ProgressCount';
@@ -8,7 +7,7 @@ import { CultureState } from '../states/CultureState';
8
7
  import { PageActionType, PageState } from '../states/PageState';
9
8
  import { UserActionType, UserState } from '../states/UserState';
10
9
  import { Labels } from './Labels';
11
- import { Utils } from './Utils';
10
+ import { ReactUtils } from './ReactUtils';
12
11
  /**
13
12
  * Global application
14
13
  */
@@ -66,9 +65,9 @@ export class ReactApp extends CoreApp {
66
65
  */
67
66
  this.userState = new UserState();
68
67
  this.history =
69
- window.location.hostname === ''
70
- ? Utils.getMemoryHistory(this.getHostUrl(window.location.href))
71
- : undefined;
68
+ BridgeUtils.host == null
69
+ ? undefined
70
+ : ReactUtils.getMemoryHistory(BridgeUtils.host.getStartUrl());
72
71
  this.cultureState = new CultureState(settings.currentCulture);
73
72
  this.pageState = new PageState();
74
73
  globalApp = this;
@@ -91,31 +90,6 @@ export class ReactApp extends CoreApp {
91
90
  ReactApp._notifierProvider = NotifierMU.setup();
92
91
  return NotifierMU.instance;
93
92
  }
94
- /**
95
- * Get parsed Url under bridge host
96
- * @param url Url
97
- * @returns Parsed Url
98
- */
99
- getHostUrl(url) {
100
- return this.getHostUrlBase(url, 'core');
101
- }
102
- /**
103
- * Get parsed Url under bridge host
104
- * @param url Url
105
- * @param name App name in the host environment
106
- * @returns Parsed Url
107
- */
108
- getHostUrlBase(url, name) {
109
- if (url.startsWith('/'))
110
- return url;
111
- const identifier = `/${name}/`;
112
- const pos = url.indexOf(identifier);
113
- if (pos === -1)
114
- return '/';
115
- return url
116
- .substring(pos + identifier.length - 1)
117
- .replace('/index.html', '/'); // Router take / as start
118
- }
119
93
  /**
120
94
  * Override alert action result
121
95
  * @param result Action result
@@ -224,7 +198,8 @@ export class ReactApp extends CoreApp {
224
198
  * @param url Url
225
199
  */
226
200
  redirectTo(url) {
227
- (this.history == null ? navigate : this.history.navigate)(url);
201
+ const navigate = ReactUtils.getNavigateFn();
202
+ navigate(url);
228
203
  }
229
204
  /**
230
205
  * Set page data
@@ -3,7 +3,7 @@ import React from 'react';
3
3
  /**
4
4
  * React utils
5
5
  */
6
- export declare namespace Utils {
6
+ export declare namespace ReactUtils {
7
7
  /**
8
8
  * Format input value
9
9
  * @param value Input value
@@ -15,7 +15,12 @@ export declare namespace Utils {
15
15
  * @param initialPath Initial path
16
16
  * @returns History
17
17
  */
18
- function getMemoryHistory(initialPath?: string): History;
18
+ function getMemoryHistory(initialPath?: string | null): History;
19
+ /**
20
+ * Get navigate function, works with memory history
21
+ * @returns NavigateFn
22
+ */
23
+ function getNavigateFn(): import("@reach/router").NavigateFn;
19
24
  /**
20
25
  * Is safe click
21
26
  * @param event Mouse event
@@ -1,9 +1,9 @@
1
- import { createHistory, createMemorySource } from '@reach/router';
1
+ import { createHistory, createMemorySource, navigate } from '@reach/router';
2
2
  /**
3
3
  * React utils
4
4
  */
5
- export var Utils;
6
- (function (Utils) {
5
+ export var ReactUtils;
6
+ (function (ReactUtils) {
7
7
  // Memory history
8
8
  // https://github.com/reach/router/issues/225
9
9
  let memoryHistory;
@@ -23,19 +23,29 @@ export var Utils;
23
23
  return value;
24
24
  return String(value);
25
25
  }
26
- Utils.formatInputValue = formatInputValue;
26
+ ReactUtils.formatInputValue = formatInputValue;
27
27
  /**
28
28
  * Get memory history
29
29
  * @param initialPath Initial path
30
30
  * @returns History
31
31
  */
32
- function getMemoryHistory(initialPath = '/') {
32
+ function getMemoryHistory(initialPath) {
33
33
  if (memoryHistory == null) {
34
- memoryHistory = createHistory(createMemorySource(initialPath));
34
+ memoryHistory = createHistory(createMemorySource(initialPath !== null && initialPath !== void 0 ? initialPath : '/'));
35
35
  }
36
36
  return memoryHistory;
37
37
  }
38
- Utils.getMemoryHistory = getMemoryHistory;
38
+ ReactUtils.getMemoryHistory = getMemoryHistory;
39
+ /**
40
+ * Get navigate function, works with memory history
41
+ * @returns NavigateFn
42
+ */
43
+ function getNavigateFn() {
44
+ if (memoryHistory == null)
45
+ return navigate;
46
+ return memoryHistory.navigate;
47
+ }
48
+ ReactUtils.getNavigateFn = getNavigateFn;
39
49
  /**
40
50
  * Is safe click
41
51
  * @param event Mouse event
@@ -61,7 +71,7 @@ export var Utils;
61
71
  }
62
72
  return true;
63
73
  }
64
- Utils.isSafeClick = isSafeClick;
74
+ ReactUtils.isSafeClick = isSafeClick;
65
75
  /**
66
76
  * Trigger input change event
67
77
  * @param input Form input
@@ -99,5 +109,5 @@ export var Utils;
99
109
  input.dispatchEvent(inputEvent);
100
110
  }
101
111
  }
102
- Utils.triggerChange = triggerChange;
103
- })(Utils || (Utils = {}));
112
+ ReactUtils.triggerChange = triggerChange;
113
+ })(ReactUtils || (ReactUtils = {}));
@@ -33,11 +33,9 @@ export declare class ServiceApp<U extends IServiceUser = IServiceUser, P extends
33
33
  */
34
34
  constructor(settings: S, name: string);
35
35
  /**
36
- * Get parsed Url under bridge host
37
- * @param url Url
38
- * @returns Parsed Url
36
+ * Load SmartERP core
39
37
  */
40
- protected getHostUrl(url: string): string;
38
+ loadSmartERP(): void;
41
39
  /**
42
40
  * Go to the login page
43
41
  * @param tryLogin Try to login again
@@ -66,8 +64,11 @@ export declare class ServiceApp<U extends IServiceUser = IServiceUser, P extends
66
64
  serviceEncrypt(message: string, passphrase?: string, iterations?: number): string;
67
65
  /**
68
66
  * Try login
67
+ * @param data Additional data
68
+ * @param showLoading Show loading bar or not
69
+ * @returns Result
69
70
  */
70
- tryLogin<D extends {} = {}>(data?: D): Promise<boolean>;
71
+ tryLogin<D extends {} = {}>(data?: D, showLoading?: boolean): Promise<boolean>;
71
72
  /**
72
73
  * User login extended
73
74
  * @param user Core system user
@@ -40,12 +40,15 @@ export class ServiceApp extends ReactApp {
40
40
  this._serviceUser = value;
41
41
  }
42
42
  /**
43
- * Get parsed Url under bridge host
44
- * @param url Url
45
- * @returns Parsed Url
43
+ * Load SmartERP core
46
44
  */
47
- getHostUrl(url) {
48
- return this.getHostUrlBase(url, 's' + this.settings.serviceId);
45
+ loadSmartERP() {
46
+ if (BridgeUtils.host == null) {
47
+ window.location.href = this.settings.webUrl;
48
+ }
49
+ else {
50
+ BridgeUtils.host.loadApp('core');
51
+ }
49
52
  }
50
53
  /**
51
54
  * Go to the login page
@@ -206,10 +209,13 @@ export class ServiceApp extends ReactApp {
206
209
  }
207
210
  /**
208
211
  * Try login
212
+ * @param data Additional data
213
+ * @param showLoading Show loading bar or not
214
+ * @returns Result
209
215
  */
210
- async tryLogin(data) {
216
+ async tryLogin(data, showLoading) {
211
217
  // Reset user state
212
- const result = await super.tryLogin(data);
218
+ const result = await super.tryLogin(data, showLoading);
213
219
  if (!result)
214
220
  return false;
215
221
  // Refresh token
@@ -228,6 +234,7 @@ export class ServiceApp extends ReactApp {
228
234
  });
229
235
  },
230
236
  data,
237
+ showLoading,
231
238
  relogin: true
232
239
  });
233
240
  }
package/lib/index.d.ts CHANGED
@@ -6,9 +6,9 @@ export * from './app/IServiceUser';
6
6
  export * from './app/ISmartERPUser';
7
7
  export * from './app/Labels';
8
8
  export * from './app/ReactApp';
9
+ export * from './app/ReactUtils';
9
10
  export * from './app/RefreshTokenRQ';
10
11
  export * from './app/ServiceApp';
11
- export * from './app/Utils';
12
12
  export * from './components/GridColumn';
13
13
  export * from './components/GridLoader';
14
14
  export * from './components/ListItemReact';
package/lib/index.js CHANGED
@@ -7,9 +7,9 @@ export * from './app/IServiceUser';
7
7
  export * from './app/ISmartERPUser';
8
8
  export * from './app/Labels';
9
9
  export * from './app/ReactApp';
10
+ export * from './app/ReactUtils';
10
11
  export * from './app/RefreshTokenRQ';
11
12
  export * from './app/ServiceApp';
12
- export * from './app/Utils';
13
13
  // components
14
14
  export * from './components/GridColumn';
15
15
  export * from './components/GridLoader';
@@ -1,7 +1,7 @@
1
- import { IconButton } from '@mui/material';
1
+ import { IconButton, useTheme } from '@mui/material';
2
2
  import ArrowBackIcon from '@mui/icons-material/ArrowBack';
3
3
  import React from 'react';
4
- import { useNavigate } from '@reach/router';
4
+ import { ReactUtils } from '../app/ReactUtils';
5
5
  /**
6
6
  * BackButton
7
7
  * @param props Props
@@ -10,20 +10,27 @@ import { useNavigate } from '@reach/router';
10
10
  export function BackButton(props) {
11
11
  // Destruct
12
12
  const { color = 'primary', size = 'small', onClick, ...rest } = props;
13
- // Navigate
14
- const navigate = useNavigate();
13
+ // Theme
14
+ const theme = useTheme();
15
+ // Color
16
+ const pColor = color != 'inherit' && color != 'default' && color in theme.palette
17
+ ? theme.palette[color]
18
+ : theme.palette.primary;
15
19
  // Click handler
16
- const onClickLocal = (event) => {
20
+ const onClickLocal = async (event) => {
17
21
  if (onClick)
18
22
  onClick(event);
19
- navigate(-1);
23
+ // Navigate
24
+ const navigate = ReactUtils.getNavigateFn();
25
+ await navigate(-1);
26
+ const history = ReactUtils.getMemoryHistory();
27
+ if (history) {
28
+ console.log(history);
29
+ }
20
30
  };
21
31
  return (React.createElement(IconButton, { "aria-label": "Back", color: color, size: size, onClick: onClickLocal, sx: {
22
- border: (theme) => `1px solid ${(() => color != 'inherit' &&
23
- color != 'default' &&
24
- color in theme.palette
25
- ? theme.palette[color]
26
- : theme.palette.primary)().light}`
32
+ backgroundColor: pColor.contrastText,
33
+ border: `1px solid ${pColor.light}`
27
34
  }, ...rest },
28
35
  React.createElement(ArrowBackIcon, null)));
29
36
  }
@@ -1,6 +1,6 @@
1
1
  import { Button } from '@mui/material';
2
- import { useNavigate } from '@reach/router';
3
2
  import React from 'react';
3
+ import { ReactUtils } from '../app/ReactUtils';
4
4
  /**
5
5
  * ButtonLink
6
6
  * @param props Props
@@ -10,7 +10,7 @@ export function ButtonLink(props) {
10
10
  // Destruct
11
11
  const { href, ...rest } = props;
12
12
  // Navigate
13
- const navigate = useNavigate();
13
+ const navigate = ReactUtils.getNavigateFn();
14
14
  const onClick = href.includes('://')
15
15
  ? () => window.open(href, '_blank')
16
16
  : () => navigate(href);
@@ -1,10 +1,10 @@
1
1
  import { Keyboard } from '@etsoo/shared';
2
2
  import { Autocomplete } from '@mui/material';
3
3
  import React from 'react';
4
- import { Utils } from '../app/Utils';
5
4
  import { Utils as SharedUtils } from '@etsoo/shared';
6
5
  import { InputField } from './InputField';
7
6
  import { SearchField } from './SearchField';
7
+ import { ReactUtils } from '../app/ReactUtils';
8
8
  /**
9
9
  * ComboBox
10
10
  * @param props Props
@@ -71,7 +71,7 @@ export function ComboBox(props) {
71
71
  const newValue = value != null ? `${Reflect.get(value, idField)}` : '';
72
72
  if (newValue !== input.value) {
73
73
  // Different value, trigger change event
74
- Utils.triggerChange(input, newValue, false);
74
+ ReactUtils.triggerChange(input, newValue, false);
75
75
  }
76
76
  }
77
77
  };
@@ -1,6 +1,6 @@
1
1
  import { IconButton } from '@mui/material';
2
- import { useNavigate } from '@reach/router';
3
2
  import React from 'react';
3
+ import { ReactUtils } from '../app/ReactUtils';
4
4
  /**
5
5
  * IconButtonLink
6
6
  * @param props Props
@@ -10,7 +10,7 @@ export function IconButtonLink(props) {
10
10
  // Destruct
11
11
  const { href, ...rest } = props;
12
12
  // Navigate
13
- const navigate = useNavigate();
13
+ const navigate = ReactUtils.getNavigateFn();
14
14
  // Layout
15
15
  return React.createElement(IconButton, { ...rest, onClick: () => navigate(href) });
16
16
  }
@@ -1,7 +1,7 @@
1
1
  import { Box, Stack } from '@mui/material';
2
2
  import React from 'react';
3
3
  import { Labels } from '../app/Labels';
4
- import { Utils } from '../app/Utils';
4
+ import { ReactUtils } from '../app/ReactUtils';
5
5
  import { GridDataGet } from '../components/GridLoader';
6
6
  import useCombinedRefs from '../uses/useCombinedRefs';
7
7
  import { useDimensions } from '../uses/useDimensions';
@@ -126,7 +126,7 @@ export function ResponsibleContainer(props) {
126
126
  return [
127
127
  React.createElement(Box, { className: "ListBox", sx: { height: heightLocal } },
128
128
  React.createElement(ScrollerListEx, { autoLoad: !hasFields, height: heightLocal, loadData: localLoadData, mRef: mRefs, onClick: (event, data) => quickAction &&
129
- Utils.isSafeClick(event) &&
129
+ ReactUtils.isSafeClick(event) &&
130
130
  quickAction(data), oRef: (element) => {
131
131
  if (element != null && elementReady)
132
132
  elementReady(element, false);
@@ -2,9 +2,9 @@ import { Button, Drawer, IconButton, Stack, useTheme } from '@mui/material';
2
2
  import React from 'react';
3
3
  import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
4
4
  import { useDimensions } from '../uses/useDimensions';
5
- import { Utils as AppUtils } from '../app/Utils';
6
5
  import { DomUtils } from '@etsoo/shared';
7
6
  import { Labels } from '../app/Labels';
7
+ import { ReactUtils } from '../app/ReactUtils';
8
8
  // Cached width attribute name
9
9
  const cachedWidthName = 'data-cached-width';
10
10
  // Reset form
@@ -20,7 +20,7 @@ const resetForm = (form) => {
20
20
  continue;
21
21
  // Ignore readOnly without data-reset=true inputs
22
22
  if (!input.readOnly || input.dataset.reset === 'true') {
23
- AppUtils.triggerChange(input, '', true);
23
+ ReactUtils.triggerChange(input, '', true);
24
24
  }
25
25
  continue;
26
26
  }
@@ -1,9 +1,9 @@
1
- import { Utils as AppUtils } from '../app/Utils';
2
1
  import { Checkbox, FormControl, InputLabel, ListItemText, MenuItem, OutlinedInput, Select } from '@mui/material';
3
2
  import React from 'react';
4
3
  import { MUGlobal } from './MUGlobal';
5
4
  import { ListItemRightIcon } from './ListItemRightIcon';
6
5
  import { Utils } from '@etsoo/shared';
6
+ import { ReactUtils } from '../app/ReactUtils';
7
7
  /**
8
8
  * Extended select component
9
9
  * @param props Props
@@ -65,7 +65,7 @@ export function SelectEx(props) {
65
65
  const input = (_a = divRef.current) === null || _a === void 0 ? void 0 : _a.querySelector('input');
66
66
  if (input) {
67
67
  // Different value, trigger change event
68
- AppUtils.triggerChange(input, id, false);
68
+ ReactUtils.triggerChange(input, id, false);
69
69
  }
70
70
  }
71
71
  };
package/lib/mu/Tiplist.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { DataTypes } from '@etsoo/shared';
2
2
  import { Autocomplete } from '@mui/material';
3
3
  import React from 'react';
4
- import { Utils } from '../app/Utils';
4
+ import { ReactUtils } from '../app/ReactUtils';
5
5
  import { InputField } from './InputField';
6
6
  import { SearchField } from './SearchField';
7
7
  /**
@@ -75,7 +75,7 @@ export function Tiplist(props) {
75
75
  const input = inputRef.current;
76
76
  if (input && input.value !== '') {
77
77
  // Different value, trigger change event
78
- Utils.triggerChange(input, '', false);
78
+ ReactUtils.triggerChange(input, '', false);
79
79
  }
80
80
  if (states.options.length > 0) {
81
81
  // Reset options
@@ -106,7 +106,7 @@ export function Tiplist(props) {
106
106
  const newValue = (_a = DataTypes.getStringValue(value, idField)) !== null && _a !== void 0 ? _a : '';
107
107
  if (newValue !== input.value) {
108
108
  // Different value, trigger change event
109
- Utils.triggerChange(input, newValue, false);
109
+ ReactUtils.triggerChange(input, newValue, false);
110
110
  }
111
111
  }
112
112
  };
@@ -48,6 +48,10 @@ export interface ViewPageProps<T extends {}> extends Exclude<CommonPageProps, 'c
48
48
  * Load data
49
49
  */
50
50
  loadData: () => PromiseLike<T | undefined>;
51
+ /**
52
+ * Pull to refresh data
53
+ */
54
+ pullToRefresh?: boolean;
51
55
  /**
52
56
  * Support refresh
53
57
  */
@@ -1,9 +1,11 @@
1
1
  import { Utils } from '@etsoo/shared';
2
2
  import { Grid, LinearProgress, Stack, Typography } from '@mui/material';
3
3
  import React from 'react';
4
+ import { Labels } from '../../app/Labels';
4
5
  import { globalApp } from '../../app/ReactApp';
5
6
  import { GridDataFormat } from '../GridDataFormat';
6
7
  import { MUGlobal } from '../MUGlobal';
8
+ import { PullToRefreshUI } from '../PullToRefreshUI';
7
9
  import { CommonPage } from './CommonPage';
8
10
  function formatItemData(fieldData) {
9
11
  if (fieldData == null)
@@ -62,9 +64,13 @@ function getItemField(field, data) {
62
64
  */
63
65
  export function ViewPage(props) {
64
66
  // Destruct
65
- const { actions, children, fields, loadData, paddings = MUGlobal.pagePaddings, supportRefresh = true, fabColumnDirection = true, supportBack = true, ...rest } = props;
67
+ const { actions, children, fields, loadData, paddings = MUGlobal.pagePaddings, supportRefresh = true, fabColumnDirection = true, supportBack = true, pullToRefresh = true, ...rest } = props;
66
68
  // Data
67
69
  const [data, setData] = React.useState();
70
+ // Labels
71
+ const labels = Labels.CommonPage;
72
+ // Container
73
+ const pullContainer = '#page-container';
68
74
  // Load data
69
75
  const refresh = async () => {
70
76
  const result = await loadData();
@@ -91,5 +97,9 @@ export function ViewPage(props) {
91
97
  React.createElement(Typography, { variant: "subtitle2" }, itemData)));
92
98
  })),
93
99
  actions != null && (React.createElement(Stack, { className: "ET-ViewPage-Actions", direction: "row", width: "100%", flexWrap: "wrap", justifyContent: "flex-end", paddingTop: paddings, paddingBottom: paddings, gap: paddings }, Utils.getResult(actions, data, refresh))),
94
- Utils.getResult(children, data, refresh)))));
100
+ Utils.getResult(children, data, refresh),
101
+ pullToRefresh && (React.createElement(PullToRefreshUI, { mainElement: pullContainer, triggerElement: pullContainer, instructionsPullToRefresh: labels.pullToRefresh, instructionsReleaseToRefresh: labels.releaseToRefresh, instructionsRefreshing: labels.refreshing, onRefresh: refresh, shouldPullToRefresh: () => {
102
+ const container = document.querySelector(pullContainer);
103
+ return !(container === null || container === void 0 ? void 0 : container.scrollTop);
104
+ } }))))));
95
105
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsoo/react",
3
- "version": "1.5.13",
3
+ "version": "1.5.17",
4
4
  "description": "TypeScript ReactJs framework",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -50,7 +50,7 @@
50
50
  "@emotion/react": "^11.7.1",
51
51
  "@emotion/style": "^0.8.0",
52
52
  "@emotion/styled": "^11.6.0",
53
- "@etsoo/appscript": "^1.2.48",
53
+ "@etsoo/appscript": "^1.2.50",
54
54
  "@etsoo/notificationbase": "^1.1.1",
55
55
  "@etsoo/shared": "^1.1.11",
56
56
  "@mui/icons-material": "^5.4.1",
@@ -14,7 +14,7 @@ import {
14
14
  NotificationReturn
15
15
  } from '@etsoo/notificationbase';
16
16
  import { DataTypes, WindowStorage } from '@etsoo/shared';
17
- import { History, navigate } from '@reach/router';
17
+ import { History } from '@reach/router';
18
18
  import React from 'react';
19
19
  import { NotifierMU } from '../mu/NotifierMU';
20
20
  import { ProgressCount } from '../mu/ProgressCount';
@@ -35,7 +35,7 @@ import {
35
35
  } from '../states/UserState';
36
36
  import { InputDialogProps } from './InputDialogProps';
37
37
  import { Labels } from './Labels';
38
- import { Utils } from './Utils';
38
+ import { ReactUtils } from './ReactUtils';
39
39
 
40
40
  /**
41
41
  * Global application
@@ -219,43 +219,18 @@ export class ReactApp<
219
219
  new WindowStorage(),
220
220
  name
221
221
  );
222
+
222
223
  this.history =
223
- window.location.hostname === ''
224
- ? Utils.getMemoryHistory(this.getHostUrl(window.location.href))
225
- : undefined;
224
+ BridgeUtils.host == null
225
+ ? undefined
226
+ : ReactUtils.getMemoryHistory(BridgeUtils.host.getStartUrl());
227
+
226
228
  this.cultureState = new CultureState(settings.currentCulture);
227
229
  this.pageState = new PageState<P>();
228
230
 
229
231
  globalApp = this;
230
232
  }
231
233
 
232
- /**
233
- * Get parsed Url under bridge host
234
- * @param url Url
235
- * @returns Parsed Url
236
- */
237
- protected getHostUrl(url: string) {
238
- return this.getHostUrlBase(url, 'core');
239
- }
240
-
241
- /**
242
- * Get parsed Url under bridge host
243
- * @param url Url
244
- * @param name App name in the host environment
245
- * @returns Parsed Url
246
- */
247
- protected getHostUrlBase(url: string, name: string) {
248
- if (url.startsWith('/')) return url;
249
-
250
- const identifier = `/${name}/`;
251
- const pos = url.indexOf(identifier);
252
- if (pos === -1) return '/';
253
-
254
- return url
255
- .substring(pos + identifier.length - 1)
256
- .replace('/index.html', '/'); // Router take / as start
257
- }
258
-
259
234
  /**
260
235
  * Override alert action result
261
236
  * @param result Action result
@@ -395,7 +370,8 @@ export class ReactApp<
395
370
  * @param url Url
396
371
  */
397
372
  override redirectTo(url: string) {
398
- (this.history == null ? navigate : this.history.navigate)(url);
373
+ const navigate = ReactUtils.getNavigateFn();
374
+ navigate(url);
399
375
  }
400
376
 
401
377
  /**
@@ -1,10 +1,15 @@
1
- import { createHistory, createMemorySource, History } from '@reach/router';
1
+ import {
2
+ createHistory,
3
+ createMemorySource,
4
+ History,
5
+ navigate
6
+ } from '@reach/router';
2
7
  import React from 'react';
3
8
 
4
9
  /**
5
10
  * React utils
6
11
  */
7
- export namespace Utils {
12
+ export namespace ReactUtils {
8
13
  // Memory history
9
14
  // https://github.com/reach/router/issues/225
10
15
  let memoryHistory: History | null;
@@ -31,13 +36,24 @@ export namespace Utils {
31
36
  * @param initialPath Initial path
32
37
  * @returns History
33
38
  */
34
- export function getMemoryHistory(initialPath: string = '/') {
39
+ export function getMemoryHistory(initialPath?: string | null) {
35
40
  if (memoryHistory == null) {
36
- memoryHistory = createHistory(createMemorySource(initialPath));
41
+ memoryHistory = createHistory(
42
+ createMemorySource(initialPath ?? '/')
43
+ );
37
44
  }
38
45
  return memoryHistory;
39
46
  }
40
47
 
48
+ /**
49
+ * Get navigate function, works with memory history
50
+ * @returns NavigateFn
51
+ */
52
+ export function getNavigateFn() {
53
+ if (memoryHistory == null) return navigate;
54
+ return memoryHistory.navigate;
55
+ }
56
+
41
57
  /**
42
58
  * Is safe click
43
59
  * @param event Mouse event
@@ -69,12 +69,14 @@ export class ServiceApp<
69
69
  }
70
70
 
71
71
  /**
72
- * Get parsed Url under bridge host
73
- * @param url Url
74
- * @returns Parsed Url
72
+ * Load SmartERP core
75
73
  */
76
- protected override getHostUrl(url: string) {
77
- return this.getHostUrlBase(url, 's' + this.settings.serviceId);
74
+ loadSmartERP() {
75
+ if (BridgeUtils.host == null) {
76
+ window.location.href = this.settings.webUrl;
77
+ } else {
78
+ BridgeUtils.host.loadApp('core');
79
+ }
78
80
  }
79
81
 
80
82
  /**
@@ -300,10 +302,16 @@ export class ServiceApp<
300
302
 
301
303
  /**
302
304
  * Try login
305
+ * @param data Additional data
306
+ * @param showLoading Show loading bar or not
307
+ * @returns Result
303
308
  */
304
- override async tryLogin<D extends {} = {}>(data?: D) {
309
+ override async tryLogin<D extends {} = {}>(
310
+ data?: D,
311
+ showLoading?: boolean
312
+ ) {
305
313
  // Reset user state
306
- const result = await super.tryLogin(data);
314
+ const result = await super.tryLogin(data, showLoading);
307
315
  if (!result) return false;
308
316
 
309
317
  // Refresh token
@@ -323,6 +331,7 @@ export class ServiceApp<
323
331
  });
324
332
  },
325
333
  data,
334
+ showLoading,
326
335
  relogin: true
327
336
  });
328
337
  }
package/src/index.ts CHANGED
@@ -7,9 +7,9 @@ export * from './app/IServiceUser';
7
7
  export * from './app/ISmartERPUser';
8
8
  export * from './app/Labels';
9
9
  export * from './app/ReactApp';
10
+ export * from './app/ReactUtils';
10
11
  export * from './app/RefreshTokenRQ';
11
12
  export * from './app/ServiceApp';
12
- export * from './app/Utils';
13
13
 
14
14
  // components
15
15
  export * from './components/GridColumn';
@@ -1,7 +1,8 @@
1
- import { IconButton, IconButtonProps } from '@mui/material';
1
+ import { IconButton, IconButtonProps, useTheme } from '@mui/material';
2
2
  import ArrowBackIcon from '@mui/icons-material/ArrowBack';
3
3
  import React from 'react';
4
- import { useNavigate } from '@reach/router';
4
+ import { ReactUtils } from '../app/ReactUtils';
5
+ import { BridgeUtils } from '@etsoo/appscript';
5
6
 
6
7
  /**
7
8
  * BackButton props
@@ -17,13 +18,27 @@ export function BackButton(props: BackButtonProps) {
17
18
  // Destruct
18
19
  const { color = 'primary', size = 'small', onClick, ...rest } = props;
19
20
 
20
- // Navigate
21
- const navigate = useNavigate();
21
+ // Theme
22
+ const theme = useTheme();
23
+
24
+ // Color
25
+ const pColor =
26
+ color != 'inherit' && color != 'default' && color in theme.palette
27
+ ? theme.palette[color]
28
+ : theme.palette.primary;
22
29
 
23
30
  // Click handler
24
- const onClickLocal = (event: React.MouseEvent<HTMLButtonElement>) => {
31
+ const onClickLocal = async (event: React.MouseEvent<HTMLButtonElement>) => {
25
32
  if (onClick) onClick(event);
26
- navigate(-1);
33
+
34
+ // Navigate
35
+ const navigate = ReactUtils.getNavigateFn();
36
+ await navigate(-1);
37
+
38
+ const history = ReactUtils.getMemoryHistory();
39
+ if (history) {
40
+ console.log(history);
41
+ }
27
42
  };
28
43
 
29
44
  return (
@@ -33,15 +48,8 @@ export function BackButton(props: BackButtonProps) {
33
48
  size={size}
34
49
  onClick={onClickLocal}
35
50
  sx={{
36
- border: (theme) =>
37
- `1px solid ${
38
- (() =>
39
- color != 'inherit' &&
40
- color != 'default' &&
41
- color in theme.palette
42
- ? theme.palette[color]
43
- : theme.palette.primary)().light
44
- }`
51
+ backgroundColor: pColor.contrastText,
52
+ border: `1px solid ${pColor.light}`
45
53
  }}
46
54
  {...rest}
47
55
  >
@@ -1,6 +1,6 @@
1
1
  import { Button, ButtonProps } from '@mui/material';
2
- import { useNavigate } from '@reach/router';
3
2
  import React from 'react';
3
+ import { ReactUtils } from '../app/ReactUtils';
4
4
 
5
5
  /**
6
6
  * ButtonLink props
@@ -22,7 +22,8 @@ export function ButtonLink(props: ButtonLinkProps) {
22
22
  const { href, ...rest } = props;
23
23
 
24
24
  // Navigate
25
- const navigate = useNavigate();
25
+ const navigate = ReactUtils.getNavigateFn();
26
+
26
27
  const onClick = href.includes('://')
27
28
  ? () => window.open(href, '_blank')
28
29
  : () => navigate(href);
@@ -2,11 +2,11 @@ import { IdLabelDto } from '@etsoo/appscript';
2
2
  import { Keyboard } from '@etsoo/shared';
3
3
  import { Autocomplete, AutocompleteRenderInputParams } from '@mui/material';
4
4
  import React from 'react';
5
- import { Utils } from '../app/Utils';
6
5
  import { Utils as SharedUtils } from '@etsoo/shared';
7
6
  import { AutocompleteExtendedProps } from './AutocompleteExtendedProps';
8
7
  import { InputField } from './InputField';
9
8
  import { SearchField } from './SearchField';
9
+ import { ReactUtils } from '../app/ReactUtils';
10
10
 
11
11
  /**
12
12
  * ComboBox props
@@ -152,7 +152,7 @@ export function ComboBox<T extends {} = IdLabelDto>(props: ComboBoxProps<T>) {
152
152
 
153
153
  if (newValue !== input.value) {
154
154
  // Different value, trigger change event
155
- Utils.triggerChange(input, newValue, false);
155
+ ReactUtils.triggerChange(input, newValue, false);
156
156
  }
157
157
  }
158
158
  };
@@ -1,6 +1,6 @@
1
1
  import { IconButton, IconButtonProps } from '@mui/material';
2
- import { useNavigate } from '@reach/router';
3
2
  import React from 'react';
3
+ import { ReactUtils } from '../app/ReactUtils';
4
4
 
5
5
  /**
6
6
  * IconButtonLink props
@@ -22,7 +22,7 @@ export function IconButtonLink(props: IconButtonLinkProps) {
22
22
  const { href, ...rest } = props;
23
23
 
24
24
  // Navigate
25
- const navigate = useNavigate();
25
+ const navigate = ReactUtils.getNavigateFn();
26
26
 
27
27
  // Layout
28
28
  return <IconButton {...rest} onClick={() => navigate(href)} />;
@@ -3,7 +3,7 @@ import { Box, Stack, SxProps, Theme } from '@mui/material';
3
3
  import React from 'react';
4
4
  import { ListChildComponentProps } from 'react-window';
5
5
  import { Labels } from '../app/Labels';
6
- import { Utils } from '../app/Utils';
6
+ import { ReactUtils } from '../app/ReactUtils';
7
7
  import { GridColumn } from '../components/GridColumn';
8
8
  import {
9
9
  GridDataGet,
@@ -326,7 +326,7 @@ export function ResponsibleContainer<
326
326
  mRef={mRefs}
327
327
  onClick={(event, data) =>
328
328
  quickAction &&
329
- Utils.isSafeClick(event) &&
329
+ ReactUtils.isSafeClick(event) &&
330
330
  quickAction(data)
331
331
  }
332
332
  oRef={(element) => {
@@ -2,9 +2,9 @@ import { Button, Drawer, IconButton, Stack, useTheme } from '@mui/material';
2
2
  import React from 'react';
3
3
  import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
4
4
  import { useDimensions } from '../uses/useDimensions';
5
- import { Utils as AppUtils } from '../app/Utils';
6
5
  import { DomUtils } from '@etsoo/shared';
7
6
  import { Labels } from '../app/Labels';
7
+ import { ReactUtils } from '../app/ReactUtils';
8
8
 
9
9
  /**
10
10
  * Search bar props
@@ -48,7 +48,7 @@ const resetForm = (form: HTMLFormElement) => {
48
48
 
49
49
  // Ignore readOnly without data-reset=true inputs
50
50
  if (!input.readOnly || input.dataset.reset === 'true') {
51
- AppUtils.triggerChange(input, '', true);
51
+ ReactUtils.triggerChange(input, '', true);
52
52
  }
53
53
  continue;
54
54
  }
@@ -1,4 +1,3 @@
1
- import { Utils as AppUtils } from '../app/Utils';
2
1
  import {
3
2
  Checkbox,
4
3
  FormControl,
@@ -15,6 +14,7 @@ import { MUGlobal } from './MUGlobal';
15
14
  import { IdLabelDto } from '@etsoo/appscript';
16
15
  import { ListItemRightIcon } from './ListItemRightIcon';
17
16
  import { Utils } from '@etsoo/shared';
17
+ import { ReactUtils } from '../app/ReactUtils';
18
18
 
19
19
  /**
20
20
  * Extended select component props
@@ -145,7 +145,7 @@ export function SelectEx<T extends {} = IdLabelDto>(props: SelectExProps<T>) {
145
145
  const input = divRef.current?.querySelector('input');
146
146
  if (input) {
147
147
  // Different value, trigger change event
148
- AppUtils.triggerChange(input, id as string, false);
148
+ ReactUtils.triggerChange(input, id as string, false);
149
149
  }
150
150
  }
151
151
  };
@@ -2,7 +2,7 @@ import { IdLabelDto } from '@etsoo/appscript';
2
2
  import { DataTypes } from '@etsoo/shared';
3
3
  import { Autocomplete, AutocompleteRenderInputParams } from '@mui/material';
4
4
  import React from 'react';
5
- import { Utils } from '../app/Utils';
5
+ import { ReactUtils } from '../app/ReactUtils';
6
6
  import { AutocompleteExtendedProps } from './AutocompleteExtendedProps';
7
7
  import { InputField } from './InputField';
8
8
  import { SearchField } from './SearchField';
@@ -152,7 +152,7 @@ export function Tiplist<T extends {} = IdLabelDto>(props: TiplistProps<T>) {
152
152
 
153
153
  if (input && input.value !== '') {
154
154
  // Different value, trigger change event
155
- Utils.triggerChange(input, '', false);
155
+ ReactUtils.triggerChange(input, '', false);
156
156
  }
157
157
 
158
158
  if (states.options.length > 0) {
@@ -186,7 +186,7 @@ export function Tiplist<T extends {} = IdLabelDto>(props: TiplistProps<T>) {
186
186
  const newValue = DataTypes.getStringValue(value, idField) ?? '';
187
187
  if (newValue !== input.value) {
188
188
  // Different value, trigger change event
189
- Utils.triggerChange(input, newValue, false);
189
+ ReactUtils.triggerChange(input, newValue, false);
190
190
  }
191
191
  }
192
192
  };
@@ -7,6 +7,7 @@ import {
7
7
  Typography
8
8
  } from '@mui/material';
9
9
  import React from 'react';
10
+ import { Labels } from '../../app/Labels';
10
11
  import { globalApp } from '../../app/ReactApp';
11
12
  import {
12
13
  GridColumnRenderProps,
@@ -14,6 +15,7 @@ import {
14
15
  } from '../../components/GridColumn';
15
16
  import { GridDataFormat } from '../GridDataFormat';
16
17
  import { MUGlobal } from '../MUGlobal';
18
+ import { PullToRefreshUI } from '../PullToRefreshUI';
17
19
  import { CommonPage } from './CommonPage';
18
20
  import { CommonPageProps } from './CommonPageProps';
19
21
 
@@ -81,6 +83,11 @@ export interface ViewPageProps<T extends {}>
81
83
  */
82
84
  loadData: () => PromiseLike<T | undefined>;
83
85
 
86
+ /**
87
+ * Pull to refresh data
88
+ */
89
+ pullToRefresh?: boolean;
90
+
84
91
  /**
85
92
  * Support refresh
86
93
  */
@@ -167,12 +174,19 @@ export function ViewPage<T extends {}>(props: ViewPageProps<T>) {
167
174
  supportRefresh = true,
168
175
  fabColumnDirection = true,
169
176
  supportBack = true,
177
+ pullToRefresh = true,
170
178
  ...rest
171
179
  } = props;
172
180
 
173
181
  // Data
174
182
  const [data, setData] = React.useState<T>();
175
183
 
184
+ // Labels
185
+ const labels = Labels.CommonPage;
186
+
187
+ // Container
188
+ const pullContainer = '#page-container';
189
+
176
190
  // Load data
177
191
  const refresh = async () => {
178
192
  const result = await loadData();
@@ -245,6 +259,23 @@ export function ViewPage<T extends {}>(props: ViewPageProps<T>) {
245
259
  </Stack>
246
260
  )}
247
261
  {Utils.getResult(children, data, refresh)}
262
+ {pullToRefresh && (
263
+ <PullToRefreshUI
264
+ mainElement={pullContainer}
265
+ triggerElement={pullContainer}
266
+ instructionsPullToRefresh={labels.pullToRefresh}
267
+ instructionsReleaseToRefresh={
268
+ labels.releaseToRefresh
269
+ }
270
+ instructionsRefreshing={labels.refreshing}
271
+ onRefresh={refresh}
272
+ shouldPullToRefresh={() => {
273
+ const container =
274
+ document.querySelector(pullContainer);
275
+ return !container?.scrollTop;
276
+ }}
277
+ />
278
+ )}
248
279
  </React.Fragment>
249
280
  )}
250
281
  </CommonPage>