@applica-software-guru/react-admin 1.3.126 → 1.3.128
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/dist/ApplicaAdmin.d.ts +14 -29
- package/dist/ApplicaAdmin.d.ts.map +1 -1
- package/dist/components/Layout/Content.d.ts +4 -1
- package/dist/components/Layout/Content.d.ts.map +1 -1
- package/dist/components/Layout/Error.d.ts +27 -0
- package/dist/components/Layout/Error.d.ts.map +1 -0
- package/dist/components/Layout/Layout.d.ts +3 -0
- package/dist/components/Layout/Layout.d.ts.map +1 -1
- package/dist/components/Layout/index.d.ts +1 -0
- package/dist/components/Layout/index.d.ts.map +1 -1
- package/dist/components/ra-forms/LongForm/utils.d.ts.map +1 -1
- package/dist/components/ra-lists/List.d.ts +19 -18
- package/dist/components/ra-lists/List.d.ts.map +1 -1
- package/dist/components/ra-lists/ListView.d.ts +257 -0
- package/dist/components/ra-lists/ListView.d.ts.map +1 -0
- package/dist/dev/CatchResult.d.ts +17 -0
- package/dist/dev/CatchResult.d.ts.map +1 -0
- package/dist/dev/ErrorEventHandler.d.ts +13 -0
- package/dist/dev/ErrorEventHandler.d.ts.map +1 -0
- package/dist/dev/index.d.ts +4 -2
- package/dist/dev/index.d.ts.map +1 -1
- package/dist/dev/useErrorEventCatcher.d.ts +12 -0
- package/dist/dev/useErrorEventCatcher.d.ts.map +1 -0
- package/dist/react-admin.cjs.js +63 -63
- package/dist/react-admin.cjs.js.map +1 -1
- package/dist/react-admin.es.js +9563 -8992
- package/dist/react-admin.es.js.map +1 -1
- package/dist/react-admin.umd.js +66 -66
- package/dist/react-admin.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/ApplicaAdmin.tsx +19 -33
- package/src/components/Layout/Content.tsx +37 -16
- package/src/components/Layout/Error.tsx +81 -0
- package/src/components/Layout/Layout.tsx +3 -2
- package/src/components/Layout/index.ts +1 -0
- package/src/components/ra-forms/LongForm/hooks.tsx +1 -1
- package/src/components/ra-forms/LongForm/utils.ts +3 -1
- package/src/components/ra-lists/List.tsx +117 -2
- package/src/components/ra-lists/ListView.tsx +369 -0
- package/src/components/ra-pages/GenericErrorPage.tsx +1 -1
- package/src/dev/CatchResult.ts +32 -0
- package/src/dev/ErrorEventHandler.ts +51 -0
- package/src/dev/index.ts +4 -2
- package/src/dev/useErrorEventCatcher.ts +50 -0
- package/src/playground/components/pages/CustomPage.jsx +6 -0
- package/dist/dev/useCliErrorCatcher.d.ts +0 -59
- package/dist/dev/useCliErrorCatcher.d.ts.map +0 -1
- package/src/dev/useCliErrorCatcher.ts +0 -142
- /package/src/assets/{genericError.png → generic-error.png} +0 -0
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { styled } from '@mui/material/styles';
|
|
3
|
+
import { cloneElement, ReactElement, ReactNode, ElementType } from 'react';
|
|
4
|
+
import { Title, TitlePropType, Pagination as DefaultPagination, ListToolbar, ListActions as DefaultActions } from 'react-admin';
|
|
5
|
+
import PropTypes from 'prop-types';
|
|
6
|
+
import { SxProps } from '@mui/system';
|
|
7
|
+
import Card from '@mui/material/Card';
|
|
8
|
+
import clsx from 'clsx';
|
|
9
|
+
import { ComponentPropType, useListContext, RaRecord } from 'ra-core';
|
|
10
|
+
|
|
11
|
+
import Empty from './Empty';
|
|
12
|
+
import Error from '../Layout/Error';
|
|
13
|
+
|
|
14
|
+
const defaultActions = <DefaultActions />;
|
|
15
|
+
const defaultPagination = <DefaultPagination />;
|
|
16
|
+
const defaultEmpty = <Empty />;
|
|
17
|
+
const DefaultComponent = Card;
|
|
18
|
+
|
|
19
|
+
export const ListView = <RecordType extends RaRecord = any>(props: ListViewProps) => {
|
|
20
|
+
const {
|
|
21
|
+
actions = defaultActions,
|
|
22
|
+
aside,
|
|
23
|
+
filters,
|
|
24
|
+
bulkActionButtons,
|
|
25
|
+
emptyWhileLoading,
|
|
26
|
+
hasCreate,
|
|
27
|
+
pagination = defaultPagination,
|
|
28
|
+
children,
|
|
29
|
+
className,
|
|
30
|
+
component: Content = DefaultComponent,
|
|
31
|
+
title,
|
|
32
|
+
empty = defaultEmpty,
|
|
33
|
+
...rest
|
|
34
|
+
} = props;
|
|
35
|
+
const { defaultTitle, data, error, isLoading, filterValues, resource } = useListContext<RecordType>(props);
|
|
36
|
+
|
|
37
|
+
if (!children || (!data && isLoading && emptyWhileLoading)) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const renderList = () => (
|
|
42
|
+
<div className={ListClasses.main}>
|
|
43
|
+
{(filters || actions) && <ListToolbar className={ListClasses.actions} filters={filters} actions={actions} hasCreate={hasCreate} />}
|
|
44
|
+
{!error && (
|
|
45
|
+
<Content className={ListClasses.content}>
|
|
46
|
+
{bulkActionButtons && children && React.isValidElement<any>(children)
|
|
47
|
+
? // FIXME remove in 5.0
|
|
48
|
+
cloneElement(children, {
|
|
49
|
+
bulkActionButtons
|
|
50
|
+
})
|
|
51
|
+
: children}
|
|
52
|
+
</Content>
|
|
53
|
+
)}
|
|
54
|
+
|
|
55
|
+
{error ? (
|
|
56
|
+
<Error
|
|
57
|
+
error={error}
|
|
58
|
+
// @ts-ignore
|
|
59
|
+
resetErrorBoundary={null}
|
|
60
|
+
/>
|
|
61
|
+
) : (
|
|
62
|
+
pagination !== false && pagination
|
|
63
|
+
)}
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const renderEmpty = () => empty !== false && cloneElement(empty, { className: ListClasses.noResults, hasCreate });
|
|
68
|
+
|
|
69
|
+
const shouldRenderEmptyPage = !isLoading && data?.length === 0 && !Object.keys(filterValues).length && empty !== false;
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<Root className={clsx('list-page', className)} {...rest}>
|
|
73
|
+
<Title title={title} defaultTitle={defaultTitle} preferenceKey={`${resource}.list.title`} />
|
|
74
|
+
{shouldRenderEmptyPage ? renderEmpty() : renderList()}
|
|
75
|
+
{aside}
|
|
76
|
+
</Root>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
ListView.propTypes = {
|
|
81
|
+
actions: PropTypes.oneOfType([PropTypes.bool, PropTypes.element]),
|
|
82
|
+
aside: PropTypes.element,
|
|
83
|
+
children: PropTypes.node,
|
|
84
|
+
className: PropTypes.string,
|
|
85
|
+
component: ComponentPropType,
|
|
86
|
+
emptyWhileLoading: PropTypes.bool,
|
|
87
|
+
filters: PropTypes.oneOfType([PropTypes.element, PropTypes.arrayOf(PropTypes.element)]),
|
|
88
|
+
hasCreate: PropTypes.bool,
|
|
89
|
+
pagination: PropTypes.oneOfType([PropTypes.element, PropTypes.bool]),
|
|
90
|
+
title: TitlePropType
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export interface ListViewProps {
|
|
94
|
+
/**
|
|
95
|
+
* The actions to display in the toolbar. defaults to Filter + Create + Export.
|
|
96
|
+
*
|
|
97
|
+
* @see https://marmelab.com/react-admin/List.html#actions
|
|
98
|
+
* @example
|
|
99
|
+
* import {
|
|
100
|
+
* CreateButton,
|
|
101
|
+
* DatagridConfigurable,
|
|
102
|
+
* ExportButton,
|
|
103
|
+
* FilterButton,
|
|
104
|
+
* List,
|
|
105
|
+
* SelectColumnsButton,
|
|
106
|
+
* TopToolbar,
|
|
107
|
+
* } from 'react-admin';
|
|
108
|
+
* import IconEvent from '@mui/icons-material/Event';
|
|
109
|
+
*
|
|
110
|
+
* const ListActions = () => (
|
|
111
|
+
* <TopToolbar>
|
|
112
|
+
* <SelectColumnsButton />
|
|
113
|
+
* <FilterButton/>
|
|
114
|
+
* <CreateButton/>
|
|
115
|
+
* <ExportButton/>
|
|
116
|
+
* </TopToolbar>
|
|
117
|
+
* );
|
|
118
|
+
*
|
|
119
|
+
* export const PostList = () => (
|
|
120
|
+
* <List actions={<ListActions/>}>
|
|
121
|
+
* <DatagridConfigurable>
|
|
122
|
+
* ...
|
|
123
|
+
* </DatagridConfigurable>
|
|
124
|
+
* </List>
|
|
125
|
+
* );
|
|
126
|
+
*/
|
|
127
|
+
actions?: ReactElement | false;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* The content to render as a sidebar.
|
|
131
|
+
* @see https://marmelab.com/react-admin/List.html#aside
|
|
132
|
+
* @example
|
|
133
|
+
* import { List, useListContext } from 'react-admin';
|
|
134
|
+
* import { Typography } from '@mui/material';
|
|
135
|
+
*
|
|
136
|
+
* const Aside = () => {
|
|
137
|
+
* const { data, isLoading } = useListContext();
|
|
138
|
+
* if (isLoading) return null;
|
|
139
|
+
* return (
|
|
140
|
+
* <div style={{ width: 200, margin: '4em 1em' }}>
|
|
141
|
+
* <Typography variant="h6">Posts stats</Typography>
|
|
142
|
+
* <Typography variant="body2">
|
|
143
|
+
* Total views: {data.reduce((sum, post) => sum + post.views, 0)}
|
|
144
|
+
* </Typography>
|
|
145
|
+
* </div>
|
|
146
|
+
* );
|
|
147
|
+
* };
|
|
148
|
+
*
|
|
149
|
+
* const PostList = () => (
|
|
150
|
+
* <List aside={<Aside />}>
|
|
151
|
+
* ...
|
|
152
|
+
* </List>
|
|
153
|
+
* );
|
|
154
|
+
*/
|
|
155
|
+
aside?: ReactElement;
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* @deprecated pass the bulkActionButtons prop to the List child (Datagrid or SimpleList) instead
|
|
159
|
+
*/
|
|
160
|
+
bulkActionButtons?: ReactElement | false;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* A class name to apply to the root div element
|
|
164
|
+
*/
|
|
165
|
+
className?: string;
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* The components rendering the list of records. Usually a <Datagrid> or <SimpleList>.
|
|
169
|
+
*
|
|
170
|
+
* @see https://marmelab.com/react-admin/List.html#children
|
|
171
|
+
* @example
|
|
172
|
+
* import { List, Datagrid, TextField, DateField, NumberField, BooleanField, ReferenceManyCount } from 'react-admin';
|
|
173
|
+
*
|
|
174
|
+
* export const BookList = () => (
|
|
175
|
+
* <List>
|
|
176
|
+
* <Datagrid rowClick="edit">
|
|
177
|
+
* <TextField source="id" />
|
|
178
|
+
* <TextField source="title" />
|
|
179
|
+
* <DateField source="published_at" />
|
|
180
|
+
* <ReferenceManyCount label="Nb comments" reference="comments" target="post_id" link />
|
|
181
|
+
* <BooleanField source="commentable" label="Com." />
|
|
182
|
+
* <NumberField source="nb_views" label="Views" />
|
|
183
|
+
* </Datagrid>
|
|
184
|
+
* </List>
|
|
185
|
+
* );
|
|
186
|
+
*/
|
|
187
|
+
children: ReactNode;
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* The component used to display the list. Defaults to <Card>.
|
|
191
|
+
*
|
|
192
|
+
* @see https://marmelab.com/react-admin/List.html#component
|
|
193
|
+
* @example
|
|
194
|
+
* import { List } from 'react-admin';
|
|
195
|
+
*
|
|
196
|
+
* const PostList = () => (
|
|
197
|
+
* <List component="div">
|
|
198
|
+
* ...
|
|
199
|
+
* </List>
|
|
200
|
+
* );
|
|
201
|
+
*/
|
|
202
|
+
component?: ElementType;
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* The component to display when the list is empty.
|
|
206
|
+
*
|
|
207
|
+
* @see https://marmelab.com/react-admin/List.html#empty
|
|
208
|
+
* @example
|
|
209
|
+
* import { CreateButton, List } from 'react-admin';
|
|
210
|
+
* import { Box, Button, Typography } from '@mui/material';
|
|
211
|
+
*
|
|
212
|
+
* const Empty = () => (
|
|
213
|
+
* <Box textAlign="center" m={1}>
|
|
214
|
+
* <Typography variant="h4" paragraph>
|
|
215
|
+
* No products available
|
|
216
|
+
* </Typography>
|
|
217
|
+
* <Typography variant="body1">
|
|
218
|
+
* Create one or import products from a file
|
|
219
|
+
* </Typography>
|
|
220
|
+
* <CreateButton />
|
|
221
|
+
* <Button onClick={...}>Import</Button>
|
|
222
|
+
* </Box>
|
|
223
|
+
* );
|
|
224
|
+
*
|
|
225
|
+
* const ProductList = () => (
|
|
226
|
+
* <List empty={<Empty />}>
|
|
227
|
+
* ...
|
|
228
|
+
* </List>
|
|
229
|
+
* );
|
|
230
|
+
*/
|
|
231
|
+
empty?: ReactElement | false;
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Set to true to return null while the list is loading.
|
|
235
|
+
*
|
|
236
|
+
* @see https://marmelab.com/react-admin/List.html#emptywhileloading
|
|
237
|
+
* @example
|
|
238
|
+
* import { List } from 'react-admin';
|
|
239
|
+
* import { SimpleBookList } from './BookList';
|
|
240
|
+
*
|
|
241
|
+
* const BookList = () => (
|
|
242
|
+
* <List emptyWhileLoading>
|
|
243
|
+
* <SimpleBookList />
|
|
244
|
+
* </List>
|
|
245
|
+
* );
|
|
246
|
+
*/
|
|
247
|
+
emptyWhileLoading?: boolean;
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* The filter inputs to display in the toolbar.
|
|
251
|
+
*
|
|
252
|
+
* @see https://marmelab.com/react-admin/List.html#filters
|
|
253
|
+
* @example
|
|
254
|
+
* import { List, TextInput } from 'react-admin';
|
|
255
|
+
*
|
|
256
|
+
* const postFilters = [
|
|
257
|
+
* <TextInput label="Search" source="q" alwaysOn />,
|
|
258
|
+
* <TextInput label="Title" source="title" defaultValue="Hello, World!" />,
|
|
259
|
+
* ];
|
|
260
|
+
*
|
|
261
|
+
* export const PostList = () => (
|
|
262
|
+
* <List filters={postFilters}>
|
|
263
|
+
* ...
|
|
264
|
+
* </List>
|
|
265
|
+
* );
|
|
266
|
+
*/
|
|
267
|
+
filters?: ReactElement | ReactElement[];
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Set to true to force a Create button in the toolbar, even if there is no create view declared in Resource
|
|
271
|
+
*
|
|
272
|
+
* @see https://marmelab.com/react-admin/List.html#hascreate
|
|
273
|
+
* @example
|
|
274
|
+
* import { List } from 'react-admin';
|
|
275
|
+
*
|
|
276
|
+
* export const PostList = () => (
|
|
277
|
+
* <List hasCreate={false}>
|
|
278
|
+
* ...
|
|
279
|
+
* </List>
|
|
280
|
+
* );
|
|
281
|
+
*/
|
|
282
|
+
hasCreate?: boolean;
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* The pagination component to display. defaults to <Pagination />
|
|
286
|
+
*
|
|
287
|
+
* @see https://marmelab.com/react-admin/List.html#pagination
|
|
288
|
+
* @example
|
|
289
|
+
* import { Pagination, List } from 'react-admin';
|
|
290
|
+
*
|
|
291
|
+
* const PostPagination = props => <Pagination rowsPerPageOptions={[10, 25, 50, 100]} {...props} />;
|
|
292
|
+
*
|
|
293
|
+
* export const PostList = () => (
|
|
294
|
+
* <List pagination={<PostPagination />}>
|
|
295
|
+
* ...
|
|
296
|
+
* </List>
|
|
297
|
+
* );
|
|
298
|
+
*/
|
|
299
|
+
pagination?: ReactElement | false;
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* The page title (main title) to display above the data. Defaults to the humanized resource name.
|
|
303
|
+
*
|
|
304
|
+
* @see https://marmelab.com/react-admin/List.html#title
|
|
305
|
+
* @example
|
|
306
|
+
* import { List } from 'react-admin';
|
|
307
|
+
*
|
|
308
|
+
* export const PostList = () => (
|
|
309
|
+
* <List title="List of posts">
|
|
310
|
+
* ...
|
|
311
|
+
* </List>
|
|
312
|
+
* );
|
|
313
|
+
*/
|
|
314
|
+
title?: string | ReactElement;
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* The CSS styles to apply to the component.
|
|
318
|
+
*
|
|
319
|
+
* @see https://marmelab.com/react-admin/List.html#sx-css-api
|
|
320
|
+
* @example
|
|
321
|
+
* const PostList = () => (
|
|
322
|
+
* <List
|
|
323
|
+
* sx={{
|
|
324
|
+
* backgroundColor: 'yellow',
|
|
325
|
+
* '& .RaList-content': {
|
|
326
|
+
* backgroundColor: 'red',
|
|
327
|
+
* },
|
|
328
|
+
* }}
|
|
329
|
+
* >
|
|
330
|
+
* ...
|
|
331
|
+
* </List>
|
|
332
|
+
* );
|
|
333
|
+
*/
|
|
334
|
+
sx?: SxProps;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const PREFIX = 'RaList';
|
|
338
|
+
|
|
339
|
+
export const ListClasses = {
|
|
340
|
+
main: `${PREFIX}-main`,
|
|
341
|
+
content: `${PREFIX}-content`,
|
|
342
|
+
actions: `${PREFIX}-actions`,
|
|
343
|
+
noResults: `${PREFIX}-noResults`
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
const Root = styled('div', {
|
|
347
|
+
name: PREFIX,
|
|
348
|
+
overridesResolver: (props, styles) => styles.root
|
|
349
|
+
})(({ theme }) => ({
|
|
350
|
+
display: 'flex',
|
|
351
|
+
|
|
352
|
+
[`& .${ListClasses.main}`]: {
|
|
353
|
+
flex: '1 1 auto',
|
|
354
|
+
display: 'flex',
|
|
355
|
+
flexDirection: 'column'
|
|
356
|
+
},
|
|
357
|
+
|
|
358
|
+
[`& .${ListClasses.content}`]: {
|
|
359
|
+
position: 'relative',
|
|
360
|
+
[theme.breakpoints.down('sm')]: {
|
|
361
|
+
boxShadow: 'none'
|
|
362
|
+
},
|
|
363
|
+
overflow: 'inherit'
|
|
364
|
+
},
|
|
365
|
+
|
|
366
|
+
[`& .${ListClasses.actions}`]: {},
|
|
367
|
+
|
|
368
|
+
[`& .${ListClasses.noResults}`]: {}
|
|
369
|
+
}));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useCallback } from 'react';
|
|
2
2
|
import { Button, Container, Stack, Typography } from '@mui/material';
|
|
3
3
|
//@ts-ignore
|
|
4
|
-
import imgSrc from '../../assets/
|
|
4
|
+
import imgSrc from '../../assets/generic-error.png';
|
|
5
5
|
|
|
6
6
|
function GenericErrorPage() {
|
|
7
7
|
const onReload = useCallback(() => location.reload(), []);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export type CatchResultConstructorData = {
|
|
2
|
+
catch: boolean;
|
|
3
|
+
display: boolean;
|
|
4
|
+
log: boolean;
|
|
5
|
+
error?: string;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export default class CatchResult {
|
|
9
|
+
catch: boolean;
|
|
10
|
+
display: boolean;
|
|
11
|
+
log: boolean;
|
|
12
|
+
error?: string;
|
|
13
|
+
|
|
14
|
+
constructor({ catch: catchErr, display: displayErr, log: logErr, error }: CatchResultConstructorData) {
|
|
15
|
+
this.catch = catchErr;
|
|
16
|
+
this.display = displayErr;
|
|
17
|
+
this.log = logErr;
|
|
18
|
+
this.error = error;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
isCatched() {
|
|
22
|
+
return this.catch;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
logError() {
|
|
26
|
+
return this.log;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
displayError() {
|
|
30
|
+
return this.display;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
export type IErrorEventHandlerConstructData = {
|
|
3
|
+
apiUrl: string;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export interface IErrorEventHandler {
|
|
7
|
+
handle(event: ErrorEvent): void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
class ErrorEventHandler implements IErrorEventHandler {
|
|
11
|
+
#headers: Headers;
|
|
12
|
+
#apiUrl: string;
|
|
13
|
+
#handledErrors: Set<string> = new Set<string>();
|
|
14
|
+
|
|
15
|
+
constructor(data: IErrorEventHandlerConstructData) {
|
|
16
|
+
const { apiUrl } = data ?? {};
|
|
17
|
+
if (!_.isString(apiUrl) || _.isEmpty(apiUrl)) {
|
|
18
|
+
throw new Error('[ErrorMessageHandler] constructor: please provide a valid apiUrl');
|
|
19
|
+
}
|
|
20
|
+
this.#apiUrl = apiUrl;
|
|
21
|
+
this.#headers = new Headers({ Accept: 'application/json', 'Content-Type': 'application/json' });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
handle(error: ErrorEvent): void {
|
|
25
|
+
const { message, filename, lineno, colno } = error;
|
|
26
|
+
const hash = `${message}${filename}${lineno}${colno}`;
|
|
27
|
+
if (this.#handledErrors.has(hash)) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
this.#handledErrors.add(hash);
|
|
32
|
+
fetch(`${this.#apiUrl}/ui/error-log`, {
|
|
33
|
+
method: 'PUT',
|
|
34
|
+
headers: this.#headers,
|
|
35
|
+
body: JSON.stringify({
|
|
36
|
+
filename: error.filename,
|
|
37
|
+
message: error.message,
|
|
38
|
+
line: error.lineno,
|
|
39
|
+
column: error.colno,
|
|
40
|
+
stack: error.error?.stack,
|
|
41
|
+
})
|
|
42
|
+
}).catch(() => {
|
|
43
|
+
// eslint-disable-next-line no-console
|
|
44
|
+
console.log('Unable to send error to server');
|
|
45
|
+
this.#handledErrors.delete(hash);
|
|
46
|
+
});
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export default ErrorEventHandler;
|
package/src/dev/index.ts
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import useErrorEventCatcher from './useErrorEventCatcher';
|
|
2
|
+
import CatchResult from './CatchResult';
|
|
3
|
+
import ErrorEventHandler from './ErrorEventHandler';
|
|
4
|
+
export { useErrorEventCatcher, CatchResult, ErrorEventHandler };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/* eslint-disable prefer-spread */
|
|
2
|
+
/* eslint-disable prefer-rest-params */
|
|
3
|
+
/* eslint-disable no-console */
|
|
4
|
+
import * as React from 'react';
|
|
5
|
+
import ErrorEventHandler, { IErrorEventHandler } from './ErrorEventHandler';
|
|
6
|
+
import CatchResult from './CatchResult';
|
|
7
|
+
|
|
8
|
+
export type UseErrorEventCatcherProps = {
|
|
9
|
+
apiUrl: string;
|
|
10
|
+
enabled?: boolean;
|
|
11
|
+
loading?: boolean;
|
|
12
|
+
catcherFn?: (error: string | any) => CatchResult;
|
|
13
|
+
errorHandler?: IErrorEventHandler;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const useErrorEventCatcher = ({
|
|
17
|
+
enabled = true,
|
|
18
|
+
apiUrl,
|
|
19
|
+
loading,
|
|
20
|
+
catcherFn = (error) => new CatchResult({ catch: error != undefined, display: false, log: error != undefined, error }),
|
|
21
|
+
...props
|
|
22
|
+
}: UseErrorEventCatcherProps) => {
|
|
23
|
+
const errorHandler = React.useMemo(() => {
|
|
24
|
+
if (props.errorHandler) {
|
|
25
|
+
return props.errorHandler;
|
|
26
|
+
}
|
|
27
|
+
return new ErrorEventHandler({ apiUrl });
|
|
28
|
+
}, [apiUrl, props.errorHandler]);
|
|
29
|
+
const handleError = React.useCallback(
|
|
30
|
+
function (event: ErrorEvent) {
|
|
31
|
+
const catchResult = catcherFn(event);
|
|
32
|
+
|
|
33
|
+
if (catchResult.logError()) {
|
|
34
|
+
errorHandler.handle(event);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return !catchResult.isCatched() || catchResult.displayError();
|
|
38
|
+
},
|
|
39
|
+
[apiUrl, loading, catcherFn, enabled, errorHandler]
|
|
40
|
+
);
|
|
41
|
+
React.useEffect(() => {
|
|
42
|
+
window.removeEventListener('error', handleError);
|
|
43
|
+
window.addEventListener('error', handleError);
|
|
44
|
+
return () => {
|
|
45
|
+
window.removeEventListener('error', handleError);
|
|
46
|
+
};
|
|
47
|
+
}, [handleError]);
|
|
48
|
+
return true;
|
|
49
|
+
};
|
|
50
|
+
export default useErrorEventCatcher;
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { Typography } from '@mui/material';
|
|
2
2
|
|
|
3
|
+
import { useEffect } from 'react';
|
|
4
|
+
|
|
3
5
|
const CustomPage = () => {
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
throw new Error('Custom Page not implemented yet');
|
|
8
|
+
}, []);
|
|
9
|
+
|
|
4
10
|
return <Typography>Hello, Custom Page!</Typography>;
|
|
5
11
|
};
|
|
6
12
|
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
export type CatchResultProps = {
|
|
2
|
-
catch: boolean;
|
|
3
|
-
display: boolean;
|
|
4
|
-
log: boolean;
|
|
5
|
-
error?: string;
|
|
6
|
-
};
|
|
7
|
-
declare class CatchResult {
|
|
8
|
-
catch: boolean;
|
|
9
|
-
display: boolean;
|
|
10
|
-
log: boolean;
|
|
11
|
-
error?: string;
|
|
12
|
-
constructor({ catch: catchErr, display: displayErr, log: logErr, error }: CatchResultProps);
|
|
13
|
-
isCatched(): boolean;
|
|
14
|
-
logError(): boolean;
|
|
15
|
-
displayError(): boolean;
|
|
16
|
-
}
|
|
17
|
-
export type CliErrorCatcherBodyBuilderProps = (error: string | any) => any;
|
|
18
|
-
export type UseCliErrorCatcherProps = {
|
|
19
|
-
/**
|
|
20
|
-
* Definisce l'URL del server API.
|
|
21
|
-
*/
|
|
22
|
-
apiUrl: string;
|
|
23
|
-
/**
|
|
24
|
-
* Definisce se il catcher è abilitato.
|
|
25
|
-
*/
|
|
26
|
-
enabled?: boolean;
|
|
27
|
-
/**
|
|
28
|
-
* Definisce l'endpoint del server API su cui inviare l'errore.
|
|
29
|
-
* Questo parametro viene aggiunto all'URL del server API (apiUrl) attraverso una semplice concatenazione.
|
|
30
|
-
*
|
|
31
|
-
* @example
|
|
32
|
-
* // Serve più per ricordarti come funziona quando lo utilizzerai attivamente dopo tanto tempo (ti conosco bene Roberto, dimenticherai tutto!).
|
|
33
|
-
* let apiUrl = 'http://localhost:3000';
|
|
34
|
-
* let endpoint = '/ui-errors/put';
|
|
35
|
-
* let url = apiUrl + endpoint;
|
|
36
|
-
* // url = 'http://localhost:3000/ui-errors/put'
|
|
37
|
-
*/
|
|
38
|
-
endpoint?: string;
|
|
39
|
-
/**
|
|
40
|
-
* Definisce se l'applicazione chiamante è in caricamento.
|
|
41
|
-
* In tal caso il catcher non viene attivato.
|
|
42
|
-
*/
|
|
43
|
-
loading?: boolean;
|
|
44
|
-
/**
|
|
45
|
-
* Definisce la funzione che determina se l'errore deve essere catturato.
|
|
46
|
-
*
|
|
47
|
-
* @param error
|
|
48
|
-
* @returns {CatchResult} Restituisce un oggetto di tipo CatchResult.
|
|
49
|
-
*/
|
|
50
|
-
catcherFn?: (error: string | any) => CatchResult;
|
|
51
|
-
/**
|
|
52
|
-
* Definisce la funzione che costruisce il body da inviare al server API.
|
|
53
|
-
*/
|
|
54
|
-
bodyBuilder?: CliErrorCatcherBodyBuilderProps;
|
|
55
|
-
};
|
|
56
|
-
declare const useCliErrorCatcher: ({ enabled, apiUrl, endpoint, loading, catcherFn, bodyBuilder }: UseCliErrorCatcherProps) => boolean;
|
|
57
|
-
export { CatchResult };
|
|
58
|
-
export default useCliErrorCatcher;
|
|
59
|
-
//# sourceMappingURL=useCliErrorCatcher.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useCliErrorCatcher.d.ts","sourceRoot":"","sources":["../../../src/dev/useCliErrorCatcher.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,OAAO,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,cAAM,WAAW;IACf,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,OAAO,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;gBAEH,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,gBAAgB;IAO1F,SAAS;IAIT,QAAQ;IAIR,YAAY;CAGb;AAED,MAAM,MAAM,+BAA+B,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,KAAK,GAAG,CAAC;AAmB3E,MAAM,MAAM,uBAAuB,GAAG;IACpC;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,KAAK,WAAW,CAAC;IACjD;;OAEG;IACH,WAAW,CAAC,EAAE,+BAA+B,CAAC;CAC/C,CAAC;AAEF,QAAA,MAAM,kBAAkB,mEAOrB,uBAAuB,YAoCzB,CAAC;AACF,OAAO,EAAE,WAAW,EAAE,CAAC;AAEvB,eAAe,kBAAkB,CAAC"}
|