@firecms/collection_editor 3.0.0-canary.18 → 3.0.0-canary.182

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 (74) hide show
  1. package/LICENSE +114 -21
  2. package/dist/ConfigControllerProvider.d.ts +11 -2
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.es.js +10069 -4770
  5. package/dist/index.es.js.map +1 -1
  6. package/dist/index.umd.js +10759 -3
  7. package/dist/index.umd.js.map +1 -1
  8. package/dist/types/collection_editor_controller.d.ts +14 -2
  9. package/dist/types/collection_inference.d.ts +1 -1
  10. package/dist/types/config_permissions.d.ts +2 -2
  11. package/dist/types/persisted_collection.d.ts +1 -1
  12. package/dist/ui/CollectionViewHeaderAction.d.ts +3 -2
  13. package/dist/ui/EditorCollectionActionStart.d.ts +2 -0
  14. package/dist/ui/PropertyAddColumnComponent.d.ts +3 -1
  15. package/dist/ui/collection_editor/CollectionDetailsForm.d.ts +3 -1
  16. package/dist/ui/collection_editor/CollectionEditorDialog.d.ts +3 -2
  17. package/dist/ui/collection_editor/CollectionEditorWelcomeView.d.ts +1 -1
  18. package/dist/ui/collection_editor/CollectionPropertiesEditorForm.d.ts +1 -1
  19. package/dist/ui/collection_editor/LayoutModeSwitch.d.ts +5 -0
  20. package/dist/ui/collection_editor/PropertyEditView.d.ts +8 -0
  21. package/dist/ui/collection_editor/PropertyTree.d.ts +9 -9
  22. package/dist/ui/collection_editor/SubcollectionsEditTab.d.ts +1 -1
  23. package/dist/ui/collection_editor/import/CollectionEditorImportMapping.d.ts +7 -0
  24. package/dist/ui/collection_editor/properties/MarkdownPropertyField.d.ts +4 -0
  25. package/dist/ui/collection_editor/properties/StringPropertyField.d.ts +1 -1
  26. package/dist/useCollectionEditorPlugin.d.ts +17 -11
  27. package/dist/utils/collections.d.ts +6 -0
  28. package/package.json +24 -35
  29. package/src/ConfigControllerProvider.tsx +76 -64
  30. package/src/index.ts +1 -0
  31. package/src/types/collection_editor_controller.tsx +14 -4
  32. package/src/types/collection_inference.ts +1 -1
  33. package/src/types/config_permissions.ts +1 -1
  34. package/src/types/persisted_collection.ts +2 -3
  35. package/src/ui/CollectionViewHeaderAction.tsx +10 -5
  36. package/src/ui/EditorCollectionAction.tsx +10 -63
  37. package/src/ui/EditorCollectionActionStart.tsx +88 -0
  38. package/src/ui/HomePageEditorCollectionAction.tsx +19 -13
  39. package/src/ui/NewCollectionButton.tsx +1 -1
  40. package/src/ui/NewCollectionCard.tsx +3 -3
  41. package/src/ui/PropertyAddColumnComponent.tsx +11 -6
  42. package/src/ui/collection_editor/CollectionDetailsForm.tsx +90 -11
  43. package/src/ui/collection_editor/CollectionEditorDialog.tsx +101 -34
  44. package/src/ui/collection_editor/CollectionEditorWelcomeView.tsx +8 -7
  45. package/src/ui/collection_editor/CollectionPropertiesEditorForm.tsx +39 -36
  46. package/src/ui/collection_editor/EntityCustomViewsSelectDialog.tsx +6 -5
  47. package/src/ui/collection_editor/EnumForm.tsx +10 -6
  48. package/src/ui/collection_editor/GetCodeDialog.tsx +56 -26
  49. package/src/ui/collection_editor/LayoutModeSwitch.tsx +54 -0
  50. package/src/ui/collection_editor/PropertyEditView.tsx +257 -80
  51. package/src/ui/collection_editor/PropertyFieldPreview.tsx +7 -10
  52. package/src/ui/collection_editor/PropertyTree.tsx +9 -7
  53. package/src/ui/collection_editor/SubcollectionsEditTab.tsx +26 -19
  54. package/src/ui/collection_editor/UnsavedChangesDialog.tsx +3 -5
  55. package/src/ui/collection_editor/import/CollectionEditorImportDataPreview.tsx +26 -9
  56. package/src/ui/collection_editor/import/CollectionEditorImportMapping.tsx +42 -9
  57. package/src/ui/collection_editor/properties/BlockPropertyField.tsx +32 -20
  58. package/src/ui/collection_editor/properties/DateTimePropertyField.tsx +54 -47
  59. package/src/ui/collection_editor/properties/EnumPropertyField.tsx +3 -1
  60. package/src/ui/collection_editor/properties/MapPropertyField.tsx +7 -6
  61. package/src/ui/collection_editor/properties/MarkdownPropertyField.tsx +139 -0
  62. package/src/ui/collection_editor/properties/ReferencePropertyField.tsx +2 -0
  63. package/src/ui/collection_editor/properties/RepeatPropertyField.tsx +0 -1
  64. package/src/ui/collection_editor/properties/StoragePropertyField.tsx +34 -19
  65. package/src/ui/collection_editor/properties/StringPropertyField.tsx +1 -10
  66. package/src/ui/collection_editor/properties/UrlPropertyField.tsx +1 -0
  67. package/src/ui/collection_editor/properties/validation/ValidationPanel.tsx +2 -2
  68. package/src/ui/collection_editor/templates/pages_template.ts +1 -6
  69. package/src/useCollectionEditorPlugin.tsx +41 -31
  70. package/src/utils/collections.ts +36 -0
  71. package/dist/ui/RootCollectionSuggestions.d.ts +0 -3
  72. package/dist/ui/collection_editor/PropertySelectItem.d.ts +0 -8
  73. package/src/ui/RootCollectionSuggestions.tsx +0 -63
  74. package/src/ui/collection_editor/PropertySelectItem.tsx +0 -32
