@firecms/collection_editor 3.0.0-canary.23 → 3.0.0-canary.232

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