@firecms/collection_editor 3.0.0-canary.24 → 3.0.0-canary.241

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 (76) hide show
  1. package/LICENSE +114 -21
  2. package/README.md +165 -1
  3. package/dist/ConfigControllerProvider.d.ts +1 -2
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.es.js +10109 -4774
  6. package/dist/index.es.js.map +1 -1
  7. package/dist/index.umd.js +10795 -3
  8. package/dist/index.umd.js.map +1 -1
  9. package/dist/types/collection_editor_controller.d.ts +3 -2
  10. package/dist/types/collection_inference.d.ts +1 -1
  11. package/dist/types/config_permissions.d.ts +2 -2
  12. package/dist/types/persisted_collection.d.ts +1 -1
  13. package/dist/ui/CollectionViewHeaderAction.d.ts +3 -2
  14. package/dist/ui/EditorCollectionActionStart.d.ts +2 -0
  15. package/dist/ui/PropertyAddColumnComponent.d.ts +3 -1
  16. package/dist/ui/collection_editor/CollectionDetailsForm.d.ts +3 -1
  17. package/dist/ui/collection_editor/CollectionEditorDialog.d.ts +3 -2
  18. package/dist/ui/collection_editor/CollectionEditorWelcomeView.d.ts +2 -2
  19. package/dist/ui/collection_editor/CollectionPropertiesEditorForm.d.ts +1 -1
  20. package/dist/ui/collection_editor/LayoutModeSwitch.d.ts +5 -0
  21. package/dist/ui/collection_editor/PropertyEditView.d.ts +8 -0
  22. package/dist/ui/collection_editor/PropertyTree.d.ts +9 -9
  23. package/dist/ui/collection_editor/SubcollectionsEditTab.d.ts +1 -1
  24. package/dist/ui/collection_editor/import/CollectionEditorImportMapping.d.ts +7 -0
  25. package/dist/ui/collection_editor/properties/MarkdownPropertyField.d.ts +4 -0
  26. package/dist/ui/collection_editor/properties/StringPropertyField.d.ts +1 -1
  27. package/dist/useCollectionEditorPlugin.d.ts +8 -11
  28. package/dist/utils/collections.d.ts +6 -0
  29. package/package.json +24 -35
  30. package/src/ConfigControllerProvider.tsx +64 -66
  31. package/src/index.ts +1 -0
  32. package/src/types/collection_editor_controller.tsx +6 -5
  33. package/src/types/collection_inference.ts +1 -1
  34. package/src/types/config_permissions.ts +1 -1
  35. package/src/types/persisted_collection.ts +2 -3
  36. package/src/ui/CollectionViewHeaderAction.tsx +10 -5
  37. package/src/ui/EditorCollectionAction.tsx +10 -63
  38. package/src/ui/EditorCollectionActionStart.tsx +88 -0
  39. package/src/ui/HomePageEditorCollectionAction.tsx +19 -13
  40. package/src/ui/NewCollectionButton.tsx +1 -1
  41. package/src/ui/NewCollectionCard.tsx +3 -3
  42. package/src/ui/PropertyAddColumnComponent.tsx +11 -6
  43. package/src/ui/collection_editor/CollectionDetailsForm.tsx +112 -18
  44. package/src/ui/collection_editor/CollectionEditorDialog.tsx +101 -34
  45. package/src/ui/collection_editor/CollectionEditorWelcomeView.tsx +13 -28
  46. package/src/ui/collection_editor/CollectionPropertiesEditorForm.tsx +41 -39
  47. package/src/ui/collection_editor/EntityCustomViewsSelectDialog.tsx +6 -5
  48. package/src/ui/collection_editor/EnumForm.tsx +11 -7
  49. package/src/ui/collection_editor/GetCodeDialog.tsx +56 -26
  50. package/src/ui/collection_editor/LayoutModeSwitch.tsx +54 -0
  51. package/src/ui/collection_editor/PropertyEditView.tsx +257 -79
  52. package/src/ui/collection_editor/PropertyFieldPreview.tsx +7 -10
  53. package/src/ui/collection_editor/PropertyTree.tsx +9 -7
  54. package/src/ui/collection_editor/SubcollectionsEditTab.tsx +26 -19
  55. package/src/ui/collection_editor/UnsavedChangesDialog.tsx +3 -5
  56. package/src/ui/collection_editor/import/CollectionEditorImportDataPreview.tsx +33 -9
  57. package/src/ui/collection_editor/import/CollectionEditorImportMapping.tsx +42 -9
  58. package/src/ui/collection_editor/properties/BlockPropertyField.tsx +32 -20
  59. package/src/ui/collection_editor/properties/DateTimePropertyField.tsx +54 -47
  60. package/src/ui/collection_editor/properties/EnumPropertyField.tsx +3 -1
  61. package/src/ui/collection_editor/properties/MapPropertyField.tsx +7 -6
  62. package/src/ui/collection_editor/properties/MarkdownPropertyField.tsx +139 -0
  63. package/src/ui/collection_editor/properties/ReferencePropertyField.tsx +2 -0
  64. package/src/ui/collection_editor/properties/RepeatPropertyField.tsx +0 -1
  65. package/src/ui/collection_editor/properties/StoragePropertyField.tsx +34 -19
  66. package/src/ui/collection_editor/properties/StringPropertyField.tsx +1 -10
  67. package/src/ui/collection_editor/properties/UrlPropertyField.tsx +1 -0
  68. package/src/ui/collection_editor/properties/validation/ValidationPanel.tsx +2 -2
  69. package/src/ui/collection_editor/templates/pages_template.ts +1 -6
  70. package/src/ui/collection_editor/utils/strings.ts +13 -6
  71. package/src/useCollectionEditorPlugin.tsx +32 -32
  72. package/src/utils/collections.ts +37 -0
  73. package/dist/ui/RootCollectionSuggestions.d.ts +0 -3
  74. package/dist/ui/collection_editor/PropertySelectItem.d.ts +0 -8
  75. package/src/ui/RootCollectionSuggestions.tsx +0 -63
  76. 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,42 @@ 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
 
