@firecms/collection_editor 3.0.0-beta.2-pre.4 → 3.0.0-beta.2

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.
@@ -1 +1,3 @@
1
- export declare function RootCollectionSuggestions(): import("react/jsx-runtime").JSX.Element;
1
+ export declare function RootCollectionSuggestions({ introMode }: {
2
+ introMode?: "new_project" | "existing_project";
3
+ }): import("react/jsx-runtime").JSX.Element;
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import { EntityCollection, FireCMSPlugin, ModifyCollectionProps, User } from "@firecms/core";
2
+ import { FireCMSPlugin, User } from "@firecms/core";
3
3
  import { CollectionEditorPermissionsBuilder } from "./types/config_permissions";
4
4
  import { PersistedCollection } from "./types/persisted_collection";
5
5
  import { CollectionInference } from "./types/collection_inference";
@@ -9,7 +9,6 @@ export interface CollectionConfigControllerProps<EC extends PersistedCollection
9
9
  * Firebase app where the configuration is saved.
10
10
  */
11
11
  collectionConfigController: CollectionsConfigController;
12
- modifyCollection?: (props: ModifyCollectionProps) => EntityCollection | void;
13
12
  /**
14
13
  * Define what actions can be performed on the configuration.
15
14
  */
@@ -31,6 +30,7 @@ export interface CollectionConfigControllerProps<EC extends PersistedCollection
31
30
  getData?: (path: string, parentPaths: string[]) => Promise<object[]>;
32
31
  getUser: (uid: string) => UserType | null;
33
32
  onAnalyticsEvent?: (event: string, params?: object) => void;
33
+ introMode?: "new_project" | "existing_project";
34
34
  }
35
35
  /**
36
36
  * Use this hook to initialise the Collection Editor plugin.
@@ -43,4 +43,7 @@ export interface CollectionConfigControllerProps<EC extends PersistedCollection
43
43
  * @param getUser
44
44
  * @param collectionInference
45
45
  */
46
- export declare function useCollectionEditorPlugin<EC extends PersistedCollection = PersistedCollection, UserType extends User = User>({ collectionConfigController, modifyCollection, configPermissions, reservedGroups, extraView, pathSuggestions, getUser, collectionInference, getData, onAnalyticsEvent }: CollectionConfigControllerProps<EC, UserType>): FireCMSPlugin<any, any, PersistedCollection>;
46
+ export declare function useCollectionEditorPlugin<EC extends PersistedCollection = PersistedCollection, UserType extends User = User>({ collectionConfigController, introMode, configPermissions, reservedGroups, extraView, pathSuggestions, getUser, collectionInference, getData, onAnalyticsEvent }: CollectionConfigControllerProps<EC, UserType>): FireCMSPlugin<any, any, PersistedCollection>;
47
+ export declare function IntroWidget({ introMode }: {
48
+ introMode?: "new_project" | "existing_project";
49
+ }): import("react/jsx-runtime").JSX.Element;
package/package.json CHANGED
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "name": "@firecms/collection_editor",
3
3
  "type": "module",
4
- "version": "3.0.0-beta.2-pre.4",
4
+ "version": "3.0.0-beta.2",
5
5
  "main": "./dist/index.umd.js",
6
6
  "module": "./dist/index.es.js",
7
7
  "types": "dist/index.d.ts",
8
8
  "source": "src/index.ts",
9
9
  "dependencies": {
10
- "@firecms/data_import_export": "^3.0.0-beta.2-pre.4",
11
- "@firecms/formex": "^3.0.0-beta.2-pre.4",
12
- "@firecms/schema_inference": "^3.0.0-beta.2-pre.4",
13
- "@firecms/ui": "^3.0.0-beta.2-pre.4",
10
+ "@firecms/data_import_export": "^3.0.0-beta.2",
11
+ "@firecms/formex": "^3.0.0-beta.2",
12
+ "@firecms/schema_inference": "^3.0.0-beta.2",
13
+ "@firecms/ui": "^3.0.0-beta.2",
14
14
  "json5": "^2.2.3",
15
15
  "prism-react-renderer": "^2.3.1"
16
16
  },
