@proteinjs/ui 2.0.2 → 2.0.4

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.
Files changed (80) hide show
  1. package/.eslintrc.js +20 -0
  2. package/.prettierignore +4 -0
  3. package/.prettierrc +8 -0
  4. package/CHANGELOG.md +16 -14
  5. package/LICENSE +21 -0
  6. package/dist/generated/index.js +1 -1
  7. package/dist/generated/index.js.map +1 -1
  8. package/dist/src/container/AccountIconButton.d.ts +1 -1
  9. package/dist/src/container/AccountIconButton.d.ts.map +1 -1
  10. package/dist/src/container/AccountIconButton.js +13 -6
  11. package/dist/src/container/AccountIconButton.js.map +1 -1
  12. package/dist/src/container/NavMenu.d.ts.map +1 -1
  13. package/dist/src/container/NavMenu.js +9 -9
  14. package/dist/src/container/NavMenu.js.map +1 -1
  15. package/dist/src/container/PageContainer.d.ts +1 -1
  16. package/dist/src/container/PageContainer.d.ts.map +1 -1
  17. package/dist/src/container/PageContainer.js +12 -7
  18. package/dist/src/container/PageContainer.js.map +1 -1
  19. package/dist/src/form/Field.d.ts.map +1 -1
  20. package/dist/src/form/Field.js +4 -2
  21. package/dist/src/form/Field.js.map +1 -1
  22. package/dist/src/form/Form.d.ts.map +1 -1
  23. package/dist/src/form/Form.js +75 -40
  24. package/dist/src/form/Form.js.map +1 -1
  25. package/dist/src/form/FormButton.d.ts.map +1 -1
  26. package/dist/src/form/FormButton.js +1 -1
  27. package/dist/src/form/FormButton.js.map +1 -1
  28. package/dist/src/form/container/FormPage.d.ts.map +1 -1
  29. package/dist/src/form/container/FormPage.js.map +1 -1
  30. package/dist/src/form/container/FormPaper.js +1 -1
  31. package/dist/src/form/container/FormPaper.js.map +1 -1
  32. package/dist/src/form/fields/TextField.d.ts.map +1 -1
  33. package/dist/src/form/fields/TextField.js +8 -6
  34. package/dist/src/form/fields/TextField.js.map +1 -1
  35. package/dist/src/list/NestedList.d.ts.map +1 -1
  36. package/dist/src/list/NestedList.js +10 -10
  37. package/dist/src/list/NestedList.js.map +1 -1
  38. package/dist/src/router/Page.d.ts.map +1 -1
  39. package/dist/src/router/Router.d.ts.map +1 -1
  40. package/dist/src/router/Router.js +11 -7
  41. package/dist/src/router/Router.js.map +1 -1
  42. package/dist/src/router/createUrlParams.d.ts.map +1 -1
  43. package/dist/src/router/createUrlParams.js +2 -1
  44. package/dist/src/router/createUrlParams.js.map +1 -1
  45. package/dist/src/router/withRouter.d.ts.map +1 -1
  46. package/dist/src/router/withRouter.js +1 -1
  47. package/dist/src/router/withRouter.js.map +1 -1
  48. package/dist/src/table/Table.d.ts +1 -1
  49. package/dist/src/table/Table.d.ts.map +1 -1
  50. package/dist/src/table/Table.js +31 -23
  51. package/dist/src/table/Table.js.map +1 -1
  52. package/dist/src/table/TableButton.d.ts.map +1 -1
  53. package/dist/src/table/TableLoader.d.ts.map +1 -1
  54. package/dist/src/table/TableToolbar.d.ts.map +1 -1
  55. package/dist/src/table/TableToolbar.js +10 -7
  56. package/dist/src/table/TableToolbar.js.map +1 -1
  57. package/generated/index.ts +1 -1
  58. package/index.ts +1 -1
  59. package/jest.config.js +8 -17
  60. package/package.json +56 -50
  61. package/src/container/AccountIconButton.tsx +103 -102
  62. package/src/container/NavMenu.tsx +20 -21
  63. package/src/container/PageContainer.tsx +110 -80
  64. package/src/form/Field.tsx +42 -35
  65. package/src/form/Form.tsx +370 -351
  66. package/src/form/FormButton.tsx +24 -22
  67. package/src/form/FunctionalForm.tsx +15 -15
  68. package/src/form/container/FormPage.tsx +16 -18
  69. package/src/form/container/FormPaper.tsx +12 -12
  70. package/src/form/fields/TextField.tsx +63 -61
  71. package/src/list/NestedList.tsx +18 -21
  72. package/src/router/Page.ts +18 -18
  73. package/src/router/Router.tsx +66 -52
  74. package/src/router/createUrlParams.ts +8 -7
  75. package/src/router/withRouter.tsx +6 -11
  76. package/src/table/Table.tsx +96 -80
  77. package/src/table/TableButton.ts +9 -9
  78. package/src/table/TableLoader.ts +6 -6
  79. package/src/table/TableToolbar.tsx +52 -59
  80. package/tsconfig.json +17 -17