@@ -1,5 +1,5 @@
1
1
  import { PluginHomePageAdditionalCardsProps, useAuthController } from "@firecms/core";
2
- import { AddIcon, Card, cn, Typography } from "@firecms/ui";
2
+ import { AddIcon, Card, cls, Typography } from "@firecms/ui";
3
3
  import { useCollectionEditorController } from "../useCollectionEditorController";
4
4
 
5
5
  export function NewCollectionCard({
@@ -20,7 +20,7 @@ export function NewCollectionCard({
20
20
  : true;
21
21
 
22
22
  return (
23
- <Card className={cn("h-full p-4 min-h-[124px]")}
23
+ <Card className={cls("h-full p-4 min-h-[124px]")}
24
24
  onClick={collectionEditorController && canCreateCollections
25
25
  ? () => collectionEditorController.createCollection({
26
26
  initialValues: group ? { group } : undefined,
@@ -31,7 +31,7 @@ export function NewCollectionCard({
31
31
  : undefined}>
32
32
 
33
33
  <div
34
- className="flex flex-col items-start h-full w-full items-center justify-center h-full w-full flex-grow flex-col">
34
+ className="flex items-center justify-center h-full w-full flex-grow flex-col">
35
35
  <AddIcon color="primary" size={"large"}/>
36
36
  <Typography color="primary"
37
37
  variant={"caption"}
@@ -1,4 +1,4 @@
1
- import { getDefaultPropertiesOrder, useAuthController } from "@firecms/core";
1
+ import { EntityTableController, getDefaultPropertiesOrder, useAuthController } from "@firecms/core";
2
2
  import { AddIcon, Tooltip } from "@firecms/ui";
3
3
  import { useCollectionEditorController } from "../useCollectionEditorController";
4
4
  import { PersistedCollection } from "../types/persisted_collection";
@@ -6,11 +6,13 @@ import { PersistedCollection } from "../types/persisted_collection";
6
6
  export function PropertyAddColumnComponent({
7
7
  fullPath,
8
8
  parentCollectionIds,
9
- collection
9
+ collection,
10
+ tableController
10
11
  }: {
11
12
  fullPath: string,
12
13
  parentCollectionIds: string[],
13
14
  collection: PersistedCollection;
15
+ tableController: EntityTableController;
14
16
  }) {
15
17
 
16
18
  const authController = useAuthController();
@@ -23,16 +25,19 @@ export function PropertyAddColumnComponent({
23
25
  : true;
24
26
 
25
27
  return (
26
- <Tooltip title={canEditCollection ? "Add new property" : "You don't have permission to add new properties"}>
28
+ <Tooltip
29
+ asChild={true}
30
+ title={canEditCollection ? "Add new property" : "You don't have permission to add new properties"}>
27
31
  <div
28
- className={"p-0.5 w-20 h-full flex items-center justify-center cursor-pointer bg-gray-100 bg-opacity-40 hover:bg-gray-100 dark:bg-gray-950 dark:bg-opacity-40 dark:hover:bg-gray-950"}
29
- // className={onHover ? "bg-white dark:bg-gray-950" : undefined}
32
+ className={"p-0.5 w-20 h-full flex items-center justify-center cursor-pointer bg-surface-100 bg-opacity-40 hover:bg-surface-100 dark:bg-surface-950 dark:bg-opacity-40 dark:hover:bg-surface-950"}
33
+ // className={onHover ? "bg-white dark:bg-surface-950" : undefined}
30
34
  onClick={() => {
31
35
  collectionEditorController.editProperty({
32
36
  editedCollectionId: collection.id,
33
37
  parentCollectionIds,
34
38
  currentPropertiesOrder: getDefaultPropertiesOrder(collection),
35
- collection
39
+ collection,
40
+ existingEntities: tableController.data
36
41
  });
37
42
  }}>
38
43
  <AddIcon color={"inherit"}/>
@@ -5,7 +5,8 @@ import {
5
5
  AutocompleteItem,
6
6
  BooleanSwitchWithLabel,
7
7
  Chip,
8
- cn,
8
+ CloseIcon,
9
+ cls,
9
10
  Container,
10
11
  DebouncedTextField,
11
12
  Dialog,
@@ -21,6 +22,8 @@ import {
21
22
  } from "@firecms/ui";
22
23
 
23
24
  import { Field, getIn, useFormex } from "@firecms/formex";
25
+ import { useCollectionEditorController } from "../../useCollectionEditorController";
26
+ import { LayoutModeSwitch } from "./LayoutModeSwitch";
24
27
 
25
28
  export function CollectionDetailsForm({
26
29
  isNewCollection,
@@ -28,7 +31,8 @@ export function CollectionDetailsForm({
28
31
  existingPaths,
29
32
  existingIds,
30
33
  groups,
31
- parentCollection
34
+ parentCollection,
35
+ children
32
36
  }: {
33
37
  isNewCollection: boolean,
34
38
  reservedGroups?: string[];
@@ -37,6 +41,7 @@ export function CollectionDetailsForm({
37
41
  groups: string[] | null;
38
42
  parentCollection?: EntityCollection;
39
43
  parentCollectionIds?: string[];
44
+ children?: React.ReactNode;
40
45
  }) {
41
46
 
42
47
  const groupRef = React.useRef<HTMLInputElement>(null);
@@ -51,9 +56,15 @@ export function CollectionDetailsForm({
51
56
  submitCount
52
57
  } = useFormex<EntityCollection>();
53
58
 
59
+ const collectionEditor = useCollectionEditorController();
60
+
54
61
  const [iconDialogOpen, setIconDialogOpen] = useState(false);
55
62
  const [advancedPanelExpanded, setAdvancedPanelExpanded] = useState(false);
56
63
 
64
+ const updateDatabaseId = (databaseId: string) => {
65
+ setFieldValue("databaseId", databaseId ?? undefined);
66
+ }
67
+
57
68
  const updateName = (name: string) => {
58
69
  setFieldValue("name", name);
59
70
 
@@ -80,6 +91,8 @@ export function CollectionDetailsForm({
80
91
  }
81
92
  }, [errors.id]);
82
93
 
94
+ const DatabaseField = collectionEditor.components?.DatabaseField ?? DefaultDatabaseField;
95
+
83
96
  const collectionIcon = <IconForView collectionOrView={values}/>;
84
97
 
85
98
  const groupOptions = groups?.filter((group) => !reservedGroups?.includes(group));
@@ -112,11 +125,15 @@ export function CollectionDetailsForm({
112
125
 
113
126
  <div>
114
127
  <div
115
- className="flex flex-row py-2 pt-3 items-center">
128
+ className="flex flex-row gap-2 py-2 pt-3 items-center">
116
129
  <Typography variant={!isNewCollection ? "h5" : "h4"} className={"flex-grow"}>
117
130
  {isNewCollection ? "New collection" : `${values?.name} collection`}
118
131
  </Typography>
119
- <Tooltip title={"Change icon"}>
132
+ <DatabaseField databaseId={values.databaseId}
133
+ onDatabaseIdUpdate={updateDatabaseId}/>
134
+
135
+ <Tooltip title={"Change icon"}
136
+ asChild={true}>
120
137
  <IconButton
121
138
  shape={"square"}
122
139
  onClick={() => setIconDialogOpen(true)}>
@@ -139,14 +156,15 @@ export function CollectionDetailsForm({
139
156
  value={values.name ?? ""}
140
157
  onChange={(e: any) => updateName(e.target.value)}
141
158
  label={"Name"}
159
+ autoFocus={true}
142
160
  required
143
161
  error={showErrors && Boolean(errors.name)}/>
144
162
  <FieldCaption error={touched.name && Boolean(errors.name)}>
145
- {touched.name && Boolean(errors.name) ? errors.name : "Name of in this collection, usually a plural name (e.g. Products)"}
163
+ {touched.name && Boolean(errors.name) ? errors.name : "Name of this collection, usually a plural name (e.g. Products)"}
146
164
  </FieldCaption>
147
165
  </div>
148
166
 
149
- <div className={cn("col-span-12 ", isSubcollection ? "" : "sm:col-span-8")}>
167
+ <div className={cls("col-span-12 ", isSubcollection ? "" : "sm:col-span-8")}>
150
168
  <Field name={"path"}
151
169
  as={DebouncedTextField}
152
170
  label={"Path"}
@@ -190,16 +208,23 @@ export function CollectionDetailsForm({
190
208
  })}
191
209
  </Autocomplete>
192
210
  <FieldCaption>
193
- {showErrors && Boolean(errors.group) ? errors.group : "Group of the collection"}
211
+ {showErrors && Boolean(errors.group) ? errors.group : "Group in the home page"}
194
212
  </FieldCaption>
213
+
214
+
195
215
  </div>}
196
216
 
197
- <div className={"col-span-12"}>
217
+ <LayoutModeSwitch
218
+ className={"col-span-12"}
219
+ value={values.openEntityMode ?? "side_panel"}
220
+ onChange={(value) => setFieldValue("openEntityMode", value)}/>
221
+
222
+ <div className={"col-span-12 mt-8"}>
198
223
  <ExpandablePanel
199
224
  expanded={advancedPanelExpanded}
200
225
  onExpandedChange={setAdvancedPanelExpanded}
201
226
  title={
202
- <div className="flex flex-row text-gray-500">
227
+ <div className="flex flex-row text-surface-500">
203
228
  <SettingsIcon/>
204
229
  <Typography variant={"subtitle2"}
205
230
  className="ml-2">
@@ -216,7 +241,7 @@ export function CollectionDetailsForm({
216
241
  label={"Collection id"}
217
242
  error={showErrors && Boolean(errors.id)}/>
218
243
  <FieldCaption error={touched.id && Boolean(errors.id)}>
219
- {touched.id && Boolean(errors.id) ? errors.id : "This id identifies this collection"}
244
+ {touched.id && Boolean(errors.id) ? errors.id : "This id identifies this collection. Typically the same as the path."}
220
245
  </FieldCaption>
221
246
  </div>
222
247
 
@@ -235,6 +260,35 @@ export function CollectionDetailsForm({
235
260
  {showErrors && Boolean(errors.singularName) ? errors.singularName : "Optionally define a singular name for your entities"}
236
261
  </FieldCaption>
237
262
  </div>
263
+ <div className={"col-span-12"}>
264
+ <TextField
265
+ error={showErrors && Boolean(errors.sideDialogWidth)}
266
+ name={"sideDialogWidth"}
267
+ type={"number"}
268
+ aria-describedby={"sideDialogWidth-helper"}
269
+ onChange={(e) => {
270
+ setFieldTouched("sideDialogWidth", true);
271
+ const value = e.target.value;
272
+ if (!value) {
273
+ setFieldValue("sideDialogWidth", null);
274
+ } else if (!isNaN(Number(value))) {
275
+ setFieldValue("sideDialogWidth", Number(value));
276
+ }
277
+ }}
278
+ endAdornment={<IconButton
279
+ size={"small"}
280
+ onClick={() => {
281
+ setFieldValue("sideDialogWidth", null);
282
+ }}
283
+ disabled={!values.sideDialogWidth}>
284
+ <CloseIcon size={"small"}/>
285
+ </IconButton>}
286
+ value={values.sideDialogWidth ?? ""}
287
+ label={"Side dialog width"}/>
288
+ <FieldCaption error={showErrors && Boolean(errors.singularName)}>
289
+ {showErrors && Boolean(errors.singularName) ? errors.singularName : "Optionally define the width (in pixels) of entities side dialog. Default is 768px"}
290
+ </FieldCaption>
291
+ </div>
238
292
  <div className={"col-span-12"}>
239
293
  <TextField
240
294
  error={showErrors && Boolean(errors.description)}
@@ -254,6 +308,8 @@ export function CollectionDetailsForm({
254
308
  <div className={"col-span-12"}>
255
309
  <Select
256
310
  name="defaultSize"
311
+ size={"large"}
312
+ fullWidth={true}
257
313
  label="Default row size"
258
314
  position={"item-aligned"}
259
315
  onChange={handleChange}
@@ -272,8 +328,10 @@ export function CollectionDetailsForm({
272
328
  <div className={"col-span-12"}>
273
329
  <Select
274
330
  name="customId"
275
- label="Data IDs generation"
331
+ label="Document IDs generation"
276
332
  position={"item-aligned"}
333
+ size={"large"}
334
+ fullWidth={true}
277
335
  disabled={customIdValue === "code_defined"}
278
336
  onValueChange={(v) => {
279
337
  if (v === "code_defined")
@@ -334,9 +392,13 @@ export function CollectionDetailsForm({
334
392
  for large collections, as it may incur in performance and cost issues.
335
393
  </FieldCaption>
336
394
  </div>
395
+
396
+
337
397
  </div>
338
398
  </ExpandablePanel>
339
399
 
400
+ {children}
401
+
340
402
  </div>
341
403
 
342
404
  </div>
@@ -363,3 +425,20 @@ export function CollectionDetailsForm({
363
425
  </div>
364
426
  );
365
427
  }
428
+
429
+ function DefaultDatabaseField({
430
+ databaseId,
431
+ onDatabaseIdUpdate
432
+ }: { databaseId?: string, onDatabaseIdUpdate: (databaseId: string) => void }) {
433
+
434
+ return <Tooltip title={"Database ID"}
435
+ side={"top"}
436
+ align={"start"}>
437
+ <TextField size={"small"}
438
+ invisible={true}
439
+ inputClassName={"text-end"}
440
+ value={databaseId ?? ""}
441
+ onChange={(e: any) => onDatabaseIdUpdate(e.target.value)}
442
+ placeholder={"(default)"}></TextField>
443
+ </Tooltip>
444
+ }
@@ -1,7 +1,9 @@
1
1
  import * as React from "react";
2
- import { useCallback, useEffect, useRef, useState } from "react";
2
+ import { useEffect, useRef, useState } from "react";
3
3
  import {
4
4
  CircularProgressCenter,
5
+ ConfirmationDialog,
6
+ Entity,
5
7
  EntityCollection,
6
8
  ErrorView,
7
9
  isPropertyBuilder,
@@ -25,17 +27,19 @@ import {
25
27
  import {
26
28
  ArrowBackIcon,
27
29
  Button,
28
- cn,
30
+ CheckIcon,
31
+ cls,
29
32
  coolIconKeys,
30
33
  defaultBorderMixin,
31
34
  Dialog,
32
35
  DialogActions,
33
36
  DialogContent,
34
- DoneIcon,
37
+ DialogTitle,
35
38
  IconButton,
36
39
  LoadingButton,
37
40
  Tab,
38
- Tabs
41
+ Tabs,
42
+ Typography
39
43
  } from "@firecms/ui";
40
44
  import { YupSchema } from "./CollectionYupValidation";
41
45
  import { CollectionDetailsForm } from "./CollectionDetailsForm";
@@ -76,9 +80,10 @@ export interface CollectionEditorDialogProps {
76
80
  icon: React.ReactNode
77
81
  };
78
82
  pathSuggestions?: (path?: string) => Promise<string[]>;
79
- getUser: (uid: string) => User | null;
83
+ getUser?: (uid: string) => User | null;
80
84
  getData?: (path: string, parentPaths: string[]) => Promise<object[]>;
81
85
  parentCollection?: PersistedCollection;
86
+ existingEntities?: Entity<any>[];
82
87
  }
83
88
 
84
89
  export function CollectionEditorDialog(props: CollectionEditorDialogProps) {
@@ -88,13 +93,13 @@ export function CollectionEditorDialog(props: CollectionEditorDialogProps) {
88
93
  const [formDirty, setFormDirty] = React.useState<boolean>(false);
89
94
  const [unsavedChangesDialogOpen, setUnsavedChangesDialogOpen] = React.useState<boolean>(false);
90
95
 
91
- const handleCancel = useCallback(() => {
96
+ const handleCancel = () => {
92
97
  if (!formDirty) {
93
98
  props.handleClose(undefined);
94
99
  } else {
95
100
  setUnsavedChangesDialogOpen(true);
96
101
  }
97
- }, [formDirty, props.handleClose]);
102
+ };
98
103
 
99
104
  useEffect(() => {
100
105
  if (!open) {
@@ -112,6 +117,7 @@ export function CollectionEditorDialog(props: CollectionEditorDialogProps) {
112
117
  maxWidth={"7xl"}
113
118
  onOpenChange={(open) => !open ? handleCancel() : undefined}
114
119
  >
120
+ <DialogTitle hidden>Collection editor</DialogTitle>
115
121
  {open && <CollectionEditor {...props}
116
122
  handleCancel={handleCancel}
117
123
  setFormDirty={setFormDirty}/>}
@@ -170,7 +176,8 @@ export function CollectionEditor(props: CollectionEditorDialogProps & {
170
176
  } catch (e) {
171
177
  console.error(e);
172
178
  }
173
- }, [navigation.getCollectionFromPaths, props.editedCollectionId, props.parentCollectionIds, navigation.initialised]);
179
+ }, [props.editedCollectionId, props.parentCollectionIds, navigation.initialised, navigation.getCollectionFromPaths]);
180
+
174
181
  if (!topLevelNavigation) {
175
182
  throw Error("Internal: Navigation not ready in collection editor");
176
183
  }
@@ -243,7 +250,8 @@ function CollectionEditorInternal<M extends Record<string, any>>({
243
250
  setCollection,
244
251
  initialValues,
245
252
  propertyConfigs,
246
- groups
253
+ groups,
254
+ existingEntities
247
255
  }: CollectionEditorDialogProps & {
248
256
  handleCancel: () => void,
249
257
  setFormDirty: (dirty: boolean) => void,
@@ -272,6 +280,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
272
280
 
273
281
  const saveCollection = (updatedCollection: PersistedCollection<M>): Promise<boolean> => {
274
282
  const id = updatedCollection.id || updatedCollection.path;
283
+
275
284
  return configController.saveCollection({
276
285
  id,
277
286
  collectionData: updatedCollection,
@@ -293,7 +302,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
293
302
  });
294
303
  };
295
304
 
296
- const setNextMode = useCallback(() => {
305
+ const setNextMode = () => {
297
306
  if (currentView === "details") {
298
307
  if (importConfig.inUse) {
299
308
  setCurrentView("import_data_saving");
@@ -314,14 +323,14 @@ function CollectionEditorInternal<M extends Record<string, any>>({
314
323
  setCurrentView("details");
315
324
  }
316
325
 
317
- }, [currentView, importConfig.inUse, extraView]);
326
+ };
318
327
 
319
- const doCollectionInference = useCallback((collection: PersistedCollection<any>) => {
328
+ const doCollectionInference = (collection: PersistedCollection<any>) => {
320
329
  if (!collectionInference) return undefined;
321
- return collectionInference?.(collection.path, collection.collectionGroup ?? false, parentCollectionIds ?? []);
322
- }, [collectionInference, parentCollectionIds]);
330
+ return collectionInference?.(collection.path, collection.collectionGroup ?? false, parentPaths ?? []);
331
+ };
323
332
 
324
- const inferCollectionFromData = useCallback(async (newCollection: PersistedCollection<M>) => {
333
+ const inferCollectionFromData = async (newCollection: PersistedCollection<M>) => {
325
334
 
326
335
  try {
327
336
  if (!doCollectionInference) {
@@ -365,15 +374,15 @@ function CollectionEditorInternal<M extends Record<string, any>>({
365
374
  });
366
375
  return newCollection;
367
376
  }
368
- }, [parentCollectionIds, doCollectionInference]);
377
+ };
369
378
 
370
379
  const onSubmit = (newCollectionState: PersistedCollection<M>, formexController: FormexController<PersistedCollection<M>>) => {
371
- console.log("Submitting collection", newCollectionState);
380
+ console.debug("Submitting collection", newCollectionState);
372
381
  try {
373
382
 
374
383
  if (!isNewCollection) {
375
384
  saveCollection(newCollectionState).then(() => {
376
- formexController.resetForm({ values: initialValues });
385
+ formexController.resetForm();
377
386
  handleClose(newCollectionState);
378
387
  });
379
388
  return;
@@ -462,7 +471,8 @@ function CollectionEditorInternal<M extends Record<string, any>>({
462
471
  const formController = useCreateFormex<PersistedCollection<M>>({
463
472
  initialValues,
464
473
  onSubmit,
465
- validation
474
+ validation,
475
+ debugId: "COLLECTION_EDITOR"
466
476
  });
467
477
 
468
478
  const {
@@ -479,26 +489,37 @@ function CollectionEditorInternal<M extends Record<string, any>>({
479
489
  const pathError = validatePath(path, isNewCollection, existingPaths, values.id);
480
490
 
481
491
  const parentPaths = !pathError && parentCollectionIds ? navigation.convertIdsToPaths(parentCollectionIds) : undefined;
482
- const resolvedPath = !pathError ? navigation.resolveAliasesFrom(updatedFullPath) : undefined;
483
- const getDataWithPath = resolvedPath && getData ? () => getData(resolvedPath, parentPaths ?? []) : undefined;
492
+ const resolvedPath = !pathError ? navigation.resolveIdsFrom(updatedFullPath) : undefined;
493
+ const getDataWithPath = resolvedPath && getData ? async () => {
494
+ const data = await getData(resolvedPath, parentPaths ?? []);
495
+ if (existingEntities) {
496
+ const existingData = existingEntities.map(e => e.values);
497
+ data.push(...existingData);
498
+ }
499
+ return data;
500
+ } : undefined;
484
501
 
485
502
  useEffect(() => {
486
503
  setFormDirty(dirty);
487
504
  }, [dirty]);
488
505
 
489
- function onImportDataSet(data: object[]) {
506
+ function onImportDataSet(data: object[], propertiesOrder?: string[]) {
490
507
  importConfig.setInUse(true);
491
508
  buildEntityPropertiesFromData(data, getInferenceType)
492
509
  .then((properties) => {
493
510
  const res = cleanPropertiesFromImport(properties);
494
511
 
495
- setFieldValue("properties", res.properties);
496
- setFieldValue("propertiesOrder", Object.keys(res.properties));
497
-
498
512
  importConfig.setIdColumn(res.idColumn);
499
513
  importConfig.setImportData(data);
500
514
  importConfig.setHeadersMapping(res.headersMapping);
515
+ const filteredHeadingsOrder = ((propertiesOrder ?? [])
516
+ .filter((key) => res.headersMapping[key]) as string[]) ?? Object.keys(res.properties);
517
+ importConfig.setHeadingsOrder(filteredHeadingsOrder);
501
518
  importConfig.setOriginProperties(res.properties);
519
+
520
+ const mappedHeadings = (propertiesOrder ?? []).map((key) => res.headersMapping[key]).filter(Boolean) as string[] ?? Object.keys(res.properties);
521
+ setFieldValue("properties", res.properties);
522
+ setFieldValue("propertiesOrder", mappedHeadings);
502
523
  });
503
524
  }
504
525
 
@@ -514,14 +535,30 @@ function CollectionEditorInternal<M extends Record<string, any>>({
514
535
  };
515
536
 
516
537
  const editable = collection?.editable === undefined || collection?.editable === true;
538
+ // @ts-ignore
539
+ const isMergedCollection = collection?.merged ?? false;
517
540
  const collectionEditable = editable || isNewCollection;
518
541
 
542
+ const [deleteRequested, setDeleteRequested] = useState(false);
543
+
544
+ const deleteCollection = () => {
545
+ if (!collection) return;
546
+ configController?.deleteCollection({ id: collection.id }).then(() => {
547
+ setDeleteRequested(false);
548
+ handleCancel();
549
+ snackbarController.open({
550
+ message: "Collection deleted",
551
+ type: "success"
552
+ });
553
+ });
554
+ };
555
+
519
556
  return <DialogContent fullHeight={true}>
520
557
  <Formex value={formController}>
521
558
 
522
559
  <>
523
560
  {!isNewCollection && <Tabs value={currentView}
524
- className={cn(defaultBorderMixin, "justify-end bg-gray-50 dark:bg-gray-950 border-b")}
561
+ innerClassName={cls(defaultBorderMixin, "px-4 h-14 w-full justify-end bg-surface-50 dark:bg-surface-950 border-b")}
525
562
  onValueChange={(v) => setCurrentView(v as EditorView)}>
526
563
  <Tab value={"details"}>
527
564
  Details
@@ -536,7 +573,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
536
573
 
537
574
  <form noValidate
538
575
  onSubmit={formController.handleSubmit}
539
- className={cn(
576
+ className={cls(
540
577
  isNewCollection ? "h-full" : "h-[calc(100%-48px)]",
541
578
  "flex-grow flex flex-col relative")}>
542
579
 
@@ -551,9 +588,10 @@ function CollectionEditorInternal<M extends Record<string, any>>({
551
588
  {currentView === "welcome" &&
552
589
  <CollectionEditorWelcomeView
553
590
  path={path}
554
- onContinue={(importData) => {
591
+ onContinue={(importData, propertiesOrder) => {
592
+ // console.log("Import data", importData, propertiesOrder)
555
593
  if (importData) {
556
- onImportDataSet(importData);
594
+ onImportDataSet(importData, propertiesOrder);
557
595
  setCurrentView("import_data_mapping");
558
596
  } else {
559
597
  setCurrentView("details");
@@ -594,7 +632,18 @@ function CollectionEditorInternal<M extends Record<string, any>>({
594
632
  groups={groups}
595
633
  parentCollectionIds={parentCollectionIds}
596
634
  parentCollection={parentCollection}
597
- isNewCollection={isNewCollection}/>}
635
+ isNewCollection={isNewCollection}>
636
+ {!isNewCollection && isMergedCollection && <div className={"flex flex-col gap-4 mt-8"}>
637
+ <Typography variant={"body2"} color={"secondary"}>This collection is defined in code.
638
+ The changes done in this editor will override the properties defined in code.
639
+ You can delete the overridden values to revert to the state defined in code.
640
+ </Typography>
641
+ <Button variant={"neutral"}
642
+ onClick={() => {
643
+ setDeleteRequested(true);
644
+ }}>Reset to code</Button>
645
+ </div>}
646
+ </CollectionDetailsForm>}
598
647
 
599
648
  {currentView === "subcollections" && collection &&
600
649
  <SubcollectionsEditTab
@@ -706,7 +755,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
706
755
  loading={isSubmitting}
707
756
  disabled={isSubmitting || (currentView === "details" && !validValues)}
708
757
  startIcon={currentView === "properties"
709
- ? <DoneIcon/>
758
+ ? <CheckIcon/>
710
759
  : undefined}
711
760
  >
712
761
  {currentView === "details" && "Next"}
@@ -728,12 +777,24 @@ function CollectionEditorInternal<M extends Record<string, any>>({
728
777
 
729
778
  </Formex>
730
779
 
780
+ <ConfirmationDialog
781
+ open={deleteRequested}
782
+ onAccept={deleteCollection}
783
+ onCancel={() => setDeleteRequested(false)}
784
+ title={<>Delete the stored config?</>}
785
+ body={<> This will <b>not
786
+ delete any data</b>, only
787
+ the stored config, and reset to the code state.</>}/>
788
+
731
789
  </DialogContent>
732
790
 
733
791
  }
734
792
 
735
793
  function applyPropertyConfigs<M extends Record<string, any> = any>(collection: PersistedCollection<M>, propertyConfigs: Record<string, PropertyConfig<any>>): PersistedCollection<M> {
736
- const { properties, ...rest } = collection;
794
+ const {
795
+ properties,
796
+ ...rest
797
+ } = collection;
737
798
  const propertiesResult: PropertiesOrBuilders<any> = {};
738
799
  if (properties) {
739
800
  Object.keys(properties).forEach((key) => {
@@ -741,7 +802,10 @@ function applyPropertyConfigs<M extends Record<string, any> = any>(collection: P
741
802
  });
742
803
  }
743
804
 
744
- return { ...rest, properties: propertiesResult };
805
+ return {
806
+ ...rest,
807
+ properties: propertiesResult
808
+ };
745
809
  }
746
810
 
747
811
  function applyPropertiesConfig(property: PropertyOrBuilder, propertyConfigs: Record<string, PropertyConfig<any>>) {
@@ -761,7 +825,10 @@ function applyPropertiesConfig(property: PropertyOrBuilder, propertyConfigs: Rec
761
825
  Object.keys(internalProperty.properties).forEach((key) => {
762
826
  properties[key] = applyPropertiesConfig(((internalProperty as MapProperty).properties as Properties)[key] as Property, propertyConfigs);
763
827
  });
764
- internalProperty = { ...internalProperty, properties };
828
+ internalProperty = {
829
+ ...internalProperty,
830
+ properties
831
+ };
765
832
  }
766
833
 
767
834
  }
@@ -1,6 +1,6 @@
1
1
  import React, { useEffect, useState } from "react";
2
2
  import { EntityCollection, unslugify, } from "@firecms/core";
3
- import { Button, Card, Chip, CircularProgress, cn, Container, Icon, Tooltip, Typography, } from "@firecms/ui";
3
+ import { Button, Card, Chip, CircularProgress, cls, Container, Icon, Tooltip, Typography, } from "@firecms/ui";
4
4
 
5
5
  import { productsCollectionTemplate } from "./templates/products_template";
6
6
  import { blogCollectionTemplate } from "./templates/blog_template";
@@ -19,7 +19,7 @@ export function CollectionEditorWelcomeView({
19
19
  path: string;
20
20
  pathSuggestions?: (path: string) => Promise<string[]>;
21
21
  parentCollection?: EntityCollection;
22
- onContinue: (importData?: object[]) => void;
22
+ onContinue: (importData?: object[], propertiesOrder?: string[]) => void;
23
23
  existingCollectionPaths?: string[];
24
24
  }) {
25
25
 
@@ -154,7 +154,7 @@ export function CollectionEditorWelcomeView({
154
154
  ● Create a collection from a file (csv, json, xls, xslx...)
155
155
  </Typography>
156
156
 
157
- <ImportFileUpload onDataAdded={(data) => onContinue(data)}/>
157
+ <ImportFileUpload onDataAdded={(data, propertiesOrder) => onContinue(data, propertiesOrder)}/>
158
158
 
159
159
  </div>}
160
160
 
@@ -185,14 +185,15 @@ export function TemplateButton({
185
185
  }) {
186
186
 
187
187
  return (
188
- <Tooltip title={subtitle}>
188
+ <Tooltip title={subtitle}
189
+ asChild={true}>
189
190
  <Card
190
191
  onClick={onClick}
191
- className={cn(
192
+ className={cls(
192
193
  "my-2 rounded-md border mx-0 p-6 px-4 focus:outline-none transition ease-in-out duration-150 flex flex-row gap-4 items-center",
193
- "text-gray-700 dark:text-slate-300",
194
+ "text-surface-700 dark:text-surface-accent-300",
194
195
  "hover:border-primary-dark hover:text-primary-dark dark:hover:text-primary focus:ring-primary hover:ring-1 hover:ring-primary",
195
- "border-gray-400 dark:border-gray-600 "
196
+ "border-surface-400 dark:border-surface-600 "
196
197
  )}
197
198
  >
198
199
  {icon}