@@ -63,7 +63,7 @@
63
63
  "react-router-dom": "^6.22.0",
64
64
  "ts-jest": "^29.1.2",
65
65
  "typescript": "^5.3.3",
66
- "vite": "^4.5.2",
66
+ "vite": "^5.1.1",
67
67
  "vite-plugin-fonts": "^0.7.0"
68
68
  },
69
69
  "files": [
@@ -73,5 +73,5 @@
73
73
  "publishConfig": {
74
74
  "access": "public"
75
75
  },
76
- "gitHead": "a947c08aada0e374aec7dff2b0dc9e540e170834"
76
+ "gitHead": "9c3561bc09311f2339bb6fa224c88a62d3b19617"
77
77
  }
@@ -1,9 +1,9 @@
1
1
  import { unslugify, useAuthController, useNavigationController } from "@firecms/core";
2
- import { AddIcon, Chip, Collapse, Typography, } from "@firecms/ui";
2
+ import { AddIcon, Chip, CircularProgress, Collapse, Typography, } from "@firecms/ui";
3
3
  import { useCollectionEditorController } from "../useCollectionEditorController";
4
4
  import React from "react";
5
5
 
6
- export function RootCollectionSuggestions() {
6
+ export function RootCollectionSuggestions({ introMode }: { introMode?: "new_project" | "existing_project" }) {
7
7
 
8
8
  const authController = useAuthController();
9
9
  const navigationController = useNavigationController();
@@ -15,22 +15,27 @@ export function RootCollectionSuggestions() {
15
15
  }).createCollections
16
16
  : true;
17
17
 
18
- const rootPathSuggestions = collectionEditorController.rootPathSuggestions ?? [];
18
+ const rootPathSuggestions = collectionEditorController.rootPathSuggestions;
19
19
 
20
- const showSuggestions = rootPathSuggestions.length > 3 || ((navigationController.collections ?? []).length === 0 && rootPathSuggestions.length > 0);
20
+ const showSuggestions = (rootPathSuggestions ?? []).length > 3 || ((navigationController.collections ?? []).length === 0 && (rootPathSuggestions ?? []).length > 0);
21
+ const forceShowSuggestions = introMode === "existing_project";
21
22
  return <Collapse
22
- in={showSuggestions}>
23
+ in={forceShowSuggestions || showSuggestions}>
23
24
 
24
25
  <div
25
26
  className={"flex flex-col gap-1 p-2 my-4"}>
26
27
 
27
- <Typography variant={"body2"} color={"secondary"}>
28
- Create a collection from your data:
29
- </Typography>
28
+ {!introMode && <Typography variant={"body2"} color={"secondary"}>
29
+ Create a collection <b>automatically</b> from your data:
30
+ </Typography>}
31
+
32
+ {introMode === "existing_project" && <Typography>
33
+ You will see your <b>database collections</b> here, a few seconds after project creation
34
+ </Typography>}
30
35
 
31
36
  <div
32
37
  className={"flex flex-row gap-1 overflow-scroll no-scrollbar "}>
33
- {rootPathSuggestions.map((path) => {
38
+ {(rootPathSuggestions ?? []).map((path) => {
34
39
  return (
35
40
  <div key={path}>
36
41
  <Chip
@@ -50,6 +55,8 @@ export function RootCollectionSuggestions() {
50
55
  </div>
51
56
  );
52
57
  })}
58
+ {rootPathSuggestions === undefined && <CircularProgress size={"small"}/>}
59
+ {rootPathSuggestions?.length === 0 && <Typography variant={"caption"}>No suggestions</Typography>}
53
60
  </div>
54
61
  </div>
55
62
  </Collapse>
@@ -21,7 +21,7 @@ import {
21
21
  } from "@firecms/ui";
22
22
 
23
23
  import { FieldHelperView } from "./properties/FieldHelperView";
24
- import { useFormex, Field, getIn } from "@firecms/formex";
24
+ import { Field, getIn, useFormex } from "@firecms/formex";
25
25
 
26
26
  export function CollectionDetailsForm({
27
27
  isNewCollection,
@@ -70,7 +70,7 @@ export function CollectionDetailsForm({
70
70
 
71
71
  const singularNameTouched = getIn(touched, "singularName");
72
72
  if (!singularNameTouched && isNewCollection && name) {
73
- setFieldValue("singularName", singular(name))
73
+ setFieldValue("singularName", singular(name));
74
74
  }
75
75
 
76
76
  };
@@ -96,17 +96,17 @@ export function CollectionDetailsForm({
96
96
  const isSubcollection = !!parentCollection;
97
97
 
98
98
  let customIdValue: "true" | "false" | "optional" | "code_defined" | undefined;
99
- if (customIdValue) {
100
- if (typeof values.customId === "object") {
101
- customIdValue = "code_defined";
102
- } else if (values.customId === true) {
103
- customIdValue = "true";
104
- } else if (values.customId === false) {
105
- customIdValue = "false";
106
- } else if (values.customId === "optional") {
107
- customIdValue = "optional";
108
- }
99
+ if (typeof values.customId === "object") {
100
+ customIdValue = "code_defined";
101
+ } else if (values.customId === true) {
102
+ customIdValue = "true";
103
+ } else if (values.customId === false) {
104
+ customIdValue = "false";
105
+ } else if (values.customId === "optional") {
106
+ customIdValue = "optional";
109
107
  }
108
+
109
+ const showErrors = submitCount > 0;
110
110
  return (
111
111
  <div className={"overflow-auto my-auto"}>
112
112
  <Container maxWidth={"4xl"} className={"flex flex-col gap-4 p-8 m-auto"}>
@@ -141,7 +141,7 @@ export function CollectionDetailsForm({
141
141
  onChange={(e: any) => updateName(e.target.value)}
142
142
  label={"Name"}
143
143
  required
144
- error={touched.name && Boolean(errors.name)}/>
144
+ error={showErrors && Boolean(errors.name)}/>
145
145
  <FieldHelperView error={touched.name && Boolean(errors.name)}>
146
146
  {touched.name && Boolean(errors.name) ? errors.name : "Name of in this collection, usually a plural name (e.g. Products)"}
147
147
  </FieldHelperView>
@@ -153,7 +153,7 @@ export function CollectionDetailsForm({
153
153
  label={"Path"}
154
154
  disabled={!isNewCollection}
155
155
  required
156
- error={touched.path && Boolean(errors.path)}/>
156
+ error={showErrors && Boolean(errors.path)}/>
157
157
 
158
158
  <FieldHelperView error={touched.path && Boolean(errors.path)}>
159
159
  {touched.path && Boolean(errors.path)
@@ -165,7 +165,7 @@ export function CollectionDetailsForm({
165
165
 
166
166
  {!isSubcollection && <div className={"col-span-12 sm:col-span-4 relative"}>
167
167
 
168
- <TextField error={touched.group && Boolean(errors.group)}
168
+ <TextField error={showErrors && Boolean(errors.group)}
169
169
  disabled={isSubmitting}
170
170
  value={values.group ?? ""}
171
171
  autoComplete="off"
@@ -191,7 +191,7 @@ export function CollectionDetailsForm({
191
191
  })}
192
192
  </Autocomplete>
193
193
  <FieldHelperView>
194
- {touched.group && Boolean(errors.group) ? errors.group : "Group of the collection"}
194
+ {showErrors && Boolean(errors.group) ? errors.group : "Group of the collection"}
195
195
  </FieldHelperView>
196
196
  </div>}
197
197
 
@@ -215,7 +215,7 @@ export function CollectionDetailsForm({
215
215
  as={DebouncedTextField}
216
216
  disabled={!isNewCollection}
217
217
  label={"Collection id"}
218
- error={touched.id && Boolean(errors.id)}/>
218
+ error={showErrors && Boolean(errors.id)}/>
219
219
  <FieldHelperView error={touched.id && Boolean(errors.id)}>
220
220
  {touched.id && Boolean(errors.id) ? errors.id : "This id identifies this collection"}
221
221
  </FieldHelperView>
@@ -223,8 +223,8 @@ export function CollectionDetailsForm({
223
223
 
224
224
  <div className={"col-span-12"}>
225
225
  <TextField
226
- error={touched.singularName && Boolean(errors.singularName)}
227
- id={"singularName"}
226
+ error={showErrors && Boolean(errors.singularName)}
227
+ name={"singularName"}
228
228
  aria-describedby={"singularName-helper"}
229
229
  onChange={(e) => {
230
230
  setFieldTouched("singularName", true);
@@ -232,14 +232,14 @@ export function CollectionDetailsForm({
232
232
  }}
233
233
  value={values.singularName ?? ""}
234
234
  label={"Singular name"}/>
235
- <FieldHelperView error={touched.singularName && Boolean(errors.singularName)}>
236
- {touched.singularName && Boolean(errors.singularName) ? errors.singularName : "Optionally define a singular name for your entities"}
235
+ <FieldHelperView error={showErrors && Boolean(errors.singularName)}>
236
+ {showErrors && Boolean(errors.singularName) ? errors.singularName : "Optionally define a singular name for your entities"}
237
237
  </FieldHelperView>
238
238
  </div>
239
239
  <div className={"col-span-12"}>
240
240
  <TextField
241
- error={touched.description && Boolean(errors.description)}
242
- id="description"
241
+ error={showErrors && Boolean(errors.description)}
242
+ name="description"
243
243
  value={values.description ?? ""}
244
244
  onChange={handleChange}
245
245
  multiline
@@ -247,8 +247,8 @@ export function CollectionDetailsForm({
247
247
  aria-describedby="description-helper-text"
248
248
  label="Description"
249
249
  />
250
- <FieldHelperView error={touched.description && Boolean(errors.description)}>
251
- {touched.description && Boolean(errors.description) ? errors.description : "Description of the collection, you can use markdown"}
250
+ <FieldHelperView error={showErrors && Boolean(errors.description)}>
251
+ {showErrors && Boolean(errors.description) ? errors.description : "Description of the collection, you can use markdown"}
252
252
  </FieldHelperView>
253
253
  </div>
254
254
 
@@ -374,7 +374,6 @@ function CollectionEditorInternal<M extends Record<string, any>>({
374
374
  if (!isNewCollection) {
375
375
  saveCollection(newCollectionState).then(() => {
376
376
  formexController.resetForm({ values: initialValues });
377
- // setNextMode();
378
377
  handleClose(newCollectionState);
379
378
  });
380
379
  return;
@@ -482,7 +481,6 @@ function CollectionEditorInternal<M extends Record<string, any>>({
482
481
  const resolvedPath = !pathError ? navigation.resolveAliasesFrom(updatedFullPath) : undefined;
483
482
  const getDataWithPath = resolvedPath && getData ? () => getData(resolvedPath, parentPaths ?? []) : undefined;
484
483
 
485
- // eslint-disable-next-line react-hooks/rules-of-hooks
486
484
  useEffect(() => {
487
485
  setFormDirty(dirty);
488
486
  }, [dirty]);
@@ -552,9 +550,9 @@ function CollectionEditorInternal<M extends Record<string, any>>({
552
550
  {currentView === "welcome" &&
553
551
  <CollectionEditorWelcomeView
554
552
  path={path}
555
- onContinue={(data) => {
556
- if (data) {
557
- onImportDataSet(data);
553
+ onContinue={(importData) => {
554
+ if (importData) {
555
+ onImportDataSet(importData);
558
556
  setCurrentView("import_data_mapping");
559
557
  } else {
560
558
  setCurrentView("details");
@@ -577,12 +575,13 @@ function CollectionEditorInternal<M extends Record<string, any>>({
577
575
  {currentView === "import_data_saving" && importConfig &&
578
576
  <ImportSaveInProgress importConfig={importConfig}
579
577
  collection={values}
580
- onImportSuccess={(importedCollection) => {
581
- handleClose(importedCollection);
578
+ onImportSuccess={async (importedCollection) => {
582
579
  snackbarController.open({
583
580
  type: "info",
584
581
  message: "Data imported successfully"
585
582
  });
583
+ await saveCollection(values);
584
+ handleClose(importedCollection);
586
585
  }}
587
586
  />}
588
587
 
@@ -649,7 +648,6 @@ function CollectionEditorInternal<M extends Record<string, any>>({
649
648
  <Button variant={"text"}
650
649
  type="button"
651
650
  onClick={() => {
652
- saveCollection(values);
653
651
  setCurrentView("import_data_mapping");
654
652
  }}>
655
653
  <ArrowBackIcon/>
@@ -718,7 +716,6 @@ function CollectionEditorInternal<M extends Record<string, any>>({
718
716
  color="primary"
719
717
  type="submit"
720
718
  loading={isSubmitting}
721
- // disabled={isSubmitting || !dirty}
722
719
  >
723
720
  Update collection
724
721
  </LoadingButton>}
@@ -99,7 +99,7 @@ function collectionToCode(collection: EntityCollection): object {
99
99
  customId: collection.customId,
100
100
  initialFilter: collection.initialFilter,
101
101
  initialSort: collection.initialSort,
102
- properties: Object.entries(collection.properties)
102
+ properties: Object.entries(collection.properties ?? {})
103
103
  .map(([key, value]) => ({
104
104
  [key]: propertyCleanup(value)
105
105
  }))
@@ -53,7 +53,7 @@ export function SubcollectionsEditTab({
53
53
 
54
54
  const [currentDialog, setCurrentDialog] = React.useState<{
55
55
  isNewCollection: boolean,
56
- editedCollectionPath?: string,
56
+ editedCollectionId?: string,
57
57
  }>();
58
58
 
59
59
  const {
@@ -78,52 +78,53 @@ export function SubcollectionsEditTab({
78
78
  Subcollections of {values.name}
79
79
  </Typography>
80
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.id);
105
- }}
106
- color="inherit">
107
- <DeleteIcon size={"small"}/>
108
- </IconButton>
109
- </Tooltip>
110
- </TableCell>
111
- </TableRow>
112
- ))}
113
- </TableBody>
114
- </Table>
115
- </Paper>}
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>
116
127
 
117
- <Button
118
- onClick={() => {
119
- setCurrentDialog({
120
- isNewCollection: true
121
- });
122
- }}
123
- variant={"outlined"}
124
- startIcon={<AddIcon/>}>
125
- Add subcollection
126
- </Button>
127
128
  </div>
128
129
 
129
130
  <div className={"flex-grow flex flex-col gap-4 items-start"}>
@@ -131,7 +132,19 @@ export function SubcollectionsEditTab({
131
132
  Custom views
132
133
  </Typography>
133
134
 
134
- {totalEntityViews > 0 && <>
135
+
136
+ {totalEntityViews === 0 &&
137
+ <Alert action={<Button variant="text"
138
+ size={"small"}
139
+ href={"https://firecms.co/docs/customization_quickstart"}
140
+ component={"a"}
141
+ rel="noopener noreferrer"
142
+ target="_blank">More info</Button>}>
143
+ Define your own custom views by uploading it with the CLI.
144
+ </Alert>
145
+ }
146
+
147
+ {<>
135
148
  <Paper className={"flex flex-col gap-4 p-2 w-full"}>
136
149
  <Table>
137
150
  <TableBody>
@@ -175,29 +188,19 @@ export function SubcollectionsEditTab({
175
188
  ))}
176
189
  </TableBody>
177
190
  </Table>
191
+
192
+ <Button
193
+ onClick={() => {
194
+ setAddEntityViewDialogOpen(true);
195
+ }}
196
+ variant={"text"}
197
+ startIcon={<AddIcon/>}>
198
+ Add custom entity view
199
+ </Button>
178
200
  </Paper>
179
201
 
180
202
  </>}
181
203
 
182
- {totalEntityViews === 0 &&
183
- <Alert action={<Button variant="text"
184
- size={"small"}
185
- href={"https://firecms.co/docs/customization_quickstart"}
186
- component={"a"}
187
- rel="noopener noreferrer"
188
- target="_blank">More info</Button>}>
189
- Define your own custom views by uploading it with the CLI.
190
- </Alert>
191
- }
192
-
193
- <Button
194
- onClick={() => {
195
- setAddEntityViewDialogOpen(true);
196
- }}
197
- variant={"outlined"}
198
- startIcon={<AddIcon/>}>
199
- Add custom entity view
200
- </Button>
201
204
 
202
205
  </div>
203
206
 
@@ -140,7 +140,7 @@ export function CollectionEditorImportMapping({
140
140
 
141
141
  return (
142
142
 
143
- <div className={"overflow-auto my-auto bg-gray-50 dark:bg-gray-900"}>
143
+ <div className={"overflow-auto my-auto"}>
144
144
  <Container maxWidth={"6xl"} className={"flex flex-col gap-4 p-8 m-auto"}>
145
145
 
146
146
  <Typography variant="h6" className={"mt-4"}>Data property mapping</Typography>
@@ -23,19 +23,22 @@ export function BlockPropertyField({ disabled, getData, allowDataInference, prop
23
23
  const [selectedPropertyKey, setSelectedPropertyKey] = useState<string | undefined>();
24
24
  const [selectedPropertyNamespace, setSelectedPropertyNamespace] = useState<string | undefined>();
25
25
 
26
- const onPropertyCreated = useCallback(({
27
- id,
28
- property
29
- }: { id?: string, property: Property }) => {
26
+ const onPropertyChanged = ({
27
+ id,
28
+ property
29
+ }: { id?: string, property: Property }) => {
30
30
  if (!id)
31
31
  throw Error();
32
+
32
33
  setFieldValue("oneOf.properties", {
33
34
  ...(values.oneOf?.properties ?? {}),
34
35
  [id]: property
35
36
  }, false);
36
- setFieldValue("oneOf.propertiesOrder", [...(values.oneOf?.propertiesOrder ?? Object.keys(values.oneOf?.properties ?? {})), id], false);
37
+ const currentPropertiesOrder = values.oneOf?.propertiesOrder ?? Object.keys(values.oneOf?.properties ?? {});
38
+ const newPropertiesOrder = currentPropertiesOrder.includes(id) ? currentPropertiesOrder : [...currentPropertiesOrder, id];
39
+ setFieldValue("oneOf.propertiesOrder", newPropertiesOrder, false);
37
40
  setPropertyDialogOpen(false);
38
- }, [values.oneOf?.properties, values.oneOf?.propertiesOrder]);
41
+ };
39
42
 
40
43
  const selectedPropertyFullId = selectedPropertyKey ? getFullId(selectedPropertyKey, selectedPropertyNamespace) : undefined;
41
44
  const selectedProperty = selectedPropertyFullId ? getIn(values.oneOf?.properties, selectedPropertyFullId.replaceAll(".", ".properties.")) : undefined;
@@ -127,7 +130,7 @@ export function BlockPropertyField({ disabled, getData, allowDataInference, prop
127
130
  existingProperty={Boolean(selectedPropertyKey)}
128
131
  autoUpdateId={!selectedPropertyKey}
129
132
  autoOpenTypeSelect={!selectedPropertyKey}
130
- onPropertyChanged={onPropertyCreated}
133
+ onPropertyChanged={onPropertyChanged}
131
134
  existingPropertyKeys={selectedPropertyKey ? undefined : values.oneOf?.propertiesOrder}
132
135
  propertyConfigs={propertyConfigs}/>}
133
136
 
@@ -25,19 +25,23 @@ export function MapPropertyField({ disabled, getData, allowDataInference, proper
25
25
  const [selectedPropertyNamespace, setSelectedPropertyNamespace] = useState<string | undefined>();
26
26
 
27
27
  const propertiesOrder = values.propertiesOrder ?? Object.keys(values.properties ?? {});
28
- const onPropertyCreated = useCallback(({
29
- id,
30
- property
31
- }: { id?: string, property: Property }) => {
28
+ const onPropertyCreated = ({
29
+ id,
30
+ property
31
+ }: { id?: string, property: Property }) => {
32
32
  if (!id)
33
33
  throw Error();
34
34
  setFieldValue("properties", {
35
35
  ...(values.properties ?? {}),
36
36
  [id]: property
37
37
  }, false);
38
- setFieldValue("propertiesOrder", [...propertiesOrder, id], false);
38
+
39
+ const currentPropertiesOrder = values.propertiesOrder ?? Object.keys(values.properties ?? {});
40
+ const newPropertiesOrder = currentPropertiesOrder.includes(id) ? currentPropertiesOrder : [...currentPropertiesOrder, id];
41
+ setFieldValue("propertiesOrder", newPropertiesOrder, false);
42
+
39
43
  setPropertyDialogOpen(false);
40
- }, [values.properties, propertiesOrder]);
44
+ };
41
45
 
42
46
  const deleteProperty = useCallback((propertyKey?: string, namespace?: string) => {
43
47
  const fullId = propertyKey ? getFullId(propertyKey, namespace) : undefined;
@@ -60,15 +64,6 @@ export function MapPropertyField({ disabled, getData, allowDataInference, proper
60
64
  const selectedPropertyFullId = selectedPropertyKey ? getFullId(selectedPropertyKey, selectedPropertyNamespace) : undefined;
61
65
  const selectedProperty = selectedPropertyFullId ? getIn(values.properties, selectedPropertyFullId.replaceAll(".", ".properties.")) : undefined;
62
66
 
63
- const addChildButton = <Button
64
- color="primary"
65
- variant={"outlined"}
66
- onClick={() => setPropertyDialogOpen(true)}
67
- startIcon={<AddIcon/>}
68
- >
69
- Add property to {values.name ?? "this group"}
70
- </Button>;
71
-
72
67
  const empty = !propertiesOrder || propertiesOrder.length < 1;
73
68
 
74
69
  const onPropertyMove = useCallback((propertiesOrder: string[], namespace?: string) => {
@@ -80,7 +75,14 @@ export function MapPropertyField({ disabled, getData, allowDataInference, proper
80
75
  <div className={"col-span-12"}>
81
76
  <div className="flex justify-between items-end my-4">
82
77
  <Typography variant={"subtitle2"}>Properties in this group</Typography>
83
- {addChildButton}
78
+ <Button
79
+ color="primary"
80
+ variant={"outlined"}
81
+ onClick={() => setPropertyDialogOpen(true)}
82
+ startIcon={<AddIcon/>}
83
+ >
84
+ Add property to {values.name ?? "this group"}
85
+ </Button>
84
86
  </div>
85
87
  <Paper className="p-2 pl-8">
86
88
  <PropertyTree
@@ -1,4 +1,4 @@
1
- import React, { useCallback, useState } from "react";
1
+ import React, { useState } from "react";
2
2
  import { ArrayProperty, getFieldConfig, Property, PropertyConfig } from "@firecms/core";
3
3
  import { Button, Paper, Typography } from "@firecms/ui";
4
4
  import { Field, getIn, useFormex } from "@firecms/formex";