@proteinjs/db-ui 1.0.16 → 1.0.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.
- package/.eslintrc.js +20 -0
- package/.prettierignore +3 -0
- package/.prettierrc +8 -0
- package/CHANGELOG.md +11 -34
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/src/form/RecordForm.d.ts.map +1 -1
- package/dist/src/form/RecordForm.js +29 -13
- package/dist/src/form/RecordForm.js.map +1 -1
- package/dist/src/form/RecordFormCustomization.d.ts.map +1 -1
- package/dist/src/form/RecordFormCustomization.js +5 -2
- package/dist/src/form/RecordFormCustomization.js.map +1 -1
- package/dist/src/form/customizations/MigrationRecordFormCustomization.d.ts.map +1 -1
- package/dist/src/form/customizations/MigrationRecordFormCustomization.js +3 -1
- package/dist/src/form/customizations/MigrationRecordFormCustomization.js.map +1 -1
- package/dist/src/pages/RecordFormPage.d.ts.map +1 -1
- package/dist/src/pages/RecordFormPage.js +8 -5
- package/dist/src/pages/RecordFormPage.js.map +1 -1
- package/dist/src/pages/RecordTablePage.d.ts.map +1 -1
- package/dist/src/pages/RecordTablePage.js +7 -7
- package/dist/src/pages/RecordTablePage.js.map +1 -1
- package/dist/src/pages/TablesPage.d.ts.map +1 -1
- package/dist/src/pages/TablesPage.js +1 -1
- package/dist/src/pages/TablesPage.js.map +1 -1
- package/dist/src/pages/UuidGeneratorPage.d.ts.map +1 -1
- package/dist/src/pages/UuidGeneratorPage.js +2 -2
- package/dist/src/pages/UuidGeneratorPage.js.map +1 -1
- package/dist/src/table/QueryTableLoader.d.ts.map +1 -1
- package/dist/src/table/QueryTableLoader.js +2 -1
- package/dist/src/table/QueryTableLoader.js.map +1 -1
- package/dist/src/table/RecordTable.d.ts.map +1 -1
- package/dist/src/table/RecordTable.js +12 -6
- package/dist/src/table/RecordTable.js.map +1 -1
- package/generated/index.ts +10 -13
- package/index.ts +4 -4
- package/jest.config.js +8 -17
- package/package.json +54 -49
- package/src/form/RecordForm.tsx +57 -40
- package/src/form/RecordFormCustomization.tsx +11 -9
- package/src/form/customizations/MigrationRecordFormCustomization.tsx +8 -6
- package/src/pages/RecordFormPage.tsx +18 -22
- package/src/pages/RecordTablePage.tsx +16 -22
- package/src/pages/TablesPage.tsx +11 -11
- package/src/pages/UuidGeneratorPage.tsx +17 -17
- package/src/table/QueryTableLoader.ts +6 -6
- package/src/table/RecordTable.tsx +32 -27
- package/tsconfig.json +17 -17
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { FormPage, Page, PageComponentProps } from '@proteinjs/ui'
|
|
3
|
-
import { getDbService, tableByName } from '@proteinjs/db'
|
|
4
|
-
import { RecordForm } from '../form/RecordForm'
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { FormPage, Page, PageComponentProps } from '@proteinjs/ui';
|
|
3
|
+
import { getDbService, tableByName } from '@proteinjs/db';
|
|
4
|
+
import { RecordForm } from '../form/RecordForm';
|
|
5
5
|
|
|
6
6
|
export const recordFormPage: Page = {
|
|
7
7
|
name: 'Record Form',
|
|
@@ -9,20 +9,20 @@ export const recordFormPage: Page = {
|
|
|
9
9
|
auth: {
|
|
10
10
|
allUsers: true,
|
|
11
11
|
},
|
|
12
|
-
component: ({...props}) => (
|
|
12
|
+
component: ({ ...props }) => (
|
|
13
13
|
<FormPage>
|
|
14
14
|
<DynamicRecordForm {...props} />
|
|
15
15
|
</FormPage>
|
|
16
|
-
)
|
|
17
|
-
}
|
|
16
|
+
),
|
|
17
|
+
};
|
|
18
18
|
|
|
19
19
|
export const recordFormLink = (tableName: string, recordId: string) => {
|
|
20
20
|
return `/${recordFormPage.path}?table=${tableName}&record=${recordId}`;
|
|
21
|
-
}
|
|
21
|
+
};
|
|
22
22
|
|
|
23
23
|
export const newRecordFormLink = (tableName: string) => {
|
|
24
24
|
return `/${recordFormPage.path}?table=${tableName}`;
|
|
25
|
-
}
|
|
25
|
+
};
|
|
26
26
|
|
|
27
27
|
const DynamicRecordForm = ({ urlParams }: PageComponentProps) => {
|
|
28
28
|
const [record, setRecord] = React.useState();
|
|
@@ -30,12 +30,14 @@ const DynamicRecordForm = ({ urlParams }: PageComponentProps) => {
|
|
|
30
30
|
React.useEffect(() => {
|
|
31
31
|
const fetchData = async () => {
|
|
32
32
|
const { table } = getTable();
|
|
33
|
-
if (!table)
|
|
33
|
+
if (!table) {
|
|
34
34
|
return;
|
|
35
|
+
}
|
|
35
36
|
|
|
36
37
|
const recordId = urlParams['record'];
|
|
37
|
-
if (!recordId)
|
|
38
|
+
if (!recordId) {
|
|
38
39
|
return;
|
|
40
|
+
}
|
|
39
41
|
|
|
40
42
|
const fetchedRecord = await getDbService().get(table, { id: recordId });
|
|
41
43
|
setRecord(fetchedRecord);
|
|
@@ -52,6 +54,7 @@ const DynamicRecordForm = ({ urlParams }: PageComponentProps) => {
|
|
|
52
54
|
try {
|
|
53
55
|
table = tableByName(tableName);
|
|
54
56
|
} catch (error) {
|
|
57
|
+
// eslint-disable-next-line no-ex-assign
|
|
55
58
|
error = `Table not accessible in UI: ${tableName}`;
|
|
56
59
|
}
|
|
57
60
|
} else {
|
|
@@ -64,18 +67,11 @@ const DynamicRecordForm = ({ urlParams }: PageComponentProps) => {
|
|
|
64
67
|
function Form() {
|
|
65
68
|
const { table, error } = getTable();
|
|
66
69
|
if (!table) {
|
|
67
|
-
return <div>{
|
|
70
|
+
return <div>{error}</div>;
|
|
68
71
|
}
|
|
69
72
|
|
|
70
|
-
return
|
|
71
|
-
<RecordForm
|
|
72
|
-
table={table}
|
|
73
|
-
record={record}
|
|
74
|
-
/>
|
|
75
|
-
);
|
|
73
|
+
return <RecordForm table={table} record={record} />;
|
|
76
74
|
}
|
|
77
75
|
|
|
78
|
-
return
|
|
79
|
-
|
|
80
|
-
);
|
|
81
|
-
}
|
|
76
|
+
return <Form />;
|
|
77
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { FormPage, Page, PageComponentProps } from '@proteinjs/ui'
|
|
3
|
-
import { tableByName, Table } from '@proteinjs/db'
|
|
4
|
-
import { RecordTable } from '../table/RecordTable'
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { FormPage, Page, PageComponentProps } from '@proteinjs/ui';
|
|
3
|
+
import { tableByName, Table } from '@proteinjs/db';
|
|
4
|
+
import { RecordTable } from '../table/RecordTable';
|
|
5
5
|
|
|
6
6
|
export const recordTablePage: Page = {
|
|
7
7
|
name: 'Record Table',
|
|
@@ -9,48 +9,42 @@ export const recordTablePage: Page = {
|
|
|
9
9
|
auth: {
|
|
10
10
|
allUsers: true,
|
|
11
11
|
},
|
|
12
|
-
component: ({...props}) => (
|
|
12
|
+
component: ({ ...props }) => (
|
|
13
13
|
<FormPage>
|
|
14
14
|
<DynamicRecordTable {...props} />
|
|
15
15
|
</FormPage>
|
|
16
|
-
)
|
|
17
|
-
}
|
|
16
|
+
),
|
|
17
|
+
};
|
|
18
18
|
|
|
19
19
|
export const recordTableLink = (table: Table<any>) => {
|
|
20
20
|
return `/${recordTablePage.path}?name=${table.name}`;
|
|
21
|
-
}
|
|
21
|
+
};
|
|
22
22
|
|
|
23
23
|
export const recordTableLinkByName = (tableName: string) => {
|
|
24
24
|
return `/${recordTablePage.path}?name=${tableName}`;
|
|
25
|
-
}
|
|
25
|
+
};
|
|
26
26
|
|
|
27
27
|
const DynamicRecordTable = ({ urlParams }: PageComponentProps) => {
|
|
28
28
|
function Table() {
|
|
29
29
|
const tableName = urlParams['name'];
|
|
30
30
|
let table;
|
|
31
|
-
let
|
|
31
|
+
let errorMessage;
|
|
32
32
|
if (tableName) {
|
|
33
33
|
try {
|
|
34
34
|
table = tableByName(tableName);
|
|
35
35
|
} catch (error) {
|
|
36
|
-
|
|
36
|
+
errorMessage = `Table not accessible in UI: ${tableName}`;
|
|
37
37
|
}
|
|
38
38
|
} else {
|
|
39
|
-
|
|
39
|
+
errorMessage = `Table not provided via the 'name' url param`;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
if (!table) {
|
|
43
|
-
return <div>{
|
|
43
|
+
return <div>{errorMessage}</div>;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
return
|
|
47
|
-
<RecordTable
|
|
48
|
-
table={table}
|
|
49
|
-
/>
|
|
50
|
-
);
|
|
46
|
+
return <RecordTable table={table} />;
|
|
51
47
|
}
|
|
52
48
|
|
|
53
|
-
return
|
|
54
|
-
|
|
55
|
-
);
|
|
56
|
-
}
|
|
49
|
+
return <Table />;
|
|
50
|
+
};
|
package/src/pages/TablesPage.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { FormPage, Page, TableLoader, Table as TableComponent, RowWindow } from '@proteinjs/ui'
|
|
3
|
-
import { getTables, getDbService, Table } from '@proteinjs/db'
|
|
4
|
-
import { recordTableLinkByName } from './RecordTablePage'
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { FormPage, Page, TableLoader, Table as TableComponent, RowWindow } from '@proteinjs/ui';
|
|
3
|
+
import { getTables, getDbService, Table } from '@proteinjs/db';
|
|
4
|
+
import { recordTableLinkByName } from './RecordTablePage';
|
|
5
5
|
|
|
6
6
|
export const tablesPage: Page = {
|
|
7
7
|
name: 'Tables',
|
|
@@ -10,13 +10,13 @@ export const tablesPage: Page = {
|
|
|
10
10
|
<FormPage>
|
|
11
11
|
<Tables />
|
|
12
12
|
</FormPage>
|
|
13
|
-
)
|
|
14
|
-
}
|
|
13
|
+
),
|
|
14
|
+
};
|
|
15
15
|
|
|
16
16
|
type TableSummary = {
|
|
17
|
-
name: string
|
|
18
|
-
rowCount: number
|
|
19
|
-
}
|
|
17
|
+
name: string;
|
|
18
|
+
rowCount: number;
|
|
19
|
+
};
|
|
20
20
|
|
|
21
21
|
class TableSummaryLoader implements TableLoader<TableSummary> {
|
|
22
22
|
constructor(private tables: Table<any>[]) {}
|
|
@@ -25,7 +25,7 @@ class TableSummaryLoader implements TableLoader<TableSummary> {
|
|
|
25
25
|
const page: RowWindow<TableSummary> = { rows: [], totalCount: this.tables.length };
|
|
26
26
|
const tables = this.tables.slice(startIndex, endIndex);
|
|
27
27
|
const dbService = getDbService();
|
|
28
|
-
for (
|
|
28
|
+
for (const table of tables) {
|
|
29
29
|
const rowCount = await dbService.getRowCount(table);
|
|
30
30
|
page.rows.push({ name: table.name, rowCount });
|
|
31
31
|
}
|
|
@@ -45,4 +45,4 @@ const Tables = () => {
|
|
|
45
45
|
}}
|
|
46
46
|
/>
|
|
47
47
|
);
|
|
48
|
-
}
|
|
48
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { FormPage, Page, Form, Fields, textField, FormButtons } from '@proteinjs/ui'
|
|
3
|
-
import { v1 as uuidv1 } from 'uuid'
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { FormPage, Page, Form, Fields, textField, FormButtons } from '@proteinjs/ui';
|
|
3
|
+
import { v1 as uuidv1 } from 'uuid';
|
|
4
4
|
|
|
5
5
|
export const uuidGeneratorPage: Page = {
|
|
6
6
|
name: 'Uuid Generator',
|
|
@@ -14,24 +14,24 @@ export const uuidGeneratorPage: Page = {
|
|
|
14
14
|
buttons={buttons}
|
|
15
15
|
/>
|
|
16
16
|
</FormPage>
|
|
17
|
-
)
|
|
18
|
-
}
|
|
17
|
+
),
|
|
18
|
+
};
|
|
19
19
|
|
|
20
20
|
class UuidFields extends Fields {
|
|
21
21
|
uuid = textField<UuidFields>({
|
|
22
|
-
name: 'uuid'
|
|
22
|
+
name: 'uuid',
|
|
23
23
|
});
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
const buttons: FormButtons<UuidFields> = {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
};
|
|
27
|
+
generate: {
|
|
28
|
+
name: 'Generate',
|
|
29
|
+
style: {
|
|
30
|
+
color: 'primary',
|
|
31
|
+
variant: 'contained',
|
|
32
|
+
},
|
|
33
|
+
onClick: async (fields: UuidFields, buttons: FormButtons<UuidFields>) => {
|
|
34
|
+
fields.uuid.field.value = uuidv1();
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Query, QueryBuilderFactory, Record, SortCriteria, Table, getDb } from '@proteinjs/db'
|
|
2
|
-
import { RowWindow, TableLoader } from '@proteinjs/ui'
|
|
1
|
+
import { Query, QueryBuilderFactory, Record, SortCriteria, Table, getDb } from '@proteinjs/db';
|
|
2
|
+
import { RowWindow, TableLoader } from '@proteinjs/ui';
|
|
3
3
|
|
|
4
4
|
export class QueryTableLoader<T extends Record> implements TableLoader<T> {
|
|
5
5
|
constructor(
|
|
@@ -11,14 +11,14 @@ export class QueryTableLoader<T extends Record> implements TableLoader<T> {
|
|
|
11
11
|
async load(startIndex: number, endIndex: number): Promise<RowWindow<T>> {
|
|
12
12
|
const db = getDb();
|
|
13
13
|
const sort: any = this.sort ? this.sort : [{ field: 'created', desc: true }];
|
|
14
|
-
const qb = new QueryBuilderFactory()
|
|
14
|
+
const qb = new QueryBuilderFactory()
|
|
15
|
+
.getQueryBuilder(this.table, this.query)
|
|
15
16
|
.sort(sort)
|
|
16
|
-
.paginate({ start: startIndex, end: endIndex })
|
|
17
|
-
;
|
|
17
|
+
.paginate({ start: startIndex, end: endIndex });
|
|
18
18
|
const queryPromise = db.query(this.table, qb);
|
|
19
19
|
const rowcountQb = new QueryBuilderFactory().getQueryBuilder(this.table, this.query);
|
|
20
20
|
const rowCountPromise = db.getRowCount(this.table, rowcountQb);
|
|
21
21
|
const [rows, totalCount] = await Promise.all([queryPromise, rowCountPromise]);
|
|
22
22
|
return { rows, totalCount };
|
|
23
23
|
}
|
|
24
|
-
}
|
|
24
|
+
}
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { Delete, Add } from '@mui/icons-material'
|
|
3
|
-
import S from 'string'
|
|
4
|
-
import { TableButton, Table as TableComponent, TableLoader, TableProps } from '@proteinjs/ui'
|
|
5
|
-
import { Column, QueryBuilderFactory, Record, Table, getDb } from '@proteinjs/db'
|
|
6
|
-
import { QueryTableLoader } from './QueryTableLoader'
|
|
7
|
-
import { newRecordFormLink, recordFormLink } from '../pages/RecordFormPage'
|
|
8
|
-
import { recordTableLink } from '../pages/RecordTablePage'
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Delete, Add } from '@mui/icons-material';
|
|
3
|
+
import S from 'string';
|
|
4
|
+
import { TableButton, Table as TableComponent, TableLoader, TableProps } from '@proteinjs/ui';
|
|
5
|
+
import { Column, QueryBuilderFactory, Record, Table, getDb } from '@proteinjs/db';
|
|
6
|
+
import { QueryTableLoader } from './QueryTableLoader';
|
|
7
|
+
import { newRecordFormLink, recordFormLink } from '../pages/RecordFormPage';
|
|
8
|
+
import { recordTableLink } from '../pages/RecordTablePage';
|
|
9
9
|
|
|
10
10
|
export type RecordTableProps<T extends Record> = {
|
|
11
|
-
table: Table<T
|
|
12
|
-
tableLoader?: TableLoader<T
|
|
13
|
-
title?: TableProps<T>['title']
|
|
14
|
-
description?: TableProps<T>['description']
|
|
15
|
-
columns?: TableProps<T>['columns']
|
|
16
|
-
defaultRowsPerPage?: TableProps<T>['defaultRowsPerPage']
|
|
17
|
-
buttons?: TableProps<T>['buttons']
|
|
18
|
-
rowOnClickRedirectUrl?: TableProps<T>['rowOnClickRedirectUrl']
|
|
19
|
-
}
|
|
11
|
+
table: Table<T>;
|
|
12
|
+
tableLoader?: TableLoader<T>;
|
|
13
|
+
title?: TableProps<T>['title'];
|
|
14
|
+
description?: TableProps<T>['description'];
|
|
15
|
+
columns?: TableProps<T>['columns'];
|
|
16
|
+
defaultRowsPerPage?: TableProps<T>['defaultRowsPerPage'];
|
|
17
|
+
buttons?: TableProps<T>['buttons'];
|
|
18
|
+
rowOnClickRedirectUrl?: TableProps<T>['rowOnClickRedirectUrl'];
|
|
19
|
+
};
|
|
20
20
|
|
|
21
21
|
function deleteButton<T extends Record>(table: Table<T>): TableButton<T> {
|
|
22
22
|
return {
|
|
@@ -27,9 +27,9 @@ function deleteButton<T extends Record>(table: Table<T>): TableButton<T> {
|
|
|
27
27
|
showWhenNoRowsSelected: false,
|
|
28
28
|
},
|
|
29
29
|
onClick: async (selectedRows, navigate) => {
|
|
30
|
-
const qb = new QueryBuilderFactory()
|
|
31
|
-
.
|
|
32
|
-
|
|
30
|
+
const qb = new QueryBuilderFactory()
|
|
31
|
+
.getQueryBuilder(table)
|
|
32
|
+
.condition({ field: 'id', operator: 'IN', value: selectedRows.map((row) => row.id) as T[keyof T][] });
|
|
33
33
|
await getDb().delete(table, qb);
|
|
34
34
|
navigate(recordTableLink(table));
|
|
35
35
|
},
|
|
@@ -53,19 +53,23 @@ function createButton<T extends Record>(table: Table<T>): TableButton<T> {
|
|
|
53
53
|
export function RecordTable<T extends Record>(props: RecordTableProps<T>) {
|
|
54
54
|
function defaultColumns() {
|
|
55
55
|
const columnProperties: (keyof T)[] = [];
|
|
56
|
-
if ((props.table.columns as any)['name'])
|
|
56
|
+
if ((props.table.columns as any)['name']) {
|
|
57
57
|
columnProperties.push('name' as keyof T);
|
|
58
|
+
}
|
|
58
59
|
|
|
59
|
-
for (
|
|
60
|
-
if (columnProperties.length >= 5)
|
|
60
|
+
for (const columnProperty of Object.keys(props.table.columns)) {
|
|
61
|
+
if (columnProperties.length >= 5) {
|
|
61
62
|
break;
|
|
63
|
+
}
|
|
62
64
|
|
|
63
|
-
if (columnProperty == 'name' || columnProperty == 'created' || columnProperty == 'updated')
|
|
65
|
+
if (columnProperty == 'name' || columnProperty == 'created' || columnProperty == 'updated') {
|
|
64
66
|
continue;
|
|
67
|
+
}
|
|
65
68
|
|
|
66
69
|
const column: Column<T, any> = (props.table.columns as any)[columnProperty];
|
|
67
|
-
if (column.options?.ui?.hidden)
|
|
70
|
+
if (column.options?.ui?.hidden) {
|
|
68
71
|
continue;
|
|
72
|
+
}
|
|
69
73
|
|
|
70
74
|
columnProperties.push(columnProperty as keyof T);
|
|
71
75
|
}
|
|
@@ -86,15 +90,16 @@ export function RecordTable<T extends Record>(props: RecordTableProps<T>) {
|
|
|
86
90
|
|
|
87
91
|
function buttons() {
|
|
88
92
|
const buttons: TableButton<T>[] = [];
|
|
89
|
-
if (props.buttons)
|
|
93
|
+
if (props.buttons) {
|
|
90
94
|
buttons.push(...props.buttons);
|
|
95
|
+
}
|
|
91
96
|
|
|
92
97
|
buttons.push(deleteButton(props.table));
|
|
93
98
|
buttons.push(createButton(props.table));
|
|
94
99
|
|
|
95
100
|
return buttons;
|
|
96
101
|
}
|
|
97
|
-
|
|
102
|
+
|
|
98
103
|
return (
|
|
99
104
|
<TableComponent
|
|
100
105
|
title={props.title ? props.title : `${S(props.table.name).humanize().toString()} Table`}
|
package/tsconfig.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"rootDir": "./",
|
|
4
|
+
"target": "es5",
|
|
5
|
+
"module": "commonjs",
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"declarationMap": true,
|
|
8
|
+
"sourceMap": true,
|
|
9
|
+
"outDir": "./dist/",
|
|
10
|
+
"strict": true,
|
|
11
|
+
"noImplicitAny": true,
|
|
12
|
+
"esModuleInterop": true,
|
|
13
|
+
"skipLibCheck": true,
|
|
14
|
+
"forceConsistentCasingInFileNames": true,
|
|
15
|
+
"resolveJsonModule": true,
|
|
16
|
+
"jsx": "react"
|
|
17
|
+
}
|
|
18
|
+
}
|