@firecms/collection_editor 3.0.1 → 3.1.0-canary.24c8270

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 (90) hide show
  1. package/dist/ConfigControllerProvider.d.ts +6 -0
  2. package/dist/api/generateCollectionApi.d.ts +71 -0
  3. package/dist/api/index.d.ts +1 -0
  4. package/dist/index.d.ts +5 -1
  5. package/dist/index.es.js +9466 -5588
  6. package/dist/index.es.js.map +1 -1
  7. package/dist/index.umd.js +9461 -5583
  8. package/dist/index.umd.js.map +1 -1
  9. package/dist/types/collection_editor_controller.d.ts +14 -0
  10. package/dist/types/collection_inference.d.ts +8 -2
  11. package/dist/types/config_controller.d.ts +23 -2
  12. package/dist/ui/AddKanbanColumnAction.d.ts +11 -0
  13. package/dist/ui/KanbanSetupAction.d.ts +10 -0
  14. package/dist/ui/collection_editor/AICollectionGeneratorPopover.d.ts +37 -0
  15. package/dist/ui/collection_editor/AIModifiedPathsContext.d.ts +20 -0
  16. package/dist/ui/collection_editor/CollectionDetailsForm.d.ts +2 -3
  17. package/dist/ui/collection_editor/CollectionEditorDialog.d.ts +24 -0
  18. package/dist/ui/collection_editor/CollectionEditorWelcomeView.d.ts +4 -1
  19. package/dist/ui/collection_editor/CollectionJsonImportDialog.d.ts +7 -0
  20. package/dist/ui/collection_editor/CollectionYupValidation.d.ts +9 -13
  21. package/dist/ui/collection_editor/DisplaySettingsForm.d.ts +3 -0
  22. package/dist/ui/collection_editor/EntityActionsEditTab.d.ts +2 -1
  23. package/dist/ui/collection_editor/ExtendSettingsForm.d.ts +14 -0
  24. package/dist/ui/collection_editor/GeneralSettingsForm.d.ts +7 -0
  25. package/dist/ui/collection_editor/KanbanConfigSection.d.ts +4 -0
  26. package/dist/ui/collection_editor/PropertyEditView.d.ts +6 -1
  27. package/dist/ui/collection_editor/PropertyTree.d.ts +2 -1
  28. package/dist/ui/collection_editor/SubcollectionsEditTab.d.ts +2 -1
  29. package/dist/ui/collection_editor/ViewModeSwitch.d.ts +6 -0
  30. package/dist/ui/collection_editor/properties/EnumPropertyField.d.ts +2 -1
  31. package/dist/ui/collection_editor/properties/conditions/ConditionsEditor.d.ts +10 -0
  32. package/dist/ui/collection_editor/properties/conditions/ConditionsPanel.d.ts +2 -0
  33. package/dist/ui/collection_editor/properties/conditions/EnumConditionsEditor.d.ts +6 -0
  34. package/dist/ui/collection_editor/properties/conditions/index.d.ts +6 -0
  35. package/dist/ui/collection_editor/properties/conditions/property_paths.d.ts +19 -0
  36. package/dist/useCollectionEditorPlugin.d.ts +7 -1
  37. package/dist/utils/validateCollectionJson.d.ts +22 -0
  38. package/package.json +15 -15
  39. package/src/ConfigControllerProvider.tsx +82 -47
  40. package/src/api/generateCollectionApi.ts +119 -0
  41. package/src/api/index.ts +1 -0
  42. package/src/index.ts +28 -1
  43. package/src/types/collection_editor_controller.tsx +16 -3
  44. package/src/types/collection_inference.ts +15 -2
  45. package/src/types/config_controller.tsx +27 -2
  46. package/src/ui/AddKanbanColumnAction.tsx +203 -0
  47. package/src/ui/EditorCollectionActionStart.tsx +1 -2
  48. package/src/ui/HomePageEditorCollectionAction.tsx +41 -13
  49. package/src/ui/KanbanSetupAction.tsx +38 -0
  50. package/src/ui/MissingReferenceWidget.tsx +1 -1
  51. package/src/ui/NewCollectionButton.tsx +1 -1
  52. package/src/ui/PropertyAddColumnComponent.tsx +1 -1
  53. package/src/ui/collection_editor/AICollectionGeneratorPopover.tsx +242 -0
  54. package/src/ui/collection_editor/AIModifiedPathsContext.tsx +88 -0
  55. package/src/ui/collection_editor/CollectionDetailsForm.tsx +212 -259
  56. package/src/ui/collection_editor/CollectionEditorDialog.tsx +237 -169
  57. package/src/ui/collection_editor/CollectionEditorWelcomeView.tsx +133 -67
  58. package/src/ui/collection_editor/CollectionJsonImportDialog.tsx +171 -0
  59. package/src/ui/collection_editor/CollectionPropertiesEditorForm.tsx +190 -91
  60. package/src/ui/collection_editor/DisplaySettingsForm.tsx +333 -0
  61. package/src/ui/collection_editor/EntityActionsEditTab.tsx +106 -96
  62. package/src/ui/collection_editor/EntityActionsSelectDialog.tsx +6 -7
  63. package/src/ui/collection_editor/EntityCustomViewsSelectDialog.tsx +1 -3
  64. package/src/ui/collection_editor/EnumForm.tsx +147 -100
  65. package/src/ui/collection_editor/ExtendSettingsForm.tsx +93 -0
  66. package/src/ui/collection_editor/GeneralSettingsForm.tsx +337 -0
  67. package/src/ui/collection_editor/GetCodeDialog.tsx +57 -36
  68. package/src/ui/collection_editor/KanbanConfigSection.tsx +207 -0
  69. package/src/ui/collection_editor/LayoutModeSwitch.tsx +22 -41
  70. package/src/ui/collection_editor/PropertyEditView.tsx +206 -142
  71. package/src/ui/collection_editor/PropertyFieldPreview.tsx +1 -1
  72. package/src/ui/collection_editor/PropertyTree.tsx +130 -58
  73. package/src/ui/collection_editor/SubcollectionsEditTab.tsx +171 -162
  74. package/src/ui/collection_editor/UnsavedChangesDialog.tsx +0 -2
  75. package/src/ui/collection_editor/ViewModeSwitch.tsx +41 -0
  76. package/src/ui/collection_editor/properties/BlockPropertyField.tsx +0 -2
  77. package/src/ui/collection_editor/properties/BooleanPropertyField.tsx +1 -0
  78. package/src/ui/collection_editor/properties/DateTimePropertyField.tsx +117 -35
  79. package/src/ui/collection_editor/properties/EnumPropertyField.tsx +28 -21
  80. package/src/ui/collection_editor/properties/MapPropertyField.tsx +0 -2
  81. package/src/ui/collection_editor/properties/MarkdownPropertyField.tsx +115 -39
  82. package/src/ui/collection_editor/properties/StoragePropertyField.tsx +1 -1
  83. package/src/ui/collection_editor/properties/conditions/ConditionsEditor.tsx +861 -0
  84. package/src/ui/collection_editor/properties/conditions/ConditionsPanel.tsx +28 -0
  85. package/src/ui/collection_editor/properties/conditions/EnumConditionsEditor.tsx +599 -0
  86. package/src/ui/collection_editor/properties/conditions/index.ts +6 -0
  87. package/src/ui/collection_editor/properties/conditions/property_paths.ts +92 -0
  88. package/src/ui/collection_editor/properties/validation/ValidationPanel.tsx +1 -1
  89. package/src/useCollectionEditorPlugin.tsx +32 -17
  90. package/src/utils/validateCollectionJson.ts +380 -0