@@ -2,29 +2,31 @@ import React from 'react';
2
2
  import { Fields } from './Field';
3
3
 
4
4
  export type FormButton<F extends Fields> = {
5
- name: string,
5
+ name: string;
6
6
  accessibility?: {
7
- disabled?: boolean,
8
- hidden?: boolean,
9
- },
10
- style: {
11
- color?: 'inherit'|'primary'|'success'|'warning'|'secondary'|'error'|'info',
12
- variant?: 'text'|'outlined'|'contained',
13
- icon?: React.ComponentType,
14
- },
15
- clearFormOnClick?: boolean,
16
- redirect?: (fields: F, buttons: FormButtons<F>) => Promise<{ path: string, props?: { [key: string]: any } }>,
17
- onClick?: (fields: F, buttons: FormButtons<F>) => Promise<string|void>,
18
- progressMessage?: (fields: F) => string
19
- }
7
+ disabled?: boolean;
8
+ hidden?: boolean;
9
+ };
10
+ style: {
11
+ color?: 'inherit' | 'primary' | 'success' | 'warning' | 'secondary' | 'error' | 'info';
12
+ variant?: 'text' | 'outlined' | 'contained';
13
+ icon?: React.ComponentType;
14
+ };
15
+ clearFormOnClick?: boolean;
16
+ redirect?: (fields: F, buttons: FormButtons<F>) => Promise<{ path: string; props?: { [key: string]: any } }>;
17
+ onClick?: (fields: F, buttons: FormButtons<F>) => Promise<string | void>;
18
+ progressMessage?: (fields: F) => string;
19
+ };
20
20
 
21
- export abstract class FormButtons<F extends Fields> { [name: string]: FormButton<F> }
21
+ export abstract class FormButtons<F extends Fields> {
22
+ [name: string]: FormButton<F>;
23
+ }
22
24
 
23
25
  export const clearButton: FormButton<any> = {
24
- name: 'Clear',
25
- style: {
26
- color: 'primary',
27
- variant: 'text',
28
- },
29
- clearFormOnClick: true
30
- };
26
+ name: 'Clear',
27
+ style: {
28
+ color: 'primary',
29
+ variant: 'text',
30
+ },
31
+ clearFormOnClick: true,
32
+ };
@@ -188,7 +188,7 @@
188
188
  // </Grid>
189
189
  // );
190
190
  // }
191
-
191
+
192
192
  // function Status() {
193
193
  // if (!status || !status.message)
194
194
  // return null;
@@ -206,7 +206,7 @@
206
206
  // </Grid>
207
207
  // );
208
208
  // }
209
-
209
+
210
210
  // function Fields() {
211
211
  // return (
212
212
  // <Grid container spacing={8}>
@@ -214,7 +214,7 @@
214
214
  // getFieldRows().map((fieldComponents, index) => {
215
215
  // if (!fieldRowVisible(fieldComponents))
216
216
  // return null;
217
-
217
+
218
218
  // return (
219
219
  // <Grid item xs={12} key={index}>
220
220
  // <Grid container spacing={8}>
@@ -222,7 +222,7 @@
222
222
  // fieldComponents.filter((fieldComponent) => {
223
223
  // if (fieldComponent.field.accessibility?.hidden)
224
224
  // return false;
225
-
225
+
226
226
  // return true;
227
227
  // }).map((fieldComponent, key) => {