216
+ <LayoutModeSwitch
217
+ className={"col-span-12"}
218
+ value={values.openEntityMode ?? "side_panel"}
219
+ onChange={(value) => setFieldValue("openEntityMode", value)}/>
220
+
197
221
  <div className={"col-span-12"}>
222
+ <BooleanSwitchWithLabel
223
+ position={"start"}
224
+ size={"large"}
225
+ allowIndeterminate={true}
226
+ label={values.history === null ? "Document history revisions enabled if enabled globally" : (
227
+ values.history ? "Document history revisions ENABLED" : "Document history revisions NOT enabled"
228
+ )}
229
+ onValueChange={(v) => setFieldValue("history", v)}
230
+ value={values.history ?? null}
231
+ />
232
+ <FieldCaption>
233
+ When enabled, each document in this collection will have a history of changes.
234
+ This is useful for auditing purposes. The data is stored in a subcollection of the document
235
+ in your database, called <b>__history</b>.
236
+ </FieldCaption>
237
+ </div>
238
+
239
+
240
+ <div className={"col-span-12 mt-8"}>
198
241
  <ExpandablePanel
199
242
  expanded={advancedPanelExpanded}
200
243
  onExpandedChange={setAdvancedPanelExpanded}
201
244
  title={
202
- <div className="flex flex-row text-gray-500">
245
+ <div className="flex flex-row text-surface-500">
203
246
  <SettingsIcon/>
204
247
  <Typography variant={"subtitle2"}
205
248
  className="ml-2">
@@ -216,7 +259,7 @@ export function CollectionDetailsForm({
216
259
  label={"Collection id"}
217
260
  error={showErrors && Boolean(errors.id)}/>
218
261
  <FieldCaption error={touched.id && Boolean(errors.id)}>
219
- {touched.id && Boolean(errors.id) ? errors.id : "This id identifies this collection"}
262
+ {touched.id && Boolean(errors.id) ? errors.id : "This id identifies this collection. Typically the same as the path."}
220
263
  </FieldCaption>
221
264
  </div>
222
265
 
@@ -235,6 +278,35 @@ export function CollectionDetailsForm({
235
278
  {showErrors && Boolean(errors.singularName) ? errors.singularName : "Optionally define a singular name for your entities"}
236
279
  </FieldCaption>
237
280
  </div>
281
+ <div className={"col-span-12"}>
282
+ <TextField
283
+ error={showErrors && Boolean(errors.sideDialogWidth)}
284
+ name={"sideDialogWidth"}
285
+ type={"number"}
286
+ aria-describedby={"sideDialogWidth-helper"}
287
+ onChange={(e) => {
288
+ setFieldTouched("sideDialogWidth", true);
289
+ const value = e.target.value;
290
+ if (!value) {
291
+ setFieldValue("sideDialogWidth", null);
292
+ } else if (!isNaN(Number(value))) {
293
+ setFieldValue("sideDialogWidth", Number(value));
294
+ }
295
+ }}
296
+ endAdornment={<IconButton
297
+ size={"small"}
298
+ onClick={() => {
299
+ setFieldValue("sideDialogWidth", null);
300
+ }}
301
+ disabled={!values.sideDialogWidth}>
302
+ <CloseIcon size={"small"}/>
303
+ </IconButton>}
304
+ value={values.sideDialogWidth ?? ""}
305
+ label={"Side dialog width"}/>
306
+ <FieldCaption error={showErrors && Boolean(errors.singularName)}>
307
+ {showErrors && Boolean(errors.singularName) ? errors.singularName : "Optionally define the width (in pixels) of entities side dialog. Default is 768px"}
308
+ </FieldCaption>
309
+ </div>
238
310
  <div className={"col-span-12"}>
239
311
  <TextField
240
312
  error={showErrors && Boolean(errors.description)}
@@ -242,7 +314,7 @@ export function CollectionDetailsForm({
242
314
  value={values.description ?? ""}
243
315
  onChange={handleChange}
244
316
  multiline
245
- rows={2}
317
+ minRows={2}
246
318
  aria-describedby="description-helper-text"
247
319
  label="Description"
248
320
  />
@@ -254,6 +326,8 @@ export function CollectionDetailsForm({
254
326
  <div className={"col-span-12"}>
255
327
  <Select
256
328
  name="defaultSize"
329
+ size={"large"}
330
+ fullWidth={true}
257
331
  label="Default row size"
258
332
  position={"item-aligned"}
259
333
  onChange={handleChange}
@@ -272,18 +346,15 @@ export function CollectionDetailsForm({
272
346
  <div className={"col-span-12"}>
273
347
  <Select
274
348
  name="customId"
275
- label="Data IDs generation"
349
+ label="Document IDs generation"
276
350
  position={"item-aligned"}
351
+ size={"large"}
352
+ fullWidth={true}
277
353
  disabled={customIdValue === "code_defined"}
278
354
  onValueChange={(v) => {
279
355
  if (v === "code_defined")
280
356
  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");
357
+ setFieldValue("customId", v);
287
358
  }}
288
359
  value={customIdValue ?? ""}
289
360
  renderValue={(value: any) => {
@@ -308,9 +379,10 @@ export function CollectionDetailsForm({
308
379
  </SelectItem>
309
380
  </Select>
310
381
  </div>
311
- <div className={"col-span-12"}>
382
+ <div className={"col-span-12 mt-4"}>
312
383
  <BooleanSwitchWithLabel
313
384
  position={"start"}
385
+ size={"large"}
314
386
  label="Collection group"
315
387
  onValueChange={(v) => setFieldValue("collectionGroup", v)}
316
388
  value={values.collectionGroup ?? false}
@@ -324,6 +396,7 @@ export function CollectionDetailsForm({
324
396
  <div className={"col-span-12"}>
325
397
  <BooleanSwitchWithLabel
326
398
  position={"start"}
399
+ size={"large"}
327
400
  label="Enable text search for this collection"
328
401
  onValueChange={(v) => setFieldValue("textSearchEnabled", v)}
329
402
  value={values.textSearchEnabled ?? false}
@@ -334,9 +407,13 @@ export function CollectionDetailsForm({
334
407
  for large collections, as it may incur in performance and cost issues.
335
408
  </FieldCaption>
336
409
  </div>
410
+
411
+
337
412
  </div>
338
413
  </ExpandablePanel>
339
414
 
415
+ {children}
416
+
340
417
  </div>
341
418
 
342
419
  </div>
@@ -363,3 +440,20 @@ export function CollectionDetailsForm({
363
440
  </div>
364
441
  );
365
442
  }
443
+
444
+ function DefaultDatabaseField({
445
+ databaseId,
446
+ onDatabaseIdUpdate
447
+ }: { databaseId?: string, onDatabaseIdUpdate: (databaseId: string) => void }) {
448
+
449
+ return <Tooltip title={"Database ID"}
450
+ side={"top"}
451
+ align={"start"}>
452
+ <TextField size={"small"}
453
+ invisible={true}
454
+ inputClassName={"text-end"}
455
+ value={databaseId ?? ""}
456
+ onChange={(e: any) => onDatabaseIdUpdate(e.target.value)}
457
+ placeholder={"(default)"}></TextField>
458
+ </Tooltip>
459
+ }
@@ -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
  }