@firecms/collection_editor 3.0.0-3.0.0-beta.4.pre.1.0

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 (140) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1 -0
  3. package/dist/ConfigControllerProvider.d.ts +37 -0
  4. package/dist/index.d.ts +11 -0
  5. package/dist/index.es.js +5454 -0
  6. package/dist/index.es.js.map +1 -0
  7. package/dist/index.umd.js +4 -0
  8. package/dist/index.umd.js.map +1 -0
  9. package/dist/types/collection_editor_controller.d.ts +36 -0
  10. package/dist/types/collection_inference.d.ts +2 -0
  11. package/dist/types/config_controller.d.ts +51 -0
  12. package/dist/types/config_permissions.d.ts +19 -0
  13. package/dist/types/persisted_collection.d.ts +6 -0
  14. package/dist/ui/CollectionViewHeaderAction.d.ts +10 -0
  15. package/dist/ui/EditorCollectionAction.d.ts +2 -0
  16. package/dist/ui/HomePageEditorCollectionAction.d.ts +2 -0
  17. package/dist/ui/MissingReferenceWidget.d.ts +3 -0
  18. package/dist/ui/NewCollectionButton.d.ts +1 -0
  19. package/dist/ui/NewCollectionCard.d.ts +2 -0
  20. package/dist/ui/PropertyAddColumnComponent.d.ts +6 -0
  21. package/dist/ui/RootCollectionSuggestions.d.ts +3 -0
  22. package/dist/ui/collection_editor/CollectionDetailsForm.d.ts +10 -0
  23. package/dist/ui/collection_editor/CollectionEditorDialog.d.ts +36 -0
  24. package/dist/ui/collection_editor/CollectionEditorWelcomeView.d.ts +15 -0
  25. package/dist/ui/collection_editor/CollectionPropertiesEditorForm.d.ts +19 -0
  26. package/dist/ui/collection_editor/CollectionYupValidation.d.ts +14 -0
  27. package/dist/ui/collection_editor/EntityCustomViewsSelectDialog.d.ts +4 -0
  28. package/dist/ui/collection_editor/EnumForm.d.ts +12 -0
  29. package/dist/ui/collection_editor/GetCodeDialog.d.ts +5 -0
  30. package/dist/ui/collection_editor/PropertyEditView.d.ts +40 -0
  31. package/dist/ui/collection_editor/PropertyFieldPreview.d.ts +15 -0
  32. package/dist/ui/collection_editor/PropertySelectItem.d.ts +8 -0
  33. package/dist/ui/collection_editor/PropertyTree.d.ts +33 -0
  34. package/dist/ui/collection_editor/SubcollectionsEditTab.d.ts +12 -0
  35. package/dist/ui/collection_editor/SwitchControl.d.ts +8 -0
  36. package/dist/ui/collection_editor/UnsavedChangesDialog.d.ts +9 -0
  37. package/dist/ui/collection_editor/import/CollectionEditorImportDataPreview.d.ts +7 -0
  38. package/dist/ui/collection_editor/import/CollectionEditorImportMapping.d.ts +7 -0
  39. package/dist/ui/collection_editor/import/clean_import_data.d.ts +7 -0
  40. package/dist/ui/collection_editor/properties/BlockPropertyField.d.ts +8 -0
  41. package/dist/ui/collection_editor/properties/BooleanPropertyField.d.ts +3 -0
  42. package/dist/ui/collection_editor/properties/CommonPropertyFields.d.ts +10 -0
  43. package/dist/ui/collection_editor/properties/DateTimePropertyField.d.ts +3 -0
  44. package/dist/ui/collection_editor/properties/EnumPropertyField.d.ts +8 -0
  45. package/dist/ui/collection_editor/properties/KeyValuePropertyField.d.ts +3 -0
  46. package/dist/ui/collection_editor/properties/MapPropertyField.d.ts +8 -0
  47. package/dist/ui/collection_editor/properties/NumberPropertyField.d.ts +3 -0
  48. package/dist/ui/collection_editor/properties/ReferencePropertyField.d.ts +13 -0
  49. package/dist/ui/collection_editor/properties/RepeatPropertyField.d.ts +10 -0
  50. package/dist/ui/collection_editor/properties/StoragePropertyField.d.ts +5 -0
  51. package/dist/ui/collection_editor/properties/StringPropertyField.d.ts +5 -0
  52. package/dist/ui/collection_editor/properties/UrlPropertyField.d.ts +4 -0
  53. package/dist/ui/collection_editor/properties/advanced/AdvancedPropertyValidation.d.ts +3 -0
  54. package/dist/ui/collection_editor/properties/validation/ArrayPropertyValidation.d.ts +5 -0
  55. package/dist/ui/collection_editor/properties/validation/GeneralPropertyValidation.d.ts +4 -0
  56. package/dist/ui/collection_editor/properties/validation/NumberPropertyValidation.d.ts +3 -0
  57. package/dist/ui/collection_editor/properties/validation/StringPropertyValidation.d.ts +11 -0
  58. package/dist/ui/collection_editor/properties/validation/ValidationPanel.d.ts +2 -0
  59. package/dist/ui/collection_editor/templates/blog_template.d.ts +2 -0
  60. package/dist/ui/collection_editor/templates/pages_template.d.ts +2 -0
  61. package/dist/ui/collection_editor/templates/products_template.d.ts +2 -0
  62. package/dist/ui/collection_editor/templates/users_template.d.ts +2 -0
  63. package/dist/ui/collection_editor/util.d.ts +5 -0
  64. package/dist/ui/collection_editor/utils/strings.d.ts +1 -0
  65. package/dist/ui/collection_editor/utils/supported_fields.d.ts +3 -0
  66. package/dist/ui/collection_editor/utils/update_property_for_widget.d.ts +2 -0
  67. package/dist/ui/collection_editor/utils/useTraceUpdate.d.ts +1 -0
  68. package/dist/useCollectionEditorController.d.ts +6 -0
  69. package/dist/useCollectionEditorPlugin.d.ts +49 -0
  70. package/dist/useCollectionsConfigController.d.ts +6 -0
  71. package/dist/utils/arrays.d.ts +1 -0
  72. package/dist/utils/entities.d.ts +3 -0
  73. package/package.json +85 -0
  74. package/src/ConfigControllerProvider.tsx +338 -0
  75. package/src/index.ts +35 -0
  76. package/src/types/collection_editor_controller.tsx +43 -0
  77. package/src/types/collection_inference.ts +3 -0
  78. package/src/types/config_controller.tsx +60 -0
  79. package/src/types/config_permissions.ts +20 -0
  80. package/src/types/persisted_collection.ts +9 -0
  81. package/src/ui/CollectionViewHeaderAction.tsx +43 -0
  82. package/src/ui/EditorCollectionAction.tsx +109 -0
  83. package/src/ui/HomePageEditorCollectionAction.tsx +84 -0
  84. package/src/ui/MissingReferenceWidget.tsx +37 -0
  85. package/src/ui/NewCollectionButton.tsx +16 -0
  86. package/src/ui/NewCollectionCard.tsx +48 -0
  87. package/src/ui/PropertyAddColumnComponent.tsx +42 -0
  88. package/src/ui/RootCollectionSuggestions.tsx +63 -0
  89. package/src/ui/collection_editor/CollectionDetailsForm.tsx +365 -0
  90. package/src/ui/collection_editor/CollectionEditorDialog.tsx +801 -0
  91. package/src/ui/collection_editor/CollectionEditorWelcomeView.tsx +213 -0
  92. package/src/ui/collection_editor/CollectionPropertiesEditorForm.tsx +506 -0
  93. package/src/ui/collection_editor/CollectionYupValidation.tsx +7 -0
  94. package/src/ui/collection_editor/EntityCustomViewsSelectDialog.tsx +37 -0
  95. package/src/ui/collection_editor/EnumForm.tsx +357 -0
  96. package/src/ui/collection_editor/GetCodeDialog.tsx +110 -0
  97. package/src/ui/collection_editor/PropertyEditView.tsx +615 -0
  98. package/src/ui/collection_editor/PropertyFieldPreview.tsx +207 -0
  99. package/src/ui/collection_editor/PropertySelectItem.tsx +32 -0
  100. package/src/ui/collection_editor/PropertyTree.tsx +252 -0
  101. package/src/ui/collection_editor/SubcollectionsEditTab.tsx +262 -0
  102. package/src/ui/collection_editor/SwitchControl.tsx +39 -0
  103. package/src/ui/collection_editor/UnsavedChangesDialog.tsx +47 -0
  104. package/src/ui/collection_editor/import/CollectionEditorImportDataPreview.tsx +37 -0
  105. package/src/ui/collection_editor/import/CollectionEditorImportMapping.tsx +268 -0
  106. package/src/ui/collection_editor/import/clean_import_data.ts +53 -0
  107. package/src/ui/collection_editor/properties/BlockPropertyField.tsx +138 -0
  108. package/src/ui/collection_editor/properties/BooleanPropertyField.tsx +40 -0
  109. package/src/ui/collection_editor/properties/CommonPropertyFields.tsx +110 -0
  110. package/src/ui/collection_editor/properties/DateTimePropertyField.tsx +86 -0
  111. package/src/ui/collection_editor/properties/EnumPropertyField.tsx +114 -0
  112. package/src/ui/collection_editor/properties/KeyValuePropertyField.tsx +20 -0
  113. package/src/ui/collection_editor/properties/MapPropertyField.tsx +150 -0
  114. package/src/ui/collection_editor/properties/NumberPropertyField.tsx +38 -0
  115. package/src/ui/collection_editor/properties/ReferencePropertyField.tsx +160 -0
  116. package/src/ui/collection_editor/properties/RepeatPropertyField.tsx +109 -0
  117. package/src/ui/collection_editor/properties/StoragePropertyField.tsx +200 -0
  118. package/src/ui/collection_editor/properties/StringPropertyField.tsx +79 -0
  119. package/src/ui/collection_editor/properties/UrlPropertyField.tsx +89 -0
  120. package/src/ui/collection_editor/properties/advanced/AdvancedPropertyValidation.tsx +45 -0
  121. package/src/ui/collection_editor/properties/validation/ArrayPropertyValidation.tsx +50 -0
  122. package/src/ui/collection_editor/properties/validation/GeneralPropertyValidation.tsx +61 -0
  123. package/src/ui/collection_editor/properties/validation/NumberPropertyValidation.tsx +115 -0
  124. package/src/ui/collection_editor/properties/validation/StringPropertyValidation.tsx +150 -0
  125. package/src/ui/collection_editor/properties/validation/ValidationPanel.tsx +28 -0
  126. package/src/ui/collection_editor/templates/blog_template.ts +115 -0
  127. package/src/ui/collection_editor/templates/pages_template.ts +188 -0
  128. package/src/ui/collection_editor/templates/products_template.ts +88 -0
  129. package/src/ui/collection_editor/templates/users_template.ts +42 -0
  130. package/src/ui/collection_editor/util.ts +28 -0
  131. package/src/ui/collection_editor/utils/strings.ts +9 -0
  132. package/src/ui/collection_editor/utils/supported_fields.tsx +29 -0
  133. package/src/ui/collection_editor/utils/update_property_for_widget.ts +271 -0
  134. package/src/ui/collection_editor/utils/useTraceUpdate.tsx +23 -0
  135. package/src/useCollectionEditorController.tsx +9 -0
  136. package/src/useCollectionEditorPlugin.tsx +154 -0
  137. package/src/useCollectionsConfigController.tsx +9 -0
  138. package/src/utils/arrays.ts +3 -0
  139. package/src/utils/entities.ts +38 -0
  140. package/src/vite-env.d.ts +1 -0