228
228
  // const fieldKey = fieldRenderKeys[fieldComponent.field.name] ? `${fieldComponent.field.name}_${fieldRenderKeys[fieldComponent.field.name]}` : `${fieldComponent.field.name}`;
@@ -245,7 +245,7 @@
245
245
  // </Grid>
246
246
  // );
247
247
  // }
248
-
248
+
249
249
  // function getFieldRows(): FieldComponent<any, any>[][] {
250
250
  // const rows: FieldComponent<any, any>[][] = [];
251
251
  // if (!fields)
@@ -281,33 +281,33 @@
281
281
  // const field = fieldComponent.field;
282
282
  // if (!field.layout)
283
283
  // throw new Error(`Unless using FormProps.fieldLayout, Field.layout must be provided; layout not provided for field: ${field.name}`);
284
-
284
+
285
285
  // if (typeof rows[field.layout.row] === 'undefined')
286
286
  // rows[field.layout.row] = [];
287
-
287
+
288
288
  // if (field.layout.column && rows[field.layout.row].length >= field.layout.column) {
289
289
  // rows[field.layout.row].splice(field.layout.column - 1, 0, fieldComponent);
290
290
  // } else
291
291
  // rows[field.layout.row].push(fieldComponent);
292
-
292
+
293
293
  // const rowWidth = rows[field.layout.row].map((fieldComponent) => fieldComponent.field.layout?.width ? fieldComponent.field.layout.width as number : 0).reduce((accumulator, currentWidth) => accumulator + currentWidth);
294
294
  // if (rowWidth > 12)
295
295
  // throw new Error(`Width of row exceeds maximum width (12), row width: ${rowWidth}, row index: ${field.layout.row}`);
296
296
  // }
297
297
  // }
298
-
298
+
299
299
  // return rows;
300
300
  // }
301
-
301
+
302
302
  // function fieldRowVisible(fieldComponents: FieldComponent<any, any>[]): boolean {
303
303
  // for (const fieldComponent of fieldComponents) {
304
304
  // if (!fieldComponent.field.accessibility || !fieldComponent.field.accessibility?.hidden)
305
305
  // return true;
306
306
  // }
307
-
307
+
308
308
  // return false;
309
309
  // }
310
-
310
+
311
311
  // function Progress() {
312
312
  // if (!progress || !progress.visible)
313
313
  // return null;
@@ -320,7 +320,7 @@
320
320
  // </Grid>
321
321
  // );
322
322
  // }
323
-
323
+
324
324
  // function Buttons() {
325
325
  // return (
326
326
  // <Grid container justify='flex-end' alignItems='flex-end' spacing={2}>
@@ -328,7 +328,7 @@
328
328
  // Object.keys(props.buttons).map((buttonPropertyName) => props.buttons[buttonPropertyName]).filter((button) => {
329
329
  // if (button.accessibility?.hidden)
330
330
  // return false;
331
-
331
+
332
332
  // return true;
333
333
  // }).map((button, key) => {
334
334
  // return (
@@ -361,4 +361,4 @@
361
361
  // </Grid>
362
362
  // );
363
363
  // }
364
- // }
364
+ // }
@@ -3,21 +3,19 @@ import { Grid, PaperProps } from '@mui/material';
3
3
  import { FormPaper } from './FormPaper';
4
4
 
5
5
  export function FormPage(props: PaperProps) {
6
- return (
7
- <Grid
8
- container
9
- sx={(theme) => ({
10
- marginTop: theme.spacing(4),
11
- })}
12
- direction='row'
13
- justifyContent='center'
14
- alignItems='center'
15
- >
16
- <Grid item>
17
- <FormPaper {...props}>
18
- {props.children}
19
- </FormPaper>
20
- </Grid>
21
- </Grid>
22
- );
23
- }
6
+ return (
7
+ <Grid
8
+ container
9
+ sx={(theme) => ({
10
+ marginTop: theme.spacing(4),
11
+ })}
12
+ direction='row'
13
+ justifyContent='center'
14
+ alignItems='center'
15
+ >
16
+ <Grid item>
17
+ <FormPaper {...props}>{props.children}</FormPaper>
18
+ </Grid>
19
+ </Grid>
20
+ );
21
+ }
@@ -2,15 +2,15 @@ import React from 'react';
2
2
  import { PaperProps, Paper } from '@mui/material';
