@firecms/collection_editor 3.0.0-alpha.9 → 3.0.0-beta.2-pre.1

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