@@ -42,10 +42,11 @@ import {
42
42
  Typography
43
43
  } from "@firecms/ui";
44
44
  import { YupSchema } from "./CollectionYupValidation";
45
- import { CollectionDetailsForm } from "./CollectionDetailsForm";
45
+ import { GeneralSettingsForm } from "./GeneralSettingsForm";
46
+ import { DisplaySettingsForm } from "./DisplaySettingsForm";
46
47
  import { CollectionPropertiesEditorForm } from "./CollectionPropertiesEditorForm";
47
48
  import { UnsavedChangesDialog } from "./UnsavedChangesDialog";
48
- import { SubcollectionsEditTab } from "./SubcollectionsEditTab";
49
+ import { ExtendSettingsForm } from "./ExtendSettingsForm";
49
50
  import { CollectionsConfigController } from "../../types/config_controller";
50
51
  import { CollectionEditorWelcomeView } from "./CollectionEditorWelcomeView";
51
52
  import { CollectionInference } from "../../types/collection_inference";
@@ -57,7 +58,9 @@ import { cleanPropertiesFromImport } from "./import/clean_import_data";
57
58
  import { PersistedCollection } from "../../types/persisted_collection";
58
59
  import { Formex, FormexController, useCreateFormex } from "@firecms/formex";
59
60
  import { getFullIdPath } from "./util";
60
- import { EntityActionsEditTab } from "./EntityActionsEditTab";
61
+ import { AICollectionGeneratorPopover } from "./AICollectionGeneratorPopover";
62
+ import { AIModifiedPathsProvider, useAIModifiedPaths } from "./AIModifiedPathsContext";
63
+ import { CollectionOperation, CollectionGenerationCallback } from "../../api/generateCollectionApi";
61
64
 