3
3
 
4
4
  export function FormPaper(props: PaperProps) {
5
- return (
6
- <Paper
7
- sx={(theme) => ({
8
- padding: theme.spacing(2, 2, 1),
9
- width: 'fit-content'
10
- })}
11
- {...props}
12
- >
13
- {props.children}
14
- </Paper>
15
- );
16
- }
5
+ return (
6
+ <Paper
7
+ sx={(theme) => ({
8
+ padding: theme.spacing(2, 2, 1),
9
+ width: 'fit-content',
10
+ })}
11
+ {...props}
12
+ >
13
+ {props.children}
14
+ </Paper>
15
+ );
16
+ }
@@ -4,68 +4,70 @@ import { Visibility, VisibilityOff } from '@mui/icons-material';
4
4
  import { Field, FieldComponent, FieldComponentProps, fieldDisplayValue, fieldLabel, Fields } from '../Field';
5
5
 
6
6
  export type TextFieldProps<T, F extends Fields> = Field<T, F> & {
7
- isPassword?: boolean
8
- }
7
+ isPassword?: boolean;
8
+ };
9
9
 
10
10
  export function textField<F extends Fields>(props: TextFieldProps<string, F>): FieldComponent<string, F> {
11
- const { isPassword } = props;
11
+ const { isPassword } = props;
12
+
13
+ return {
14
+ field: props,
15
+ component: TextField,
16
+ };
12
17
 
13
- return {
14
- field: props,
15
- component: TextField
16
- };
18
+ function TextField(props: FieldComponentProps<string, F>) {
19
+ const { field, onChange, ...other } = props;
20
+ const [error, setError] = React.useState(false);
21
+ const [statusMessage, setStatusMessage] = React.useState<string>();
22
+ const [passwordVisible, setPasswordVisible] = React.useState(false);
17
23
 
18
- function TextField(props: FieldComponentProps<string, F>) {
19
- const { field, onChange, ...other } = props;
20
- const [error, setError] = React.useState(false);
21
- const [statusMessage, setStatusMessage] = React.useState<string>();
22
- const [passwordVisible, setPasswordVisible] = React.useState(false);
23
-
24
- return (
25
- <MuiTextField
26
- sx={{
27
- display: 'flex',
28
- flexWrap: 'nowrap',
29
- }}
30
- key={field.name}
31
- label={fieldLabel(field)}
32
- type={isPassword && !passwordVisible ? 'password' : 'text'}
33
- value={fieldDisplayValue(field)}
34
- error={error}
35
- helperText={statusMessage ? statusMessage : field.description}
36
- required={field.accessibility?.required}
37
- disabled={field.accessibility?.readonly}
38
- onChange={event => {
39
- let errorReceived = false;
40
- let messageReceived: string;
41
- onChange(field, event.target.value, (message: string, isError: boolean) => {
42
- setError(isError);
43
- setStatusMessage(message);
44
- errorReceived = isError;
45
- messageReceived = message;
46
- }).then(() => { // setFieldStatus may not be called in Field.onChange, but we still want to run after Form.onChange
47
- if (!errorReceived)
48
- setError(false);
49
-
50
- if (!messageReceived)
51
- setStatusMessage(undefined);
52
- })
53
- }}
54
- InputProps={{
55
- endAdornment: (
56
- isPassword &&
57
- <InputAdornment position='end'>
58
- <IconButton
59
- aria-label='Toggle password visibility'
60
- onClick={event => setPasswordVisible(!passwordVisible)}
61
- >
62
- {passwordVisible ? <VisibilityOff /> : <Visibility />}
63
- </IconButton>
64
- </InputAdornment>
65
- )
66
- }}
67
- {...other}
68
- />
69
- );
70
- }
71
- }
24
+ return (
25
+ <MuiTextField
26
+ sx={{
27
+ display: 'flex',
28
+ flexWrap: 'nowrap',
29
+ }}
30
+ key={field.name}
31
+ label={fieldLabel(field)}
32
+ type={isPassword && !passwordVisible ? 'password' : 'text'}
33
+ value={fieldDisplayValue(field)}
34
+ error={error}
35
+ helperText={statusMessage ? statusMessage : field.description}
36
+ required={field.accessibility?.required}
37
+ disabled={field.accessibility?.readonly}
38
+ onChange={(event) => {
39
+ let errorReceived = false;
40
+ let messageReceived: string;
41
+ onChange(field, event.target.value, (message: string, isError: boolean) => {
42
+ setError(isError);
43
+ setStatusMessage(message);
44
+ errorReceived = isError;
45
+ messageReceived = message;
46
+ }).then(() => {
47
+ // setFieldStatus may not be called in Field.onChange, but we still want to run after Form.onChange
48
+ if (!errorReceived) {
49
+ setError(false);
50
+ }
51
+
52
+ if (!messageReceived) {
53
+ setStatusMessage(undefined);
54
+ }
55
+ });
56
+ }}
57
+ InputProps={{
58
+ endAdornment: isPassword && (
59
+ <InputAdornment position='end'>
60
+ <IconButton
61
+ aria-label='Toggle password visibility'
62
+ onClick={(event) => setPasswordVisible(!passwordVisible)}
63
+ >
64
+ {passwordVisible ? <VisibilityOff /> : <Visibility />}
65
+ </IconButton>
66
+ </InputAdornment>
67
+ ),
68
+ }}
69
+ {...other}
70
+ />
71
+ );
72
+ }
73
+ }
@@ -1,8 +1,8 @@
1
- import React, { ReactElement } from 'react'
2
- import { Box } from '@mui/material'
1
+ import React, { ReactElement } from 'react';
2
+ import { Box } from '@mui/material';
3
3
 
