@firecms/collection_editor 3.0.0-canary.21 → 3.0.0-canary.211

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 +2 -2
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.es.js +10060 -4770
  5. package/dist/index.es.js.map +1 -1
  6. package/dist/index.umd.js +10750 -3
  7. package/dist/index.umd.js.map +1 -1
  8. package/dist/types/collection_editor_controller.d.ts +4 -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 +8 -11
  27. package/dist/utils/collections.d.ts +6 -0
  28. package/package.json +24 -35
  29. package/src/ConfigControllerProvider.tsx +67 -64
  30. package/src/index.ts +1 -0
  31. package/src/types/collection_editor_controller.tsx +7 -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 +89 -12
  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 +37 -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 -79
  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 +33 -32
  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
@@ -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
 
@@ -112,11 +123,15 @@ export function CollectionDetailsForm({
112
123
 
113
124
  <div>
114
125
  <div
115
- className="flex flex-row py-2 pt-3 items-center">
126
+ className="flex flex-row gap-2 py-2 pt-3 items-center">
116
127
  <Typography variant={!isNewCollection ? "h5" : "h4"} className={"flex-grow"}>
117
128
  {isNewCollection ? "New collection" : `${values?.name} collection`}
118
129
  </Typography>
119
- <Tooltip title={"Change icon"}>
130
+ <DefaultDatabaseField databaseId={values.databaseId}
131
+ onDatabaseIdUpdate={updateDatabaseId}/>
132
+
133
+ <Tooltip title={"Change icon"}
134
+ asChild={true}>
120
135
  <IconButton
121
136
  shape={"square"}
122
137
  onClick={() => setIconDialogOpen(true)}>
@@ -139,14 +154,15 @@ export function CollectionDetailsForm({
139
154
  value={values.name ?? ""}
140
155
  onChange={(e: any) => updateName(e.target.value)}
141
156
  label={"Name"}
157
+ autoFocus={true}
142
158
  required
143
159
  error={showErrors && Boolean(errors.name)}/>
144
160
  <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)"}
161
+ {touched.name && Boolean(errors.name) ? errors.name : "Name of this collection, usually a plural name (e.g. Products)"}
146
162
  </FieldCaption>
147
163
  </div>
148
164
 
149
- <div className={cn("col-span-12 ", isSubcollection ? "" : "sm:col-span-8")}>
165
+ <div className={cls("col-span-12 ", isSubcollection ? "" : "sm:col-span-8")}>
150
166
  <Field name={"path"}
151
167
  as={DebouncedTextField}
152
168
  label={"Path"}
@@ -190,16 +206,23 @@ export function CollectionDetailsForm({
190
206
  })}
191
207
  </Autocomplete>
192
208
  <FieldCaption>
193
- {showErrors && Boolean(errors.group) ? errors.group : "Group of the collection"}
209
+ {showErrors && Boolean(errors.group) ? errors.group : "Group in the home page"}
194
210
  </FieldCaption>
211
+
212
+
195
213
  </div>}
196
214
 
197
- <div className={"col-span-12"}>
215
+ <LayoutModeSwitch
216
+ className={"col-span-12"}
217
+ value={values.openEntityMode ?? "side_panel"}
218
+ onChange={(value) => setFieldValue("openEntityMode", value)}/>
219
+
220
+ <div className={"col-span-12 mt-8"}>
198
221
  <ExpandablePanel
199
222
  expanded={advancedPanelExpanded}
200
223
  onExpandedChange={setAdvancedPanelExpanded}
201
224
  title={
202
- <div className="flex flex-row text-gray-500">
225
+ <div className="flex flex-row text-surface-500">
203
226
  <SettingsIcon/>
204
227
  <Typography variant={"subtitle2"}
205
228
  className="ml-2">
@@ -216,7 +239,7 @@ export function CollectionDetailsForm({
216
239
  label={"Collection id"}
217
240
  error={showErrors && Boolean(errors.id)}/>
218
241
  <FieldCaption error={touched.id && Boolean(errors.id)}>
219
- {touched.id && Boolean(errors.id) ? errors.id : "This id identifies this collection"}
242
+ {touched.id && Boolean(errors.id) ? errors.id : "This id identifies this collection. Typically the same as the path."}
220
243
  </FieldCaption>
221
244
  </div>
222
245
 
@@ -235,6 +258,35 @@ export function CollectionDetailsForm({
235
258
  {showErrors && Boolean(errors.singularName) ? errors.singularName : "Optionally define a singular name for your entities"}
236
259
  </FieldCaption>
237
260
  </div>
261
+ <div className={"col-span-12"}>
262
+ <TextField
263
+ error={showErrors && Boolean(errors.sideDialogWidth)}
264
+ name={"sideDialogWidth"}
265
+ type={"number"}
266
+ aria-describedby={"sideDialogWidth-helper"}
267
+ onChange={(e) => {
268
+ setFieldTouched("sideDialogWidth", true);
269
+ const value = e.target.value;
270
+ if (!value) {
271
+ setFieldValue("sideDialogWidth", null);
272
+ } else if (!isNaN(Number(value))) {
273
+ setFieldValue("sideDialogWidth", Number(value));
274
+ }
275
+ }}
276
+ endAdornment={<IconButton
277
+ size={"small"}
278
+ onClick={() => {
279
+ setFieldValue("sideDialogWidth", null);
280
+ }}
281
+ disabled={!values.sideDialogWidth}>
282
+ <CloseIcon size={"small"}/>
283
+ </IconButton>}
284
+ value={values.sideDialogWidth ?? ""}
285
+ label={"Side dialog width"}/>
286
+ <FieldCaption error={showErrors && Boolean(errors.singularName)}>
287
+ {showErrors && Boolean(errors.singularName) ? errors.singularName : "Optionally define the width (in pixels) of entities side dialog. Default is 768px"}
288
+ </FieldCaption>
289
+ </div>
238
290
  <div className={"col-span-12"}>
239
291
  <TextField
240
292
  error={showErrors && Boolean(errors.description)}
@@ -242,7 +294,7 @@ export function CollectionDetailsForm({
242
294
  value={values.description ?? ""}
243
295
  onChange={handleChange}
244
296
  multiline
245
- rows={2}
297
+ minRows={2}
246
298
  aria-describedby="description-helper-text"
247
299
  label="Description"
248
300
  />
@@ -254,6 +306,8 @@ export function CollectionDetailsForm({
254
306
  <div className={"col-span-12"}>
255
307
  <Select
256
308
  name="defaultSize"
309
+ size={"large"}
310
+ fullWidth={true}
257
311
  label="Default row size"
258
312
  position={"item-aligned"}
259
313
  onChange={handleChange}
@@ -272,8 +326,10 @@ export function CollectionDetailsForm({
272
326
  <div className={"col-span-12"}>
273
327
  <Select
274
328
  name="customId"
275
- label="Data IDs generation"
329
+ label="Document IDs generation"
276
330
  position={"item-aligned"}
331
+ size={"large"}
332
+ fullWidth={true}
277
333
  disabled={customIdValue === "code_defined"}
278
334
  onValueChange={(v) => {
279
335
  if (v === "code_defined")
@@ -334,9 +390,13 @@ export function CollectionDetailsForm({
334
390
  for large collections, as it may incur in performance and cost issues.
335
391
  </FieldCaption>
336
392
  </div>
393
+
394
+
337
395
  </div>
338
396
  </ExpandablePanel>
339
397
 
398
+ {children}
399
+
340
400
  </div>
341
401
 
342
402
  </div>
@@ -363,3 +423,20 @@ export function CollectionDetailsForm({
363
423
  </div>
364
424
  );
365
425
  }
426
+
427
+ function DefaultDatabaseField({
428
+ databaseId,
429
+ onDatabaseIdUpdate
430
+ }: { databaseId?: string, onDatabaseIdUpdate: (databaseId: string) => void }) {
431
+
432
+ return <Tooltip title={"Database ID"}
433
+ side={"top"}
434
+ align={"start"}>
435
+ <TextField size={"small"}
436
+ invisible={true}
437
+ inputClassName={"text-end"}
438
+ value={databaseId ?? ""}
439
+ onChange={(e: any) => onDatabaseIdUpdate(e.target.value)}
440
+ placeholder={"(default)"}></TextField>
441
+ </Tooltip>
442
+ }
@@ -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}