@@ -0,0 +1,262 @@
1
+ import React from "react";
2
+ import {
3
+ DeleteConfirmationDialog,
4
+ EntityCollection,
5
+ EntityCustomView,
6
+ resolveEntityView,
7
+ useCustomizationController,
8
+ User
9
+ } from "@firecms/core";
10
+ import {
11
+ AddIcon,
12
+ Alert,
13
+ Button,
14
+ Container,
15
+ DeleteIcon,
16
+ IconButton,
17
+ Paper,
18
+ Table,
19
+ TableBody,
20
+ TableCell,
21
+ TableRow,
22
+ Tooltip,
23
+ Typography,
24
+ } from "@firecms/ui";
25
+ import { CollectionEditorDialog } from "./CollectionEditorDialog";
26
+ import { CollectionsConfigController } from "../../types/config_controller";
27
+ import { PersistedCollection } from "../../types/persisted_collection";
28
+ import { CollectionInference } from "../../types/collection_inference";
29
+ import { EntityCustomViewsSelectDialog } from "./EntityCustomViewsSelectDialog";
30
+ import { useFormex } from "@firecms/formex";
31
+
32
+ export function SubcollectionsEditTab({
33
+ collection,
34
+ parentCollection,
35
+ configController,
36
+ collectionInference,
37
+ getUser,
38
+ parentCollectionIds
39
+ }: {
40
+ collection: PersistedCollection,
41
+ parentCollection?: EntityCollection,
42
+ configController: CollectionsConfigController;
43
+ collectionInference?: CollectionInference;
44
+ getUser: (uid: string) => User | null;
45
+ parentCollectionIds?: string[];
46
+ }) {
47
+
48
+ const { entityViews: contextEntityViews } = useCustomizationController();
49
+
50
+ const [subcollectionToDelete, setSubcollectionToDelete] = React.useState<string | undefined>();
51
+ const [addEntityViewDialogOpen, setAddEntityViewDialogOpen] = React.useState<boolean>(false);
52
+ const [viewToDelete, setViewToDelete] = React.useState<string | undefined>();
53
+
54
+ const [currentDialog, setCurrentDialog] = React.useState<{
55
+ isNewCollection: boolean,
56
+ editedCollectionId?: string,
57
+ }>();
58
+
59
+ const {
60
+ values,
61
+ setFieldValue,
62
+ } = useFormex<EntityCollection>();
63
+
64
+ const subcollections = collection.subcollections ?? [];
65
+ const resolvedEntityViews = values.entityViews?.filter(e => typeof e === "string")
66
+ .map(e => resolveEntityView(e, contextEntityViews))
67
+ .filter(Boolean) as EntityCustomView[] ?? [];
68
+ const hardCodedEntityViews = collection.entityViews?.filter(e => typeof e !== "string") as EntityCustomView[] ?? [];
69
+ const totalEntityViews = resolvedEntityViews.length + hardCodedEntityViews.length;
70
+
71
+ return (
72
+ <div className={"overflow-auto my-auto"}>
73
+ <Container maxWidth={"2xl"} className={"flex flex-col gap-4 p-8 m-auto"}>
74
+ <div className={"flex flex-col gap-16"}>
75
+
76
+ <div className={"flex-grow flex flex-col gap-4 items-start"}>
77
+ <Typography variant={"h5"}>
78
+ Subcollections of {values.name}
79
+ </Typography>
80
+
81
+ <Paper className={"flex flex-col gap-4 p-2 w-full"}>
82
+ {subcollections && subcollections.length > 0 && <Table>
83
+ <TableBody>
84
+ {subcollections.map((subcollection) => (
85
+ <TableRow key={subcollection.path}
86
+ onClick={() => setCurrentDialog({
87
+ isNewCollection: false,
88
+ editedCollectionId: subcollection.id
89
+ })}>
90
+ <TableCell
91
+ align="left">
92
+ <Typography variant={"subtitle2"} className={"flex-grow"}>
93
+ {subcollection.name}
94
+ </Typography>
95
+ </TableCell>
96
+ <TableCell
97
+ align="right">
98
+ <Tooltip title={"Remove"}>
99
+ <IconButton size="small"
100
+ onClick={(e) => {
101
+ e.preventDefault();
102
+ e.stopPropagation();
103
+ setSubcollectionToDelete(subcollection.id);
104
+ }}
105
+ color="inherit">
106
+ <DeleteIcon size={"small"}/>
107
+ </IconButton>
108
+ </Tooltip>
109
+ </TableCell>
110
+ </TableRow>
111
+ ))}
112
+ </TableBody>
113
+ </Table>}
114
+
115
+ <Button
116
+ onClick={() => {
117
+ setCurrentDialog({
118
+ isNewCollection: true
119
+ });
120
+ }}
121
+ variant={"text"}
122
+ startIcon={<AddIcon/>}>
123
+ Add subcollection
124
+ </Button>
125
+
126
+ </Paper>
127
+
128
+ </div>
129
+
130
+ <div className={"flex-grow flex flex-col gap-4 items-start"}>
131
+ <Typography variant={"h5"}>
132
+ Custom views
133
+ </Typography>
134
+
135
+ {totalEntityViews === 0 &&
136
+ <Alert action={<Button variant="text"
137
+ size={"small"}
138
+ href={"https://firecms.co/docs/customization_quickstart"}
139
+ component={"a"}
140
+ rel="noopener noreferrer"
141
+ target="_blank">More info</Button>}>
142
+ Define your own custom views by uploading them with the CLI.
143
+ </Alert>
144
+ }
145
+
146
+ {<>
147
+ <Paper className={"flex flex-col gap-4 p-2 w-full"}>
148
+ <Table>
149
+ <TableBody>
150
+ {resolvedEntityViews.map((view) => (
151
+ <TableRow key={view.key}>
152
+ <TableCell
153
+ align="left">
154
+ <Typography variant={"subtitle2"} className={"flex-grow"}>
155
+ {view.name}
156
+ </Typography>
157
+ </TableCell>
158
+ <TableCell
159
+ align="right">
160
+ <Tooltip title={"Remove"}>
161
+ <IconButton size="small"
162
+ onClick={(e) => {
163
+ e.preventDefault();
164
+ e.stopPropagation();
165
+ setViewToDelete(view.key);
166
+ }}
167
+ color="inherit">
168
+ <DeleteIcon size={"small"}/>
169
+ </IconButton>
170
+ </Tooltip>
171
+ </TableCell>
172
+ </TableRow>
173
+ ))}
174
+ {hardCodedEntityViews.map((view) => (
175
+ <TableRow key={view.key}>
176
+ <TableCell
177
+ align="left">
178
+ <Typography variant={"subtitle2"} className={"flex-grow"}>
179
+ {view.name}
180
+ </Typography>
181
+ <Typography variant={"caption"} className={"flex-grow"}>
182
+ This view is defined in code with
183
+ key <code>{view.key}</code>
184
+ </Typography>
185
+ </TableCell>
186
+ </TableRow>
187
+ ))}
188
+ </TableBody>
189
+ </Table>
190
+
191
+ <Button
192
+ onClick={() => {
193
+ setAddEntityViewDialogOpen(true);
194
+ }}
195
+ variant={"text"}
196
+ startIcon={<AddIcon/>}>
197
+ Add custom entity view
198
+ </Button>
199
+ </Paper>
200
+
201
+ </>}
202
+
203
+
204
+ </div>
205
+
206
+ </div>
207
+ </Container>
208
+
209
+ <div style={{ height: "52px" }}/>
210
+
211
+ {subcollectionToDelete &&
212
+ <DeleteConfirmationDialog open={Boolean(subcollectionToDelete)}
213
+ onAccept={() => {
214
+ const props = {
215
+ id: subcollectionToDelete,
216
+ parentCollectionIds: [...(parentCollectionIds ?? []), collection.id]
217
+ };
218
+ console.debug("Deleting subcollection", props)
219
+ configController.deleteCollection(props);
220
+ setSubcollectionToDelete(undefined);
221
+ }}
222
+ onCancel={() => setSubcollectionToDelete(undefined)}
223
+ title={<>Delete this subcollection?</>}
224
+ body={<> This will <b>not
225
+ delete any data</b>, only
226
+ the collection in the CMS</>}/>}
227
+ {viewToDelete &&
228
+ <DeleteConfirmationDialog open={Boolean(viewToDelete)}
229
+ onAccept={() => {
230
+ setFieldValue("entityViews", values.entityViews?.filter(e => e !== viewToDelete));
231
+ setViewToDelete(undefined);
232
+ }}
233
+ onCancel={() => setViewToDelete(undefined)}
234
+ title={<>Remove this view?</>}
235
+ body={<>This will <b>not
236
+ delete any data</b>, only
237
+ the view in the CMS</>}/>}
238
+
239
+ <CollectionEditorDialog
240
+ open={Boolean(currentDialog)}
241
+ configController={configController}
242
+ parentCollection={collection}
243
+ collectionInference={collectionInference}
244
+ parentCollectionIds={[...parentCollectionIds ?? [], values.id]}
245
+ isNewCollection={false}
246
+ {...currentDialog}
247
+ getUser={getUser}
248
+ handleClose={() => {
249
+ setCurrentDialog(undefined);
250
+ }}/>
251
+
252
+ <EntityCustomViewsSelectDialog
253
+ open={addEntityViewDialogOpen}
254
+ onClose={(selectedViewKey) => {
255
+ if (selectedViewKey) {
256
+ setFieldValue("entityViews", [...(values.entityViews ?? []), selectedViewKey]);
257
+ }
258
+ setAddEntityViewDialogOpen(false);
259
+ }}/>
260
+ </div>
261
+ );
262
+ }
@@ -0,0 +1,39 @@
1
+ import React from "react";
2
+
3
+ import { BooleanSwitchWithLabel, Tooltip } from "@firecms/ui";
4
+ import { FormexFieldProps } from "@firecms/formex";
5
+
6
+ export function SwitchControl({
7
+ field,
8
+ form,
9
+ label,
10
+ tooltip,
11
+ disabled,
12
+ size = "small",
13
+ allowIndeterminate
14
+ }: FormexFieldProps & {
15
+ label: string,
16
+ tooltip?: string,
17
+ disabled?: boolean,
18
+ size?: "small" | "medium",
19
+ allowIndeterminate?: boolean
20
+ }) {
21
+
22
+ const formControlLabel = <BooleanSwitchWithLabel
23
+ label={label}
24
+ size={size}
25
+ position={"start"}
26
+ value={field.value}
27
+ disabled={disabled}
28
+ allowIndeterminate={allowIndeterminate}
29
+ onValueChange={(checked:boolean | null) => form.setFieldValue(field.name, checked)}/>;
30
+
31
+ if (tooltip)
32
+ return (
33
+ <Tooltip
34
+ title={tooltip}>
35
+ {formControlLabel}
36
+ </Tooltip>
37
+ );
38
+ return formControlLabel;
39
+ }
@@ -0,0 +1,47 @@
1
+ import React from "react";
2
+ import { Button, Dialog, DialogActions, DialogContent, Typography } from "@firecms/ui";
3
+
4
+ export interface UnsavedChangesDialogProps {
5
+ open: boolean;
6
+ body?: React.ReactNode;
7
+ title?: string;
8
+ handleOk: () => void;
9
+ handleCancel: () => void;
10
+ }
11
+
12
+ export function UnsavedChangesDialog({
13
+ open,
14
+ handleOk,
15
+ handleCancel,
16
+ body,
17
+ title
18
+ }: UnsavedChangesDialogProps) {
19
+
20
+ return (
21
+ <Dialog
22
+ open={open}
23
+ onOpenChange={(open) => open ? handleCancel() : handleOk()}
24
+ aria-labelledby="alert-dialog-title"
25
+ aria-describedby="alert-dialog-description"
26
+ >
27
+ <DialogContent>
28
+ <Typography variant={"h6"}>
29
+ {title ?? "Unsaved changes"}
30
+ </Typography>
31
+
32
+ {body && <Typography>
33
+ {body}
34
+ </Typography>}
35
+ <Typography>
36
+ Are you sure?
37
+ </Typography>
38
+
39
+ </DialogContent>
40
+
41
+ <DialogActions>
42
+ <Button variant="text" onClick={handleCancel} autoFocus> Cancel </Button>
43
+ <Button onClick={handleOk}> Ok </Button>
44
+ </DialogActions>
45
+ </Dialog>
46
+ );
47
+ }
@@ -0,0 +1,37 @@
1
+ import { convertDataToEntity, getPropertiesMapping, ImportConfig } from "@firecms/data_import_export";
2
+ import { EntityCollectionTable, Properties, useSelectionController } from "@firecms/core";
3
+ import { useEffect } from "react";
4
+ import { Typography } from "@firecms/ui";
5
+
6
+ export function CollectionEditorImportDataPreview({ importConfig, properties, propertiesOrder }: {
7
+ importConfig: ImportConfig,
8
+ properties: Properties,
9
+ propertiesOrder: string[]
10
+ }) {
11
+
12
+ useEffect(() => {
13
+ const propertiesMapping = getPropertiesMapping(importConfig.originProperties, properties);
14
+ const mappedData = importConfig.importData.map(d => convertDataToEntity(d, importConfig.idColumn, importConfig.headersMapping, properties, propertiesMapping, "TEMP_PATH"));
15
+ importConfig.setEntities(mappedData);
16
+ }, []);
17
+
18
+ const selectionController = useSelectionController();
19
+
20
+ return <EntityCollectionTable
21
+ title={<div>
22
+ <Typography variant={"subtitle2"}>Imported data preview</Typography>
23
+ <Typography variant={"caption"}>Entities with the same id will be overwritten</Typography>
24
+ </div>}
25
+ tableController={{
26
+ data: importConfig.entities,
27
+ dataLoading: false,
28
+ noMoreToLoad: false
29
+ }}
30
+ endAdornment={<div className={"h-12"}/>}
31
+ filterable={false}
32
+ sortable={false}
33
+ selectionController={selectionController}
34
+ displayedColumnIds={propertiesOrder.map(p => ({ key: p, disabled: false }))}
35
+ properties={properties}/>
36
+
37
+ }
@@ -0,0 +1,268 @@
1
+ import {
2
+ DataNewPropertiesMapping,
3
+ getInferenceType,
4
+ ImportConfig,
5
+ ImportNewPropertyFieldPreview
6
+ } from "@firecms/data_import_export";
7
+ import { getIn, useFormex } from "@firecms/formex";
8
+
9
+ import { PropertyConfigBadge, getFieldConfig, getFieldId, Properties, Property, PropertyConfig, } from "@firecms/core";
10
+ import { Container, Select, Tooltip, Typography } from "@firecms/ui";
11
+ import React, { useState } from "react";
12
+ import { OnPropertyChangedParams, PropertyFormDialog, PropertyWithId } from "../PropertyEditView";
13
+ import { getFullId, idToPropertiesPath, namespaceToPropertiesOrderPath } from "../util";
14
+ import { PersistedCollection } from "../../../types/persisted_collection";
15
+ import { updatePropertyFromWidget } from "../utils/update_property_for_widget";
16
+ import { PropertySelectItem } from "../PropertySelectItem";
17
+ import { supportedFields } from "../utils/supported_fields";
18
+ import { buildPropertyFromData } from "@firecms/schema_inference";
19
+
20
+ export function CollectionEditorImportMapping({
21
+ importConfig,
22
+ propertyConfigs,
23
+ collectionEditable
24
+ }:
25
+ {
26
+ importConfig: ImportConfig,
27
+ propertyConfigs: Record<string, PropertyConfig>,
28
+ collectionEditable: boolean
29
+ }) {
30
+
31
+ // const {
32
+ // values,
33
+ // setFieldValue,
34
+ // setFieldTouched,
35
+ // setValues,
36
+ // submitCount
37
+ // } = useFormex();
38
+
39
+ const {
40
+ setFieldValue,
41
+ setFieldTouched,
42
+ values
43
+ } = useFormex<PersistedCollection>();
44
+ const [selectedProperty, setSelectedProperty] = useState<PropertyWithId | undefined>(undefined);
45
+
46
+ const currentPropertiesOrderRef = React.useRef<{
47
+ [key: string]: string[]
48
+ }>(values.propertiesOrder ? { "": values.propertiesOrder } : {});
49
+
50
+ const propertyKey = selectedProperty ? selectedProperty.id : undefined;
51
+ const property = selectedProperty || undefined;
52
+
53
+ const onPropertyChanged = ({
54
+ id,
55
+ property,
56
+ previousId,
57
+ namespace
58
+ }: OnPropertyChangedParams) => {
59
+
60
+ const fullId = id ? getFullId(id, namespace) : undefined;
61
+ const propertyPath = fullId ? idToPropertiesPath(fullId) : undefined;
62
+
63
+ // setSelectedProperty(property);
64
+ const getCurrentPropertiesOrder = (namespace?: string) => {
65
+ if (!namespace) return currentPropertiesOrderRef.current[""];
66
+ return currentPropertiesOrderRef.current[namespace] ?? getIn(values, namespaceToPropertiesOrderPath(namespace));
67
+ }
68
+
69
+ const updatePropertiesOrder = (newPropertiesOrder: string[], namespace?: string) => {
70
+ const propertiesOrderPath = namespaceToPropertiesOrderPath(namespace);
71
+
72
+ setFieldValue(propertiesOrderPath, newPropertiesOrder, false);
73
+ currentPropertiesOrderRef.current[namespace ?? ""] = newPropertiesOrder;
74
+
75
+ };
76
+
77
+ // If the id has changed we need to a little cleanup
78
+ if (previousId && previousId !== id) {
79
+ const previousFullId = getFullId(previousId, namespace);
80
+ const previousPropertyPath = idToPropertiesPath(previousFullId);
81
+
82
+ const currentPropertiesOrder = getCurrentPropertiesOrder(namespace);
83
+
84
+ // replace previousId with id in propertiesOrder
85
+ const newPropertiesOrder = currentPropertiesOrder
86
+ .map((p) => p === previousId ? id : p)
87
+ .filter((p) => p !== undefined) as string[];
88
+ updatePropertiesOrder(newPropertiesOrder, namespace);
89
+
90
+ // replace previousId with id in headersMapping
91
+ const newHeadersMapping = { ...importConfig.headersMapping };
92
+ Object.keys(newHeadersMapping).forEach((key) => {
93
+ if (newHeadersMapping[key] === previousId) {
94
+ newHeadersMapping[key] = id ?? "";
95
+ }
96
+ });
97
+ importConfig.setHeadersMapping(newHeadersMapping);
98
+
99
+ // if (id) {
100
+ // setSelectedPropertyIndex(newPropertiesOrder.indexOf(id));
101
+ // setSelectedPropertyKey(id);
102
+ // }
103
+ setFieldValue(previousPropertyPath, undefined, false);
104
+ setFieldTouched(previousPropertyPath, false, false);
105
+ }
106
+
107
+ if (propertyPath) {
108
+ setFieldValue(propertyPath, property, false);
109
+ setFieldTouched(propertyPath, true, false);
110
+ }
111
+ };
112
+ const onPropertyTypeChanged = async ({
113
+ id,
114
+ importKey,
115
+ property,
116
+ namespace
117
+ }: OnPropertyChangedParams & {
118
+ importKey: string
119
+ }) => {
120
+
121
+ const fullId = id ? getFullId(id, namespace) : undefined;
122
+ const propertyPath = fullId ? idToPropertiesPath(fullId) : undefined;
123
+
124
+ // we try to infer the rest of the properties of a property, from the type and the data
125
+ const propertyData = importConfig.importData.map((d) => getIn(d, importKey));
126
+ const inferredNewProperty = {
127
+ ...buildPropertyFromData(propertyData, property, getInferenceType),
128
+ editable: true
129
+ };
130
+
131
+ if (propertyPath) {
132
+ if (inferredNewProperty) {
133
+ setFieldValue(propertyPath, inferredNewProperty, false);
134
+ } else {
135
+ setFieldValue(propertyPath, property, false);
136
+ }
137
+ setFieldTouched(propertyPath, true, false);
138
+ }
139
+ };
140
+
141
+ return (
142
+
143
+ <div className={"overflow-auto my-auto"}>
144
+ <Container maxWidth={"6xl"} className={"flex flex-col gap-4 p-8 m-auto"}>
145
+
146
+ <Typography variant="h6" className={"mt-4"}>Data property mapping</Typography>
147
+
148
+ <DataNewPropertiesMapping headersMapping={importConfig.headersMapping}
149
+ idColumn={importConfig.idColumn}
150
+ originProperties={importConfig.originProperties}
151
+ destinationProperties={values.properties as Properties}
152
+ onIdPropertyChanged={(value) => importConfig.setIdColumn(value ?? undefined)}
153
+ buildPropertyView={({
154
+ property,
155
+ propertyKey,
156
+ importKey
157
+ }) => {
158
+ return <ImportNewPropertyFieldPreview
159
+ property={property}
160
+ propertyKey={propertyKey}
161
+ onPropertyNameChanged={(propertyKey: string, value: string) => setFieldValue(`properties.${propertyKey}.name`, value, false)}
162
+ onEditClick={() => {
163
+ if (!propertyKey || !property) return;
164
+ setSelectedProperty({
165
+ ...property,
166
+ id: propertyKey,
167
+ editable: true
168
+ });
169
+ }}
170
+ propertyTypeView={<PropertySelect property={property}
171
+ disabled={false}
172
+ onPropertyChanged={(props) => onPropertyTypeChanged({
173
+ ...props,
174
+ importKey
175
+ })}
176
+ propertyKey={propertyKey}
177
+ propertyConfigs={propertyConfigs}/>}
178
+ />;
179
+ }}/>
180
+ </Container>
181
+
182
+ <PropertyFormDialog
183
+ open={selectedProperty !== undefined}
184
+ propertyKey={propertyKey}
185
+ property={property}
186
+ inArray={false}
187
+ autoUpdateId={false}
188
+ onPropertyChanged={onPropertyChanged}
189
+ allowDataInference={false}
190
+ collectionEditable={collectionEditable}
191
+ onOkClicked={() => {
192
+ setSelectedProperty(undefined);
193
+ }}
194
+ onCancel={() => {
195
+ setSelectedProperty(undefined);
196
+ }}
197
+ autoOpenTypeSelect={false}
198
+ existingProperty={false}
199
+ propertyConfigs={propertyConfigs}/>
200
+
201
+ <div style={{ height: "52px" }}/>
202
+ </div>
203
+ );
204
+
205
+ }
206
+
207
+ function PropertySelect({
208
+ property,
209
+ onPropertyChanged,
210
+ propertyKey,
211
+ propertyConfigs,
212
+ disabled
213
+ }: {
214
+ property: Property | null,
215
+ propertyKey: string | null,
216
+ onPropertyChanged: ({
217
+ id,
218
+ property,
219
+ previousId,
220
+ namespace
221
+ }: OnPropertyChangedParams) => void,
222
+ propertyConfigs: Record<string, PropertyConfig>,
223
+ disabled?: boolean
224
+ }) {
225
+
226
+ const fieldId = property ? getFieldId(property) : null;
227
+ const widget = property ? getFieldConfig(property, propertyConfigs) : null;
228
+
229
+ const [selectOpen, setSelectOpen] = useState(false);
230
+
231
+ return <Tooltip title={property && widget ? `${widget?.name} - ${property.dataType}` : undefined}
232
+ open={selectOpen ? false : undefined}>
233
+ <Select
234
+ open={selectOpen}
235
+ onOpenChange={setSelectOpen}
236
+ invisible={true}
237
+ className={"w-full"}
238
+ disabled={disabled}
239
+ error={!widget}
240
+ value={fieldId ?? ""}
241
+ placeholder={"Select a property widget"}
242
+ position={"item-aligned"}
243
+ renderValue={(value) => {
244
+ if (!widget) return null;
245
+ return <PropertyConfigBadge propertyConfig={widget}/>
246
+ }}
247
+ onValueChange={(newSelectedWidgetId) => {
248
+ const newProperty = updatePropertyFromWidget(property, newSelectedWidgetId, propertyConfigs)
249
+ if (!propertyKey) return;
250
+ onPropertyChanged({
251
+ id: propertyKey,
252
+ property: newProperty,
253
+ previousId: propertyKey,
254
+ namespace: undefined
255
+ });
256
+ }}>
257
+ {Object.entries(supportedFields).map(([key, widget]) => {
258
+ return <PropertySelectItem
259
+ key={key}
260
+ value={key}
261
+ optionDisabled={false}
262
+ propertyConfig={widget}
263
+ existing={false}/>;
264
+ })
265
+ }
266
+ </Select>
267
+ </Tooltip>;
268
+ }