4
- export type NestedListItem = { element: ReactElement, children?: NestedListItem[], renderChildren?: boolean }
5
- type NestedListProps = { items: NestedListItem[], level?: number, levelColors?: string[] }
4
+ export type NestedListItem = { element: ReactElement; children?: NestedListItem[]; renderChildren?: boolean };
5
+ type NestedListProps = { items: NestedListItem[]; level?: number; levelColors?: string[] };
6
6
  const blue = '#1976d2';
7
7
  const green = '#76d275';
8
8
  const orange = '#ffa726';
@@ -15,39 +15,36 @@ export const NestedList = ({ items, level, levelColors }: NestedListProps) => {
15
15
  const resolvedLevelColors = levelColors ? levelColors : defaultLevelColors;
16
16
  const flattenedItems = flattenItems(items);
17
17
 
18
- function flattenItems(items: NestedListItem[]): (NestedListItem|NestedListItem[])[] {
18
+ function flattenItems(items: NestedListItem[]): (NestedListItem | NestedListItem[])[] {
19
19
  const flattened = [];
20
- for (let item of items) {
20
+ for (const item of items) {
21
21
  flattened.push(item);
22
- if (item.children && (item.renderChildren || typeof item.renderChildren === 'undefined'))
22
+ if (item.children && (item.renderChildren || typeof item.renderChildren === 'undefined')) {
23
23
  flattened.push(item.children);
24
+ }
24
25
  }
25
26
 
26
27
  return flattened;
27
28
  }
28
29
 
29
30
  return (
30
- <Box
31
- component='ul'
32
- sx={{
31
+ <Box
32
+ component='ul'
33
+ sx={{
33
34
  padding: isRoot ? 0 : undefined,
34
35
  margin: isRoot ? 0 : undefined,
35
36
  }}
36
37
  >
37
- {
38
- flattenedItems.map((item, index) => (Array.isArray(item)) ?
39
- <NestedList
40
- key={index}
41
- items={item}
42
- level={resolvedLevel + 1}
43
- />
44
- :
38
+ {flattenedItems.map((item, index) =>
39
+ Array.isArray(item) ? (
40
+ <NestedList key={index} items={item} level={resolvedLevel + 1} />
41
+ ) : (
45
42
  <Box
46
43
  key={index}
47
44
  component='li'
48
45
  sx={(theme) => ({
49
46
  borderLeft: 3,
50
- borderLeftColor: resolvedLevelColors[resolvedLevel%resolvedLevelColors.length],
47
+ borderLeftColor: resolvedLevelColors[resolvedLevel % resolvedLevelColors.length],
51
48
  listStyleType: 'none',
52
49
  marginTop: !(isRoot && index == 0) ? theme.spacing(1) : undefined,
53
50
  })}
@@ -55,7 +52,7 @@ export const NestedList = ({ items, level, levelColors }: NestedListProps) => {
55
52
  {item.element}
56
53
  </Box>
57
54
  )
58
- }
55
+ )}
59
56
  </Box>
60
57
  );
61
- }
58
+ };
@@ -6,23 +6,23 @@ import { NavigateFunction } from 'react-router-dom';
6
6
  export const getPages = () => SourceRepository.get().objects<Page>('@proteinjs/ui/Page');
7
7
 
8
8
  export type PageComponentProps = {
9
- urlParams: {[key: string]: string},
10
- navigate: NavigateFunction,
11
- }
9
+ urlParams: { [key: string]: string };
10
+ navigate: NavigateFunction;
11
+ };
12
12
 
13
13
  export interface Page extends Loadable {
14
- name: string;
15
- path: string|string[];
16
- component: React.ComponentType<PageComponentProps>;
17
- /** Render component on its own without any additional, top-level container */
18
- noPageContainer?: boolean;
19
- auth?: {
20
- /** If true, the user does not need to be logged in or have any roles to access this page. If blank, defaults to false. */
21
- public?: boolean;
22
- /** If true, the user does not need to have any roles to access this page, but must be logged in. If blank, defaults to false. */
23
- allUsers?: boolean,
24
- /** The user must be logged in and have these roles to access this page. If blank, defaults to requiring the 'admin' role. */
25
- roles?: string[];
26
- };
27
- pageContainerSxProps?: (theme: Theme) => SxProps;
28
- }
14
+ name: string;
15
+ path: string | string[];
16
+ component: React.ComponentType<PageComponentProps>;
17
+ /** Render component on its own without any additional, top-level container */
18
+ noPageContainer?: boolean;
19
+ auth?: {
20
+ /** If true, the user does not need to be logged in or have any roles to access this page. If blank, defaults to false. */
21
+ public?: boolean;
22
+ /** If true, the user does not need to have any roles to access this page, but must be logged in. If blank, defaults to false. */
23
+ allUsers?: boolean;
24
+ /** The user must be logged in and have these roles to access this page. If blank, defaults to requiring the 'admin' role. */
25
+ roles?: string[];
26
+ };
27
+ pageContainerSxProps?: (theme: Theme) => SxProps;
28
+ }
@@ -7,70 +7,84 @@ import { Page, getPages } from './Page';
7
7
  import { createUrlParams } from './createUrlParams';
8
8
 
9
9
  export type AppOptions = {
10
- pageContainer?: React.ComponentType<{ page: Page }>,
11
- pageNotFound?: React.ComponentType
12
- }
10
+ pageContainer?: React.ComponentType<{ page: Page }>;
11
+ pageNotFound?: React.ComponentType;
12
+ };
13
13
 
14
14
  export function loadApp(options: AppOptions = {}) {
15
- const container = document.getElementById('app');
16
- const root = createRoot(container!);
17
- root.render(<Router pages={getPages()} options={options} />);
15
+ const container = document.getElementById('app');
16
+ const root = createRoot(container!);
17
+ root.render(<Router pages={getPages()} options={options} />);
18
18
  }
19
19
 
20
- export function Router(props: { pages: Page[], options: AppOptions }) {
21
- const { pages, options } = props;
20
+ export function Router(props: { pages: Page[]; options: AppOptions }) {
21
+ const { pages, options } = props;
22
+ return (
23
+ <div>
24
+ <CssBaseline />
25
+ <BrowserRouter>
26
+ <RoutesComponent />
27
+ </BrowserRouter>
28
+ </div>
29
+ );
30
+
31
+ function RoutesComponent() {
32
+ const navigate = useNavigate();
22
33
  return (
23
- <div>
24
- <CssBaseline />
25
- <BrowserRouter>
26
- <RoutesComponent />
27
- </BrowserRouter>
28
- </div>
34
+ <Routes>
35
+ {(() => {
36
+ const routes = [];
37
+ let key = 0;
38
+ for (const page of pages) {
39
+ if (typeof page.path === 'string') {
40
+ routes.push(
41
+ <Route
42
+ key={key++}
43
+ path={getPath(page.path)}
44
+ element={<ContainerizedComponent options={options} page={page} navigate={navigate} />}
45
+ />
46
+ );
47
+ } else {
48
+ const paths = page.path as string[];
49
+ for (const path of paths) {
50
+ routes.push(
51
+ <Route
52
+ key={key++}
53
+ path={getPath(path)}
54
+ element={<ContainerizedComponent options={options} page={page} navigate={navigate} />}
55
+ />
56
+ );
57
+ }
58
+ }
59
+ }
60
+ return routes;
61
+ })()}
62
+ <Route element={<PageNotFound pageNotFound={options.pageNotFound} />} />
63
+ </Routes>
29
64
  );
65
+ }
30
66
 
31
- function RoutesComponent() {
32
- const navigate = useNavigate();
33
- return (
34
- <Routes>
35
- {
36
- (() => {
37
- const routes = [];
38
- let key = 0;
39
- for (const page of pages) {
40
- if (typeof page.path === 'string')
41
- routes.push(<Route key={key++} path={getPath(page.path)} element={<ContainerizedComponent options={options} page={page} navigate={navigate} />} />);
42
- else {
43
- const paths = page.path as string[];
44
- for (const path of paths)
45
- routes.push(<Route key={key++} path={getPath(path)} element={<ContainerizedComponent options={options} page={page} navigate={navigate} />} />);
46
- }
47
- }
48
- return routes;
49
- })()
50
- }
51
- <Route element={<PageNotFound pageNotFound={options.pageNotFound} />} />
52
- </Routes>
53
- )
54
- }
55
-
56
- function ContainerizedComponent(props: { options: AppOptions, page: Page, navigate: NavigateFunction}) {
57
- if (props.options.pageContainer && !props.page.noPageContainer)
58
- return ( <props.options.pageContainer page={props.page} /> );
59
-
60
- return ( <props.page.component urlParams={createUrlParams()} navigate={props.navigate} /> );
67
+ function ContainerizedComponent(props: { options: AppOptions; page: Page; navigate: NavigateFunction }) {
68
+ if (props.options.pageContainer && !props.page.noPageContainer) {
69
+ return <props.options.pageContainer page={props.page} />;
61
70
  }
62
71
 
63
- function PageNotFound(props: { pageNotFound: AppOptions['pageNotFound'] }) {
64
- if (props.pageNotFound)
65
- return ( <props.pageNotFound /> );
72
+ return <props.page.component urlParams={createUrlParams()} navigate={props.navigate} />;
73
+ }
66
74
 
67
- return <h1>404: Page not found</h1>;
75
+ function PageNotFound(props: { pageNotFound: AppOptions['pageNotFound'] }) {
76
+ if (props.pageNotFound) {
77
+ return <props.pageNotFound />;
68
78
  }
79
+
80
+ return <h1>404: Page not found</h1>;
81
+ }
69
82
  }
70
83
 
71
84
  function getPath(path: string) {
72
- if (path.startsWith('/'))
73
- return path;
85
+ if (path.startsWith('/')) {
86
+ return path;
87
+ }
74
88
 
75
- return `/${path}`;
76
- }
89
+ return `/${path}`;
90
+ }
@@ -1,16 +1,17 @@
1
1
  import { useLocation } from 'react-router';
2
2
 
3
3
  export function createUrlParams() {
4
- const urlParams: {[paramName: string]: string} = {};
4
+ const urlParams: { [paramName: string]: string } = {};
5
5
  const location = useLocation();
6
6
  const serializedParams = location.search.substring(1).split('&');
7
- for (let serializedParam of serializedParams) {
8
- if (serializedParam === '')
9
- continue;
7
+ for (const serializedParam of serializedParams) {
8
+ if (serializedParam === '') {
9
+ continue;
10
+ }
10
11
 
11
- const kvp = serializedParam.split('=');
12
- urlParams[decodeURIComponent(kvp[0])] = decodeURIComponent(kvp[1]);
12
+ const kvp = serializedParam.split('=');
13
+ urlParams[decodeURIComponent(kvp[0])] = decodeURIComponent(kvp[1]);
13
14
  }
14
15
 
15
16
  return urlParams;
16
- }
17
+ }