62
65
  export interface CollectionEditorDialogProps {
63
66
  open: boolean;
@@ -67,6 +70,11 @@ export interface CollectionEditorDialogProps {
67
70
  path?: string,
68
71
  name?: string,
69
72
  }
73
+ /**
74
+ * A collection to duplicate from. If provided, the new collection will be
75
+ * pre-populated with the same properties (but with empty name, path, and id).
76
+ */
77
+ copyFrom?: PersistedCollection;
70
78
  editedCollectionId?: string;
71
79
  fullPath?: string; // full path of this particular collection, like `products/123/locales`
72
80
  parentCollectionIds?: string[]; // path ids of the parent collection, like [`products`]
@@ -84,6 +92,24 @@ export interface CollectionEditorDialogProps {
84
92
  getData?: (path: string, parentPaths: string[]) => Promise<object[]>;
85
93
  parentCollection?: PersistedCollection;
86
94
  existingEntities?: Entity<any>[];
95
+ /**
96
+ * Initial view to open when editing: "general", "display", or "properties".
97
+ * For new collections, this is ignored.
98
+ */
99
+ initialView?: "general" | "display" | "properties";
100
+ /**
101
+ * If true, auto-expand the Kanban configuration section.
102
+ */
103
+ expandKanban?: boolean;
104
+ /**
105
+ * Callback function for generating/modifying collections.
106
+ * The plugin is API-agnostic - the consumer provides the implementation.
107
+ */
108
+ generateCollection?: CollectionGenerationCallback;
109
+ /**
110
+ * Optional analytics callback
111
+ */
112
+ onAnalyticsEvent?: (event: string, params?: object) => void;
87
113
  }
88
114
 
89
115
  export function CollectionEditorDialog(props: CollectionEditorDialogProps) {
@@ -118,30 +144,31 @@ export function CollectionEditorDialog(props: CollectionEditorDialogProps) {
118
144
  onOpenChange={(open) => !open ? handleCancel() : undefined}
119
145
  >
120
146
  <DialogTitle hidden>Collection editor</DialogTitle>
121
- {open && <CollectionEditor {...props}
122
- handleCancel={handleCancel}
123
- setFormDirty={setFormDirty}/>}
124
-
125
- <UnsavedChangesDialog
126
- open={unsavedChangesDialogOpen}
127
- handleOk={() => props.handleClose(undefined)}
128
- handleCancel={() => setUnsavedChangesDialogOpen(false)}
129
- body={"There are unsaved changes in this collection"}/>
130
-
147
+ <AIModifiedPathsProvider>
148
+ {open && <CollectionEditor {...props}
149
+ handleCancel={handleCancel}
150
+ setFormDirty={setFormDirty} />}
151
+
152
+ <UnsavedChangesDialog
153
+ open={unsavedChangesDialogOpen}
154
+ handleOk={() => props.handleClose(undefined)}
155
+ handleCancel={() => setUnsavedChangesDialogOpen(false)}
156
+ body={"There are unsaved changes in this collection"} />
157
+ </AIModifiedPathsProvider>
131
158
  </Dialog>
132
159
  );
133
160
  }
134
161
 
135
162
  type EditorView = "welcome"
136
- | "details"
163
+ | "general"
164
+ | "display"
137
165
  | "import_data_mapping"
138
166
  | "import_data_preview"
139
167
  | "import_data_saving"
140
168
  | "properties"
141
169
  | "loading"
142
170
  | "extra_view"
143
- | "subcollections"
144
- | "custom_actions";
171
+ | "extend";
145
172
 
146
173
  export function CollectionEditor(props: CollectionEditorDialogProps & {
147
174
  handleCancel: () => void,
@@ -157,7 +184,9 @@ export function CollectionEditor(props: CollectionEditorDialogProps & {
157
184
  } = navigation;
158
185
 
159
186
  const initialValuesProp = props.initialValues;
160
- const includeTemplates = !initialValuesProp?.path && (props.parentCollectionIds ?? []).length === 0;
187
+ const copyFromProp = props.copyFrom;
188
+ // Skip templates when duplicating (copyFrom is provided)
189
+ const includeTemplates = !copyFromProp && !initialValuesProp?.path && (props.parentCollectionIds ?? []).length === 0;
161
190
  const collectionsInThisLevel = (props.parentCollection ? props.parentCollection.subcollections : collections) ?? [];
162
191
  const existingPaths = collectionsInThisLevel.map(col => col.path.trim().toLowerCase());
163
192
  const existingIds = collectionsInThisLevel.map(col => col.id?.trim().toLowerCase()).filter(Boolean) as string[];
@@ -194,25 +223,36 @@ export function CollectionEditor(props: CollectionEditorDialogProps & {
194
223
  }
195
224
  : undefined;
196
225
 
226
+ // Build initial values - handle copyFrom for duplication
197
227
  const initialValues: PersistedCollection<any> = initialCollection
198
228
  ? applyPropertyConfigs(initialCollection, propertyConfigs)
199
- : {
200
- id: initialValuesProp?.path ?? randomString(16),
201
- path: initialValuesProp?.path ?? "",
202
- name: initialValuesProp?.name ?? "",
203
- group: initialValuesProp?.group ?? "",
204
- properties: {} as PropertiesOrBuilders,
205
- propertiesOrder: [],
206
- icon: coolIconKeys[Math.floor(Math.random() * coolIconKeys.length)],
207
- ownerId: authController.user?.uid ?? ""
208
- };
229
+ : copyFromProp
230
+ ? {
231
+ // When duplicating, copy all properties but clear identifiers
232
+ ...copyFromProp,
233
+ id: randomString(16),
234
+ path: "",
235
+ name: "",
236
+ subcollections: undefined, // Don't copy subcollections
237
+ ownerId: authController.user?.uid ?? ""
238
+ }
239
+ : {
240
+ id: initialValuesProp?.path ?? randomString(16),
241
+ path: initialValuesProp?.path ?? "",
242
+ name: initialValuesProp?.name ?? "",
243
+ group: initialValuesProp?.group ?? "",
244
+ properties: {} as PropertiesOrBuilders,
245
+ propertiesOrder: [],
246
+ icon: coolIconKeys[Math.floor(Math.random() * coolIconKeys.length)],
247
+ ownerId: authController.user?.uid ?? ""
248
+ };
209
249
 
210
250
  if (!initialLoadingCompleted) {
211
- return <CircularProgressCenter/>;
251
+ return <CircularProgressCenter />;
212
252
  }
213
253
 
214
254
  if (!props.isNewCollection && (!navigation.initialised || !initialLoadingCompleted)) {
215
- return <CircularProgressCenter/>;
255
+ return <CircularProgressCenter />;
216
256
  }
217
257
 
218
258
  return <CollectionEditorInternal
@@ -224,46 +264,50 @@ export function CollectionEditor(props: CollectionEditorDialogProps & {
224
264
  collection={collection}
225
265
  setCollection={setCollection}
226
266
  groups={groups}
227
- propertyConfigs={propertyConfigs}/>
267
+ propertyConfigs={propertyConfigs} />
228
268
 
229
269
  }
230
270
 
231
271
  function CollectionEditorInternal<M extends Record<string, any>>({
232
- isNewCollection,
233
- configController,
234
- editedCollectionId,
235
- parentCollectionIds,
236
- fullPath,
237
- collectionInference,
238
- handleClose,
239
- reservedGroups,
240
- extraView,
241
- handleCancel,
242
- setFormDirty,
243
- getUser,
244
- parentCollection,
245
- getData,
246
- existingPaths,
247
- existingIds,
248
- includeTemplates,
249
- collection,
250
- setCollection,
251
- initialValues,
252
- propertyConfigs,
253
- groups,
254
- existingEntities
255
- }: CollectionEditorDialogProps & {
256
- handleCancel: () => void,
257
- setFormDirty: (dirty: boolean) => void,
258
- initialValues: PersistedCollection<M>,
259
- existingPaths: string[],
260
- existingIds: string[],
261
- includeTemplates: boolean,
262
- collection: PersistedCollection<M> | undefined,
263
- setCollection: (collection: PersistedCollection<M>) => void,
264
- propertyConfigs: Record<string, PropertyConfig<any>>,
265
- groups: string[],
266
- }
272
+ isNewCollection,
273
+ configController,
274
+ editedCollectionId,
275
+ parentCollectionIds,
276
+ fullPath,
277
+ collectionInference,
278
+ handleClose,
279
+ reservedGroups,
280
+ extraView,
281
+ handleCancel,
282
+ setFormDirty,
283
+ getUser,
284
+ parentCollection,
285
+ getData,
286
+ existingPaths,
287
+ existingIds,
288
+ includeTemplates,
289
+ collection,
290
+ setCollection,
291
+ initialValues,
292
+ propertyConfigs,
293
+ groups,
294
+ existingEntities,
295
+ initialView: initialViewProp,
296
+ expandKanban,
297
+ generateCollection,
298
+ onAnalyticsEvent
299
+ }: CollectionEditorDialogProps & {
300
+ handleCancel: () => void,
301
+ setFormDirty: (dirty: boolean) => void,
302
+ initialValues: PersistedCollection<M>,
303
+ existingPaths: string[],
304
+ existingIds: string[],
305
+ includeTemplates: boolean,
306
+ collection: PersistedCollection<M> | undefined,
307
+ setCollection: (collection: PersistedCollection<M>) => void,
308
+ propertyConfigs: Record<string, PropertyConfig<any>>,
309
+ groups: string[],
310
+ }
267
311
  ) {
268
312
 
269
313
  const importConfig = useImportConfig();
@@ -273,7 +317,9 @@ function CollectionEditorInternal<M extends Record<string, any>>({
273
317
  // Use this ref to store which properties have errors
274
318
  const propertyErrorsRef = useRef({});
275
319
 
276
- const initialView = isNewCollection ? (includeTemplates ? "welcome" : "details") : "properties";
320
+ const initialView = isNewCollection
321
+ ? (includeTemplates ? "welcome" : "general")
322
+ : (initialViewProp ?? "properties");
277
323
  const [currentView, setCurrentView] = useState<EditorView>(initialView); // this view can edit either the details view or the properties one
278
324
 
279
325
  const [error, setError] = React.useState<Error | undefined>();
@@ -303,7 +349,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
303
349
  };
304
350
 
305
351
  const setNextMode = () => {
306
- if (currentView === "details") {
352
+ if (currentView === "general") {
307
353
  if (importConfig.inUse) {
308
354
  setCurrentView("import_data_saving");
309
355
  } else if (extraView) {
@@ -312,22 +358,29 @@ function CollectionEditorInternal<M extends Record<string, any>>({
312
358
  setCurrentView("properties");
313
359
  }
314
360
  } else if (currentView === "welcome") {
315
- setCurrentView("details");
361
+ setCurrentView("general");
316
362
  } else if (currentView === "import_data_mapping") {
317
363
  setCurrentView("import_data_preview");
318
364
  } else if (currentView === "import_data_preview") {
319
- setCurrentView("details");
365
+ setCurrentView("general");
320
366
  } else if (currentView === "extra_view") {
321
367
  setCurrentView("properties");
322
368
  } else {
323
- setCurrentView("details");
369
+ setCurrentView("general");
324
370
  }
325
371
 
326
372
  };
327
373
 
328
374
  const doCollectionInference = collectionInference ? (collection: PersistedCollection<any>) => {
329
375
  if (!collectionInference) return undefined;
330
- return collectionInference?.(collection.path, collection.collectionGroup ?? false, parentPaths ?? [], collection.databaseId);
376
+ return collectionInference?.(
377
+ collection.path,
378
+ collection.collectionGroup ?? false,
379
+ parentPaths ?? [],
380
+ collection.databaseId,
381
+ collection.initialFilter,
382
+ collection.initialSort
383
+ );
331
384
  } : undefined;
332
385
 
333
386
  const inferCollectionFromData = async (newCollection: PersistedCollection<M>) => {
@@ -382,6 +435,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
382
435
 
383
436
  if (!isNewCollection) {
384
437
  saveCollection(newCollectionState).then(() => {
438
+ aiModifiedPaths?.clearAllPaths();
385
439
  formexController.resetForm();
386
440
  handleClose(newCollectionState);
387
441
  });
@@ -391,7 +445,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
391
445
  if (currentView === "welcome") {
392
446
  setNextMode();
393
447
  formexController.resetForm({ values: newCollectionState });
394
- } else if (currentView === "details") {
448
+ } else if (currentView === "general") {
395
449
  if (extraView || importConfig.inUse) {
396
450
  formexController.resetForm({ values: newCollectionState });
397
451
  setNextMode();
@@ -406,8 +460,8 @@ function CollectionEditorInternal<M extends Record<string, any>>({
406
460
  }
407
461
  });
408
462
  }).finally(() => {
409
- setNextMode();
410
- });
463
+ setNextMode();
464
+ });
411
465
  } else {
412
466
  formexController.resetForm({ values: newCollectionState });
413
467
  setNextMode();
@@ -442,7 +496,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
442
496
  const validation = (col: PersistedCollection) => {
443
497
 
444
498
  let errors: Record<string, any> = {};
445
- const schema = (currentView === "properties" || currentView === "subcollections" || currentView === "details") && YupSchema;
499
+ const schema = (currentView === "properties" || currentView === "extend" || currentView === "general") && YupSchema;
446
500
  if (schema) {
447
501
  try {
448
502
  schema.validateSync(col, { abortEarly: false });
@@ -455,7 +509,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
455
509
  if (currentView === "properties") {
456
510
  errors = { ...errors, ...propertyErrorsRef.current };
457
511
  }
458
- if (currentView === "details") {
512
+ if (currentView === "general") {
459
513
  const pathError = validatePath(col.path, isNewCollection, existingPaths, col.id);
460
514
  if (pathError) {
461
515
  errors.path = pathError;
@@ -483,15 +537,15 @@ function CollectionEditorInternal<M extends Record<string, any>>({
483
537
  submitCount
484
538
  } = formController;
485
539
 
486
- // TODO: getting data is only working in root collections with this code
487
540
  const path = values.path;
488
541
  const updatedFullPath = fullPath?.includes("/") ? fullPath?.split("/").slice(0, -1).join("/") + "/" + path : path; // TODO: this path is wrong
489
542
  const pathError = validatePath(path, isNewCollection, existingPaths, values.id);
490
543
 
491
544
  const parentPaths = !pathError && parentCollectionIds ? navigation.convertIdsToPaths(parentCollectionIds) : undefined;
492
545
  const resolvedPath = !pathError ? navigation.resolveIdsFrom(updatedFullPath) : undefined;
546
+
493
547
  const getDataWithPath = resolvedPath && getData ? async () => {
494
- const data = await getData(resolvedPath, parentPaths ?? []);
548
+ const data = await getData!(resolvedPath, parentPaths ?? []);
495
549
  if (existingEntities) {
496
550
  const existingData = existingEntities.map(e => e.values);
497
551
  data.push(...existingData);
@@ -558,7 +612,16 @@ function CollectionEditorInternal<M extends Record<string, any>>({
558
612
  onImportDataSet(importData, propertiesOrder);
559
613
  setCurrentView("import_data_mapping");
560
614
  } else {
561
- setCurrentView("details");
615
+ setCurrentView("general");
616
+ }
617
+ };
618
+
619
+ const aiModifiedPaths = useAIModifiedPaths();
620
+
621
+ const handleAIGenerated = (generatedCollection: EntityCollection, operations?: CollectionOperation[]) => {
622
+ formController.setValues(generatedCollection as PersistedCollection<M>);
623
+ if (operations && aiModifiedPaths) {
624
+ aiModifiedPaths.addModifiedPaths(operations);
562
625
  }
563
626
  };
564
627
 
@@ -566,99 +629,102 @@ function CollectionEditorInternal<M extends Record<string, any>>({
566
629
  <Formex value={formController}>
567
630
 
568
631
  <>
569
- {!isNewCollection && <Tabs value={currentView}
570
- innerClassName={cls(defaultBorderMixin, "px-4 h-14 w-full justify-end bg-surface-50 dark:bg-surface-950 border-b")}
571
- onValueChange={(v) => setCurrentView(v as EditorView)}>
572
- <Tab value={"details"}>
573
- Details
574
- </Tab>
575
- <Tab value={"properties"}>
576
- Properties
577
- </Tab>
578
- <Tab value={"subcollections"}>
579
- Additional views
580
- </Tab>
581
- <Tab value={"custom_actions"}>
582
- Custom actions
583
- </Tab>
584
- </Tabs>}
632
+ {!isNewCollection && <div className={cls("px-4 py-2 w-full flex items-center justify-end gap-2 bg-surface-50 dark:bg-surface-950 border-b", defaultBorderMixin)}>
633
+ {generateCollection && (
634
+ <AICollectionGeneratorPopover
635
+ existingCollection={values}
636
+ onGenerated={handleAIGenerated}
637
+ generateCollection={generateCollection}
638
+ onAnalyticsEvent={onAnalyticsEvent}
639
+ />
640
+ )}
641
+ <Tabs value={currentView}
642
+ onValueChange={(v) => setCurrentView(v as EditorView)}>
643
+ <Tab value={"general"}>
644
+ General
645
+ </Tab>
646
+ <Tab value={"display"}>
647
+ Display
648
+ </Tab>
649
+ <Tab value={"properties"}>
650
+ Properties
651
+ </Tab>
652
+ <Tab value={"extend"}>
653
+ Extend
654
+ </Tab>
655
+ </Tabs>
656
+ </div>}
585
657
 
586
658
  <form noValidate
587
- onSubmit={formController.handleSubmit}
588
- className={cls(
589
- isNewCollection ? "h-full" : "h-[calc(100%-48px)]",
590
- "flex-grow flex flex-col relative")}>
659
+ onSubmit={formController.handleSubmit}
660
+ className={cls(
661
+ isNewCollection ? "h-full" : "h-[calc(100%-48px)]",
662
+ "flex-grow flex flex-col relative")}>
591
663
 
592
664
  {currentView === "loading" &&
593
- <CircularProgressCenter/>}
665
+ <CircularProgressCenter />}
594
666
 
595
667
  {currentView === "extra_view" &&
596
668
  path &&
597
669
  extraView?.View &&
598
- <extraView.View path={path}/>}
670
+ <extraView.View path={path} />}
599
671
 
600
672
  {currentView === "welcome" &&
601
673
  <CollectionEditorWelcomeView
602
674
  path={path}
603
675
  onContinue={onWelcomeScreenContinue}
604
676
  existingCollectionPaths={existingPaths}
605
- parentCollection={parentCollection}/>}
677
+ parentCollection={parentCollection}
678
+ generateCollection={generateCollection}
679
+ onAnalyticsEvent={onAnalyticsEvent} />}
606
680
 
607
681
  {currentView === "import_data_mapping" && importConfig &&
608
682
  <CollectionEditorImportMapping importConfig={importConfig}
609
- collectionEditable={collectionEditable}
610
- propertyConfigs={propertyConfigs}/>}
683
+ collectionEditable={collectionEditable}
684
+ propertyConfigs={propertyConfigs} />}
611
685
 
612
686
  {currentView === "import_data_preview" && importConfig &&
613
687
  <CollectionEditorImportDataPreview importConfig={importConfig}
614
- properties={values.properties as Properties}
615
- propertiesOrder={values.propertiesOrder as string[]}/>}
688
+ properties={values.properties as Properties}
689
+ propertiesOrder={values.propertiesOrder as string[]} />}
616
690
 
617
691
  {currentView === "import_data_saving" && importConfig &&
618
692
  <ImportSaveInProgress importConfig={importConfig}
619
- collection={values}
620
- path={path}
621
- onImportSuccess={async (importedCollection) => {
622
- snackbarController.open({
623
- type: "info",
624
- message: "Data imported successfully"
625
- });
626
- await saveCollection(values);
627
- handleClose(importedCollection);
628
- }}
693
+ collection={values}
694
+ path={path}
695
+ onImportSuccess={async (importedCollection) => {
696
+ snackbarController.open({
697
+ type: "info",
698
+ message: "Data imported successfully"
699
+ });
700
+ await saveCollection(values);
701
+ handleClose(importedCollection);
702
+ }}
629
703
  />}
630
704
 
631
- {currentView === "details" &&
632
- <CollectionDetailsForm
705
+ {currentView === "general" &&
706
+ <GeneralSettingsForm
633
707
  existingPaths={existingPaths}
634
708
  existingIds={existingIds}
635
- groups={groups}
636
- parentCollectionIds={parentCollectionIds}
637
709
  parentCollection={parentCollection}
638
- isNewCollection={isNewCollection}>
639
- {!isNewCollection && isMergedCollection && <div className={"flex flex-col gap-4 mt-8"}>
640
- <Typography variant={"body2"} color={"secondary"}>This collection is defined in code.
641
- The changes done in this editor will override the properties defined in code.
642
- You can delete the overridden values to revert to the state defined in code.
643
- </Typography>
644
- <Button color={"neutral"}
645
- onClick={() => {
646
- setDeleteRequested(true);
647
- }}>Reset to code</Button>
648
- </div>}
649
- </CollectionDetailsForm>}
650
-
651
- {currentView === "custom_actions" && collection &&
652
- <EntityActionsEditTab collection={collection}/>}
653
-
654
- {currentView === "subcollections" && collection &&
655
- <SubcollectionsEditTab
710
+ isNewCollection={isNewCollection} />
711
+ }
712
+
713
+ {currentView === "display" &&
714
+ <DisplaySettingsForm expandKanban={expandKanban} />
715
+ }
716
+
717
+ {currentView === "extend" && collection &&
718
+ <ExtendSettingsForm
719
+ collection={collection}
656
720
  parentCollection={parentCollection}
657
721
  configController={configController}
658
- getUser={getUser}
659
722
  collectionInference={collectionInference}
723
+ getUser={getUser}
660
724
  parentCollectionIds={parentCollectionIds}
661
- collection={collection}/>}
725
+ isMergedCollection={!isNewCollection && isMergedCollection}
726
+ onResetToCode={() => setDeleteRequested(true)} />
727
+ }
662
728
 
663
729
  {currentView === "properties" &&
664
730
  <CollectionPropertiesEditorForm
@@ -683,52 +749,52 @@ function CollectionEditorInternal<M extends Record<string, any>>({
683
749
  color={"primary"}
684
750
  onClick={() => setCurrentView("extra_view")}>
685
751
  {extraView.icon}
686
- </IconButton>}/>
752
+ </IconButton>} />
687
753
  }
688
754
 
689
755
  <DialogActions
690
756
  position={"absolute"}>
691
- {error && <ErrorView error={error}/>}
757
+ {error && <ErrorView error={error} />}
692
758
 
693
759
  {isNewCollection && includeTemplates && currentView === "import_data_mapping" &&
694
760
  <Button variant={"text"}
695
- type="button"
696
- onClick={() => {
697
- importConfig.setInUse(false);
698
- return setCurrentView("welcome");
699
- }}>
761
+ type="button"
762
+ onClick={() => {
763
+ importConfig.setInUse(false);
764
+ return setCurrentView("welcome");
765
+ }}>
700
766
  Back
701
767
  </Button>}
702
768
 
703
769
  {isNewCollection && includeTemplates && currentView === "import_data_preview" &&
704
770
  <Button variant={"text"}
705
- type="button"
706
- onClick={() => {
707
- setCurrentView("import_data_mapping");
708
- }}>
771
+ type="button"
772
+ onClick={() => {
773
+ setCurrentView("import_data_mapping");
774
+ }}>
709
775
  Back
710
776
  </Button>}
711
777
 
712
- {isNewCollection && includeTemplates && currentView === "details" &&
778
+ {isNewCollection && includeTemplates && currentView === "general" &&
713
779
  <Button variant={"text"}
714
- type="button"
715
- onClick={() => setCurrentView("welcome")}>
780
+ type="button"
781
+ onClick={() => setCurrentView("welcome")}>
716
782
  Back
717
783
  </Button>}
718
784
 
719
785
  {isNewCollection && currentView === "properties" && <Button variant={"text"}
720
- type="button"
721
- color={"neutral"}
722
- onClick={() => setCurrentView("details")}>
723
- <ArrowBackIcon/>
786
+ type="button"
787
+ color={"neutral"}
788
+ onClick={() => setCurrentView("general")}>
789
+ <ArrowBackIcon />
724
790
  Back
725
791
  </Button>}
726
792
 
727
793
  <Button variant={"text"}
728
- color={"neutral"}
729
- onClick={() => {
730
- handleCancel();
731
- }}>
794
+ color={"neutral"}
795
+ onClick={() => {
796
+ handleCancel();
797
+ }}>
732
798
  Cancel
733
799
  </Button>
734
800
 
@@ -757,18 +823,18 @@ function CollectionEditorInternal<M extends Record<string, any>>({
757
823
  Next
758
824
  </Button>}
759
825
 
760
- {isNewCollection && (currentView === "details" || currentView === "properties") &&
826
+ {isNewCollection && (currentView === "general" || currentView === "properties") &&
761
827
  <LoadingButton
762
828
  variant={"filled"}
763
829
  color="primary"
764
830
  type="submit"
765
831
  loading={isSubmitting}
766
- disabled={isSubmitting || (currentView === "details" && !validValues)}
832
+ disabled={isSubmitting || (currentView === "general" && !validValues)}
767
833
  startIcon={currentView === "properties"
768
- ? <CheckIcon/>
834
+ ? <CheckIcon />
769
835
  : undefined}
770
836
  >
771
- {currentView === "details" && "Next"}
837
+ {currentView === "general" && "Next"}
772
838
  {currentView === "properties" && "Create collection"}
773
839
  </LoadingButton>}
774
840
 
@@ -794,7 +860,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
794
860
  title={<>Delete the stored config?</>}
795
861
  body={<> This will <b>not
796
862
  delete any data</b>, only
797
- the stored config, and reset to the code state.</>}/>
863
+ the stored config, and reset to the code state.</>} />
798
864
 
799
865
  </DialogContent>
800
866
 
@@ -808,7 +874,9 @@ function applyPropertyConfigs<M extends Record<string, any> = any>(collection: P
808
874
  const propertiesResult: PropertiesOrBuilders<any> = {};
809
875
  if (properties) {
810
876
  Object.keys(properties).forEach((key) => {
811
- propertiesResult[key] = applyPropertiesConfig(properties[key] as PropertyOrBuilder, propertyConfigs);
877
+ const prop = properties[key];
878
+ if (prop == null) return;
879
+ propertiesResult[key] = applyPropertiesConfig(prop as PropertyOrBuilder, propertyConfigs);
812
880
  });
813
881
  }
814
882
 
@@ -820,7 +888,7 @@ function applyPropertyConfigs<M extends Record<string, any> = any>(collection: P
820
888
 
821
889
  function applyPropertiesConfig(property: PropertyOrBuilder, propertyConfigs: Record<string, PropertyConfig<any>>) {
822
890
  let internalProperty = property;
823
- if (propertyConfigs && typeof internalProperty === "object" && internalProperty.propertyConfig) {
891
+ if (propertyConfigs && internalProperty && typeof internalProperty === "object" && internalProperty.propertyConfig) {
824
892
  const propertyConfig = propertyConfigs[internalProperty.propertyConfig];
825
893
  if (propertyConfig && isPropertyBuilder(propertyConfig.property)) {
826
894
  internalProperty = propertyConfig.property;