@firecms/collection_editor 3.0.0-alpha.5 → 3.0.0-alpha.51

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