@etsoo/react 1.4.88 → 1.4.92
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.
- package/lib/mu/ListMoreDisplay.d.ts +14 -5
- package/lib/mu/ListMoreDisplay.js +29 -17
- package/lib/mu/LoadingButton.js +4 -8
- package/package.json +11 -11
- package/src/mu/ListMoreDisplay.tsx +75 -30
- package/src/mu/LoadingButton.tsx +5 -7
|
@@ -1,26 +1,35 @@
|
|
|
1
|
+
import { DataTypes } from '@etsoo/shared';
|
|
1
2
|
import { CardProps } from '@mui/material';
|
|
2
3
|
import React from 'react';
|
|
3
|
-
import { GridLoader } from '../components/GridLoader';
|
|
4
|
+
import { GridData, GridLoader } from '../components/GridLoader';
|
|
4
5
|
/**
|
|
5
6
|
* ListMoreDisplay props
|
|
6
7
|
*/
|
|
7
|
-
export interface ListMoreDisplayProps<T> extends CardProps, GridLoader<T> {
|
|
8
|
+
export interface ListMoreDisplayProps<T, F extends DataTypes.BasicTemplate = DataTypes.BasicTemplate> extends CardProps, GridLoader<T> {
|
|
8
9
|
/**
|
|
9
10
|
* Children to display the list
|
|
10
11
|
*/
|
|
11
12
|
children: (data: T, index: number) => React.ReactNode;
|
|
12
13
|
/**
|
|
13
|
-
*
|
|
14
|
+
* Search field template
|
|
14
15
|
*/
|
|
15
|
-
|
|
16
|
+
fieldTemplate?: F;
|
|
17
|
+
/**
|
|
18
|
+
* Header renderer
|
|
19
|
+
*/
|
|
20
|
+
headerRenderer?: (reset: (data?: GridData) => void) => React.ReactNode;
|
|
16
21
|
/**
|
|
17
22
|
* Header title
|
|
18
23
|
*/
|
|
19
24
|
headerTitle?: React.ReactNode;
|
|
25
|
+
/**
|
|
26
|
+
* More button label
|
|
27
|
+
*/
|
|
28
|
+
moreLabel?: string;
|
|
20
29
|
}
|
|
21
30
|
/**
|
|
22
31
|
* ListMoreDisplay
|
|
23
32
|
* @param props Props
|
|
24
33
|
* @returns Component
|
|
25
34
|
*/
|
|
26
|
-
export declare function ListMoreDisplay<T extends {}>(props: ListMoreDisplayProps<T>): JSX.Element;
|
|
35
|
+
export declare function ListMoreDisplay<T extends {}, F extends DataTypes.BasicTemplate = DataTypes.BasicTemplate>(props: ListMoreDisplayProps<T, F>): JSX.Element;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Card, CardActions, CardContent, CardHeader, CircularProgress } from '@mui/material';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import { globalApp
|
|
3
|
+
import { globalApp } from '../app/ReactApp';
|
|
4
|
+
import { GridDataGet } from '../components/GridLoader';
|
|
5
|
+
import { LoadingButton } from './LoadingButton';
|
|
4
6
|
/**
|
|
5
7
|
* ListMoreDisplay
|
|
6
8
|
* @param props Props
|
|
@@ -8,9 +10,9 @@ import { globalApp, LoadingButton } from '..';
|
|
|
8
10
|
*/
|
|
9
11
|
export function ListMoreDisplay(props) {
|
|
10
12
|
// Destruct
|
|
11
|
-
const { autoLoad =
|
|
13
|
+
const { children, defaultOrderBy, headerRenderer, autoLoad = headerRenderer == null, headerTitle, loadBatchSize, loadData, moreLabel = typeof globalApp === 'undefined'
|
|
12
14
|
? undefined
|
|
13
|
-
: globalApp.get('more') + '...', threshold, ...rest } = props;
|
|
15
|
+
: globalApp.get('more') + '...', fieldTemplate, threshold, ...rest } = props;
|
|
14
16
|
// Refs
|
|
15
17
|
const refs = React.useRef({
|
|
16
18
|
autoLoad,
|
|
@@ -28,7 +30,7 @@ export function ListMoreDisplay(props) {
|
|
|
28
30
|
return { ...currentStates, ...newStates };
|
|
29
31
|
}, { completed: false });
|
|
30
32
|
// Load data
|
|
31
|
-
const loadDataLocal = async () => {
|
|
33
|
+
const loadDataLocal = async (reset = false) => {
|
|
32
34
|
// Prevent multiple loadings
|
|
33
35
|
if (!ref.hasNextPage || ref.isNextPageLoading)
|
|
34
36
|
return;
|
|
@@ -43,7 +45,8 @@ export function ListMoreDisplay(props) {
|
|
|
43
45
|
orderByAsc,
|
|
44
46
|
data
|
|
45
47
|
};
|
|
46
|
-
const
|
|
48
|
+
const mergedData = GridDataGet(loadProps, fieldTemplate);
|
|
49
|
+
const items = await loadData(mergedData);
|
|
47
50
|
if (items == null || ref.isMounted === false) {
|
|
48
51
|
return;
|
|
49
52
|
}
|
|
@@ -54,7 +57,7 @@ export function ListMoreDisplay(props) {
|
|
|
54
57
|
ref.isNextPageLoading = false;
|
|
55
58
|
ref.hasNextPage = hasNextPage;
|
|
56
59
|
// Update rows
|
|
57
|
-
if (states.items == null)
|
|
60
|
+
if (states.items == null || reset)
|
|
58
61
|
setStates({ items, completed: !hasNextPage });
|
|
59
62
|
else
|
|
60
63
|
setStates({
|
|
@@ -62,6 +65,16 @@ export function ListMoreDisplay(props) {
|
|
|
62
65
|
completed: !hasNextPage
|
|
63
66
|
});
|
|
64
67
|
};
|
|
68
|
+
const reset = (data) => {
|
|
69
|
+
// Update the form data
|
|
70
|
+
ref.data = data;
|
|
71
|
+
// Reset page number
|
|
72
|
+
ref.isNextPageLoading = false;
|
|
73
|
+
ref.currentPage = 0;
|
|
74
|
+
ref.hasNextPage = true;
|
|
75
|
+
// Load data
|
|
76
|
+
loadDataLocal(true);
|
|
77
|
+
};
|
|
65
78
|
React.useEffect(() => {
|
|
66
79
|
if (autoLoad)
|
|
67
80
|
loadDataLocal();
|
|
@@ -71,15 +84,14 @@ export function ListMoreDisplay(props) {
|
|
|
71
84
|
ref.isMounted = false;
|
|
72
85
|
};
|
|
73
86
|
}, []);
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
React.createElement(LoadingButton, { onClick: async () => await loadDataLocal() }, moreLabel)))));
|
|
87
|
+
return (React.createElement(React.Fragment, null,
|
|
88
|
+
headerRenderer && headerRenderer(reset),
|
|
89
|
+
React.createElement(Card, { ...rest },
|
|
90
|
+
React.createElement(CardHeader, { title: headerTitle }),
|
|
91
|
+
React.createElement(CardContent, { sx: {
|
|
92
|
+
paddingTop: 0,
|
|
93
|
+
paddingBottom: states.completed ? 0 : 'inherit'
|
|
94
|
+
} }, states.items == null ? (React.createElement(CircularProgress, { size: 20 })) : (states.items.map((item, index) => children(item, index)))),
|
|
95
|
+
!states.completed && (React.createElement(CardActions, { sx: { justifyContent: 'flex-end' } },
|
|
96
|
+
React.createElement(LoadingButton, { onClick: async () => await loadDataLocal() }, moreLabel))))));
|
|
85
97
|
}
|
package/lib/mu/LoadingButton.js
CHANGED
|
@@ -27,19 +27,15 @@ export function LoadingButton(props) {
|
|
|
27
27
|
if (onClick) {
|
|
28
28
|
// Update state
|
|
29
29
|
setLoading(true);
|
|
30
|
+
// https://stackoverflow.com/questions/38508420/how-to-know-if-a-function-is-async
|
|
30
31
|
// const AsyncFunction = (async () => {}).constructor;
|
|
31
32
|
// onClick instanceof AsyncFunction
|
|
32
|
-
|
|
33
|
-
// May long time running
|
|
34
|
-
await onClick(event);
|
|
35
|
-
}
|
|
36
|
-
else {
|
|
37
|
-
onClick(event);
|
|
38
|
-
}
|
|
33
|
+
await onClick(event);
|
|
39
34
|
// Warning: Can't perform a React state update on an unmounted component
|
|
40
35
|
// It's necessary to check the component is mounted now
|
|
41
|
-
if (isMounted.current)
|
|
36
|
+
if (isMounted.current) {
|
|
42
37
|
setLoading(false);
|
|
38
|
+
}
|
|
43
39
|
}
|
|
44
40
|
}, ...rest }));
|
|
45
41
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@etsoo/react",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.92",
|
|
4
4
|
"description": "TypeScript ReactJs framework",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -50,16 +50,16 @@
|
|
|
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.
|
|
53
|
+
"@etsoo/appscript": "^1.2.37",
|
|
54
54
|
"@etsoo/notificationbase": "^1.1.1",
|
|
55
|
-
"@etsoo/shared": "^1.1.
|
|
55
|
+
"@etsoo/shared": "^1.1.10",
|
|
56
56
|
"@mui/icons-material": "^5.3.1",
|
|
57
57
|
"@mui/material": "^5.4.0",
|
|
58
58
|
"@reach/router": "^1.3.4",
|
|
59
|
-
"@types/pica": "^
|
|
59
|
+
"@types/pica": "^9.0.0",
|
|
60
60
|
"@types/pulltorefreshjs": "^0.1.5",
|
|
61
61
|
"@types/reach__router": "^1.3.10",
|
|
62
|
-
"@types/react": "^17.0.
|
|
62
|
+
"@types/react": "^17.0.39",
|
|
63
63
|
"@types/react-avatar-editor": "^10.3.6",
|
|
64
64
|
"@types/react-beautiful-dnd": "^13.1.2",
|
|
65
65
|
"@types/react-dom": "^17.0.11",
|
|
@@ -72,15 +72,15 @@
|
|
|
72
72
|
"react-beautiful-dnd": "^13.1.0",
|
|
73
73
|
"react-dom": "^17.0.2",
|
|
74
74
|
"react-draggable": "^4.4.4",
|
|
75
|
-
"react-imask": "^6.
|
|
75
|
+
"react-imask": "^6.4.0",
|
|
76
76
|
"react-window": "^1.8.6"
|
|
77
77
|
},
|
|
78
78
|
"devDependencies": {
|
|
79
|
-
"@babel/cli": "^7.
|
|
80
|
-
"@babel/core": "^7.
|
|
81
|
-
"@babel/plugin-transform-runtime": "^7.
|
|
79
|
+
"@babel/cli": "^7.17.0",
|
|
80
|
+
"@babel/core": "^7.17.0",
|
|
81
|
+
"@babel/plugin-transform-runtime": "^7.17.0",
|
|
82
82
|
"@babel/preset-env": "^7.16.11",
|
|
83
|
-
"@babel/runtime-corejs3": "^7.
|
|
83
|
+
"@babel/runtime-corejs3": "^7.17.0",
|
|
84
84
|
"@types/jest": "^27.4.0",
|
|
85
85
|
"@types/react-test-renderer": "^17.0.1",
|
|
86
86
|
"@typescript-eslint/eslint-plugin": "^5.10.2",
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
"eslint-config-airbnb-base": "^15.0.0",
|
|
90
90
|
"eslint-plugin-import": "^2.25.4",
|
|
91
91
|
"eslint-plugin-react": "^7.28.0",
|
|
92
|
-
"jest": "^27.
|
|
92
|
+
"jest": "^27.5.0",
|
|
93
93
|
"react-test-renderer": "^17.0.2",
|
|
94
94
|
"ts-jest": "^27.1.3",
|
|
95
95
|
"typescript": "^4.5.5"
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DataTypes } from '@etsoo/shared';
|
|
1
2
|
import {
|
|
2
3
|
Card,
|
|
3
4
|
CardActions,
|
|
@@ -7,31 +8,48 @@ import {
|
|
|
7
8
|
CircularProgress
|
|
8
9
|
} from '@mui/material';
|
|
9
10
|
import React from 'react';
|
|
10
|
-
import { globalApp
|
|
11
|
+
import { globalApp } from '../app/ReactApp';
|
|
11
12
|
import {
|
|
13
|
+
GridData,
|
|
14
|
+
GridDataGet,
|
|
12
15
|
GridLoadDataProps,
|
|
13
16
|
GridLoader,
|
|
14
17
|
GridLoaderStates
|
|
15
18
|
} from '../components/GridLoader';
|
|
19
|
+
import { LoadingButton } from './LoadingButton';
|
|
16
20
|
|
|
17
21
|
/**
|
|
18
22
|
* ListMoreDisplay props
|
|
19
23
|
*/
|
|
20
|
-
export interface ListMoreDisplayProps<
|
|
24
|
+
export interface ListMoreDisplayProps<
|
|
25
|
+
T,
|
|
26
|
+
F extends DataTypes.BasicTemplate = DataTypes.BasicTemplate
|
|
27
|
+
> extends CardProps,
|
|
28
|
+
GridLoader<T> {
|
|
21
29
|
/**
|
|
22
30
|
* Children to display the list
|
|
23
31
|
*/
|
|
24
32
|
children: (data: T, index: number) => React.ReactNode;
|
|
25
33
|
|
|
26
34
|
/**
|
|
27
|
-
*
|
|
35
|
+
* Search field template
|
|
28
36
|
*/
|
|
29
|
-
|
|
37
|
+
fieldTemplate?: F;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Header renderer
|
|
41
|
+
*/
|
|
42
|
+
headerRenderer?: (reset: (data?: GridData) => void) => React.ReactNode;
|
|
30
43
|
|
|
31
44
|
/**
|
|
32
45
|
* Header title
|
|
33
46
|
*/
|
|
34
47
|
headerTitle?: React.ReactNode;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* More button label
|
|
51
|
+
*/
|
|
52
|
+
moreLabel?: string;
|
|
35
53
|
}
|
|
36
54
|
|
|
37
55
|
type states<T> = {
|
|
@@ -44,18 +62,23 @@ type states<T> = {
|
|
|
44
62
|
* @param props Props
|
|
45
63
|
* @returns Component
|
|
46
64
|
*/
|
|
47
|
-
export function ListMoreDisplay<
|
|
65
|
+
export function ListMoreDisplay<
|
|
66
|
+
T extends {},
|
|
67
|
+
F extends DataTypes.BasicTemplate = DataTypes.BasicTemplate
|
|
68
|
+
>(props: ListMoreDisplayProps<T, F>) {
|
|
48
69
|
// Destruct
|
|
49
70
|
const {
|
|
50
|
-
autoLoad = true,
|
|
51
71
|
children,
|
|
52
72
|
defaultOrderBy,
|
|
73
|
+
headerRenderer,
|
|
74
|
+
autoLoad = headerRenderer == null,
|
|
53
75
|
headerTitle,
|
|
54
76
|
loadBatchSize,
|
|
55
77
|
loadData,
|
|
56
78
|
moreLabel = typeof globalApp === 'undefined'
|
|
57
79
|
? undefined
|
|
58
80
|
: globalApp.get('more') + '...',
|
|
81
|
+
fieldTemplate,
|
|
59
82
|
threshold,
|
|
60
83
|
...rest
|
|
61
84
|
} = props;
|
|
@@ -82,7 +105,7 @@ export function ListMoreDisplay<T extends {}>(props: ListMoreDisplayProps<T>) {
|
|
|
82
105
|
);
|
|
83
106
|
|
|
84
107
|
// Load data
|
|
85
|
-
const loadDataLocal = async () => {
|
|
108
|
+
const loadDataLocal = async (reset: boolean = false) => {
|
|
86
109
|
// Prevent multiple loadings
|
|
87
110
|
if (!ref.hasNextPage || ref.isNextPageLoading) return;
|
|
88
111
|
|
|
@@ -100,7 +123,9 @@ export function ListMoreDisplay<T extends {}>(props: ListMoreDisplayProps<T>) {
|
|
|
100
123
|
data
|
|
101
124
|
};
|
|
102
125
|
|
|
103
|
-
const
|
|
126
|
+
const mergedData = GridDataGet(loadProps, fieldTemplate);
|
|
127
|
+
|
|
128
|
+
const items = await loadData(mergedData);
|
|
104
129
|
if (items == null || ref.isMounted === false) {
|
|
105
130
|
return;
|
|
106
131
|
}
|
|
@@ -113,7 +138,8 @@ export function ListMoreDisplay<T extends {}>(props: ListMoreDisplayProps<T>) {
|
|
|
113
138
|
ref.hasNextPage = hasNextPage;
|
|
114
139
|
|
|
115
140
|
// Update rows
|
|
116
|
-
if (states.items == null
|
|
141
|
+
if (states.items == null || reset)
|
|
142
|
+
setStates({ items, completed: !hasNextPage });
|
|
117
143
|
else
|
|
118
144
|
setStates({
|
|
119
145
|
items: [...states.items, ...items],
|
|
@@ -121,6 +147,19 @@ export function ListMoreDisplay<T extends {}>(props: ListMoreDisplayProps<T>) {
|
|
|
121
147
|
});
|
|
122
148
|
};
|
|
123
149
|
|
|
150
|
+
const reset = (data?: GridData) => {
|
|
151
|
+
// Update the form data
|
|
152
|
+
ref.data = data;
|
|
153
|
+
|
|
154
|
+
// Reset page number
|
|
155
|
+
ref.isNextPageLoading = false;
|
|
156
|
+
ref.currentPage = 0;
|
|
157
|
+
ref.hasNextPage = true;
|
|
158
|
+
|
|
159
|
+
// Load data
|
|
160
|
+
loadDataLocal(true);
|
|
161
|
+
};
|
|
162
|
+
|
|
124
163
|
React.useEffect(() => {
|
|
125
164
|
if (autoLoad) loadDataLocal();
|
|
126
165
|
}, [autoLoad]);
|
|
@@ -131,27 +170,33 @@ export function ListMoreDisplay<T extends {}>(props: ListMoreDisplayProps<T>) {
|
|
|
131
170
|
};
|
|
132
171
|
}, []);
|
|
133
172
|
|
|
134
|
-
// Loading
|
|
135
|
-
if (states.items == null) return <CircularProgress size={20} />;
|
|
136
|
-
|
|
137
173
|
return (
|
|
138
|
-
<
|
|
139
|
-
|
|
140
|
-
<
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
</
|
|
154
|
-
|
|
155
|
-
|
|
174
|
+
<React.Fragment>
|
|
175
|
+
{headerRenderer && headerRenderer(reset)}
|
|
176
|
+
<Card {...rest}>
|
|
177
|
+
<CardHeader title={headerTitle}></CardHeader>
|
|
178
|
+
<CardContent
|
|
179
|
+
sx={{
|
|
180
|
+
paddingTop: 0,
|
|
181
|
+
paddingBottom: states.completed ? 0 : 'inherit'
|
|
182
|
+
}}
|
|
183
|
+
>
|
|
184
|
+
{states.items == null ? (
|
|
185
|
+
<CircularProgress size={20} />
|
|
186
|
+
) : (
|
|
187
|
+
states.items.map((item, index) => children(item, index))
|
|
188
|
+
)}
|
|
189
|
+
</CardContent>
|
|
190
|
+
{!states.completed && (
|
|
191
|
+
<CardActions sx={{ justifyContent: 'flex-end' }}>
|
|
192
|
+
<LoadingButton
|
|
193
|
+
onClick={async () => await loadDataLocal()}
|
|
194
|
+
>
|
|
195
|
+
{moreLabel}
|
|
196
|
+
</LoadingButton>
|
|
197
|
+
</CardActions>
|
|
198
|
+
)}
|
|
199
|
+
</Card>
|
|
200
|
+
</React.Fragment>
|
|
156
201
|
);
|
|
157
202
|
}
|
package/src/mu/LoadingButton.tsx
CHANGED
|
@@ -57,18 +57,16 @@ export function LoadingButton(props: LoadingButtonProps) {
|
|
|
57
57
|
// Update state
|
|
58
58
|
setLoading(true);
|
|
59
59
|
|
|
60
|
+
// https://stackoverflow.com/questions/38508420/how-to-know-if-a-function-is-async
|
|
60
61
|
// const AsyncFunction = (async () => {}).constructor;
|
|
61
62
|
// onClick instanceof AsyncFunction
|
|
62
|
-
|
|
63
|
-
// May long time running
|
|
64
|
-
await onClick(event);
|
|
65
|
-
} else {
|
|
66
|
-
onClick(event);
|
|
67
|
-
}
|
|
63
|
+
await onClick(event);
|
|
68
64
|
|
|
69
65
|
// Warning: Can't perform a React state update on an unmounted component
|
|
70
66
|
// It's necessary to check the component is mounted now
|
|
71
|
-
if (isMounted.current)
|
|
67
|
+
if (isMounted.current) {
|
|
68
|
+
setLoading(false);
|
|
69
|
+
}
|
|
72
70
|
}
|
|
73
71
|
}}
|
|
74
72
|
{...rest}
|