@firecms/collection_editor 3.0.0-alpha.17 → 3.0.0-alpha.19

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