@firecms/collection_editor 3.0.0-beta.2-pre.2 → 3.0.0-beta.2-pre.4

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 (59) hide show
  1. package/dist/index.es.js +2532 -2386
  2. package/dist/index.es.js.map +1 -1
  3. package/dist/index.umd.js +2 -2
  4. package/dist/index.umd.js.map +1 -1
  5. package/dist/types/collection_editor_controller.d.ts +3 -2
  6. package/dist/types/config_controller.d.ts +3 -3
  7. package/dist/ui/collection_editor/CollectionEditorDialog.d.ts +3 -5
  8. package/dist/ui/collection_editor/CollectionEditorWelcomeView.d.ts +2 -2
  9. package/dist/ui/collection_editor/CollectionPropertiesEditorForm.d.ts +1 -2
  10. package/dist/ui/collection_editor/EnumForm.d.ts +1 -2
  11. package/dist/ui/collection_editor/PropertyEditView.d.ts +5 -5
  12. package/dist/ui/collection_editor/PropertyTree.d.ts +14 -13
  13. package/dist/ui/collection_editor/SwitchControl.d.ts +8 -0
  14. package/dist/ui/collection_editor/properties/CommonPropertyFields.d.ts +0 -1
  15. package/dist/ui/collection_editor/util.d.ts +1 -0
  16. package/package.json +6 -5
  17. package/src/ConfigControllerProvider.tsx +24 -22
  18. package/src/types/collection_editor_controller.tsx +4 -3
  19. package/src/types/config_controller.tsx +3 -3
  20. package/src/ui/CollectionViewHeaderAction.tsx +1 -1
  21. package/src/ui/EditorCollectionAction.tsx +3 -3
  22. package/src/ui/HomePageEditorCollectionAction.tsx +2 -2
  23. package/src/ui/MissingReferenceWidget.tsx +2 -1
  24. package/src/ui/NewCollectionButton.tsx +3 -3
  25. package/src/ui/NewCollectionCard.tsx +2 -1
  26. package/src/ui/PropertyAddColumnComponent.tsx +1 -1
  27. package/src/ui/RootCollectionSuggestions.tsx +3 -2
  28. package/src/ui/collection_editor/CollectionDetailsForm.tsx +2 -2
  29. package/src/ui/collection_editor/CollectionEditorDialog.tsx +420 -374
  30. package/src/ui/collection_editor/CollectionEditorWelcomeView.tsx +19 -12
  31. package/src/ui/collection_editor/CollectionPropertiesEditorForm.tsx +26 -18
  32. package/src/ui/collection_editor/EnumForm.tsx +118 -114
  33. package/src/ui/collection_editor/GetCodeDialog.tsx +1 -1
  34. package/src/ui/collection_editor/PropertyEditView.tsx +199 -142
  35. package/src/ui/collection_editor/PropertyFieldPreview.tsx +5 -1
  36. package/src/ui/collection_editor/PropertyTree.tsx +132 -113
  37. package/src/ui/collection_editor/SubcollectionsEditTab.tsx +18 -11
  38. package/src/ui/collection_editor/SwitchControl.tsx +39 -0
  39. package/src/ui/collection_editor/import/CollectionEditorImportMapping.tsx +10 -2
  40. package/src/ui/collection_editor/properties/BlockPropertyField.tsx +2 -2
  41. package/src/ui/collection_editor/properties/BooleanPropertyField.tsx +13 -9
  42. package/src/ui/collection_editor/properties/CommonPropertyFields.tsx +11 -37
  43. package/src/ui/collection_editor/properties/DateTimePropertyField.tsx +2 -2
  44. package/src/ui/collection_editor/properties/EnumPropertyField.tsx +3 -6
  45. package/src/ui/collection_editor/properties/MapPropertyField.tsx +2 -2
  46. package/src/ui/collection_editor/properties/NumberPropertyField.tsx +2 -2
  47. package/src/ui/collection_editor/properties/ReferencePropertyField.tsx +11 -14
  48. package/src/ui/collection_editor/properties/RepeatPropertyField.tsx +10 -9
  49. package/src/ui/collection_editor/properties/StoragePropertyField.tsx +15 -9
  50. package/src/ui/collection_editor/properties/StringPropertyField.tsx +2 -2
  51. package/src/ui/collection_editor/properties/UrlPropertyField.tsx +2 -2
  52. package/src/ui/collection_editor/properties/advanced/AdvancedPropertyValidation.tsx +27 -18
  53. package/src/ui/collection_editor/properties/validation/ArrayPropertyValidation.tsx +2 -2
  54. package/src/ui/collection_editor/properties/validation/GeneralPropertyValidation.tsx +27 -16
  55. package/src/ui/collection_editor/properties/validation/NumberPropertyValidation.tsx +33 -18
  56. package/src/ui/collection_editor/properties/validation/StringPropertyValidation.tsx +99 -80
  57. package/src/ui/collection_editor/util.ts +7 -0
  58. package/src/ui/collection_editor/utils/strings.ts +2 -1
  59. package/src/useCollectionEditorPlugin.tsx +1 -16
@@ -2,7 +2,6 @@ import * as React from "react";
2
2
  import { useCallback, useEffect, useRef, useState } from "react";
3
3
  import {
4
4
  CircularProgressCenter,
5
- CMSType,
6
5
  EntityCollection,
7
6
  ErrorView,
8
7
  isPropertyBuilder,
@@ -36,9 +35,8 @@ import {
36
35
  IconButton,
37
36
  LoadingButton,
38
37
  Tab,
39
- Tabs,
38
+ Tabs
40
39
  } from "@firecms/ui";
41
- import { Form, Formik, FormikHelpers } from "formik";
42
40
  import { YupSchema } from "./CollectionYupValidation";
43
41
  import { CollectionDetailsForm } from "./CollectionDetailsForm";
44
42
  import { CollectionPropertiesEditorForm } from "./CollectionPropertiesEditorForm";
@@ -53,6 +51,8 @@ import { CollectionEditorImportMapping } from "./import/CollectionEditorImportMa
53
51
  import { CollectionEditorImportDataPreview } from "./import/CollectionEditorImportDataPreview";
54
52
  import { cleanPropertiesFromImport } from "./import/clean_import_data";
55
53
  import { PersistedCollection } from "../../types/persisted_collection";
54
+ import { Formex, FormexController, useCreateFormex } from "@firecms/formex";
55
+ import { getFullIdPath } from "./util";
56
56
 
57
57
  export interface CollectionEditorDialogProps {
58
58
  open: boolean;
@@ -62,7 +62,7 @@ export interface CollectionEditorDialogProps {
62
62
  path?: string,
63
63
  name?: string,
64
64
  }
65
- editedCollectionPath?: string; // last segment of the path, like `locales`
65
+ editedCollectionId?: string;
66
66
  fullPath?: string; // full path of this particular collection, like `products/123/locales`
67
67
  parentCollectionIds?: string[]; // path ids of the parent collection, like [`products`]
68
68
  handleClose: (collection?: EntityCollection) => void;
@@ -112,9 +112,9 @@ export function CollectionEditorDialog(props: CollectionEditorDialogProps) {
112
112
  maxWidth={"7xl"}
113
113
  onOpenChange={(open) => !open ? handleCancel() : undefined}
114
114
  >
115
- {open && <CollectionEditorDialogInternal {...props}
116
- handleCancel={handleCancel}
117
- setFormDirty={setFormDirty}/>}
115
+ {open && <CollectionEditor {...props}
116
+ handleCancel={handleCancel}
117
+ setFormDirty={setFormDirty}/>}
118
118
 
119
119
  <UnsavedChangesDialog
120
120
  open={unsavedChangesDialogOpen}
@@ -136,45 +136,41 @@ type EditorView = "welcome"
136
136
  | "extra_view"
137
137
  | "subcollections";
138
138
 
139
- export function CollectionEditorDialogInternal<M extends {
140
- [Key: string]: CMSType
141
- }>({
142
- isNewCollection,
143
- initialValues: initialValuesProp,
144
- configController,
145
- editedCollectionPath,
146
- parentCollectionIds,
147
- fullPath,
148
- collectionInference,
149
- handleClose,
150
- reservedGroups,
151
- extraView,
152
- handleCancel,
153
- setFormDirty,
154
- pathSuggestions,
155
- getUser,
156
- parentCollection,
157
- getData
158
- }: CollectionEditorDialogProps & {
159
- handleCancel: () => void,
160
- setFormDirty: (dirty: boolean) => void
161
- }
162
- ) {
163
-
139
+ export function CollectionEditor<M extends Record<string, any>>(props: CollectionEditorDialogProps & {
140
+ handleCancel: () => void,
141
+ setFormDirty: (dirty: boolean) => void
142
+ }) {
164
143
  const { propertyConfigs } = useCustomizationController();
165
144
  const navigation = useNavigationController();
145
+ const authController = useAuthController();
146
+
166
147
  const {
167
148
  topLevelNavigation,
168
149
  collections
169
150
  } = navigation;
170
151
 
171
- const includeTemplates = !initialValuesProp?.path && (parentCollectionIds ?? []).length === 0;
172
- const collectionsInThisLevel = (parentCollection ? parentCollection.subcollections : collections) ?? [];
152
+ const initialValuesProp = props.initialValues;
153
+ const includeTemplates = !initialValuesProp?.path && (props.parentCollectionIds ?? []).length === 0;
154
+ const collectionsInThisLevel = (props.parentCollection ? props.parentCollection.subcollections : collections) ?? [];
173
155
  const existingPaths = collectionsInThisLevel.map(col => col.path.trim().toLowerCase());
174
156
  const existingIds = collectionsInThisLevel.map(col => col.id?.trim().toLowerCase()).filter(Boolean) as string[];
157
+ const [collection, setCollection] = React.useState<PersistedCollection<M> | undefined>();
158
+ const [initialLoadingCompleted, setInitialLoadingCompleted] = React.useState(false);
175
159
 
176
- const importConfig = useImportConfig();
177
-
160
+ useEffect(() => {
161
+ try {
162
+ if (navigation.initialised) {
163
+ if (props.editedCollectionId) {
164
+ setCollection(navigation.getCollectionFromPaths<PersistedCollection<M>>([...(props.parentCollectionIds ?? []), props.editedCollectionId]));
165
+ } else {
166
+ setCollection(undefined);
167
+ }
168
+ setInitialLoadingCompleted(true);
169
+ }
170
+ } catch (e) {
171
+ console.error(e);
172
+ }
173
+ }, [navigation.getCollectionFromPaths, props.editedCollectionId, props.parentCollectionIds, navigation.initialised]);
178
174
  if (!topLevelNavigation) {
179
175
  throw Error("Internal: Navigation not ready in collection editor");
180
176
  }
@@ -183,8 +179,88 @@ export function CollectionEditorDialogInternal<M extends {
183
179
  groups
184
180
  }: TopNavigationResult = topLevelNavigation;
185
181
 
182
+ const initialCollection = collection
183
+ ? {
184
+ ...collection,
185
+ id: collection.id ?? collection.path ?? randomString(16)
186
+ }
187
+ : undefined;
188
+
189
+ const initialValues: PersistedCollection<M> = initialCollection
190
+ ? applyPropertyConfigs(initialCollection, propertyConfigs)
191
+ : {
192
+ id: initialValuesProp?.path ?? randomString(16),
193
+ path: initialValuesProp?.path ?? "",
194
+ name: initialValuesProp?.name ?? "",
195
+ group: initialValuesProp?.group ?? "",
196
+ properties: {} as PropertiesOrBuilders<M>,
197
+ propertiesOrder: [],
198
+ icon: coolIconKeys[Math.floor(Math.random() * coolIconKeys.length)],
199
+ ownerId: authController.user?.uid ?? ""
200
+ };
201
+
202
+ if (!initialLoadingCompleted) {
203
+ return <CircularProgressCenter/>;
204
+ }
205
+
206
+ if (!props.isNewCollection && (!navigation.initialised || !initialLoadingCompleted)) {
207
+ return <CircularProgressCenter/>;
208
+ }
209
+
210
+ return <CollectionEditorInternal
211
+ {...props}
212
+ initialValues={initialValues}
213
+ existingPaths={existingPaths}
214
+ existingIds={existingIds}
215
+ includeTemplates={includeTemplates}
216
+ collection={collection}
217
+ setCollection={setCollection}
218
+ groups={groups}
219
+ propertyConfigs={propertyConfigs}/>
220
+
221
+ }
222
+
223
+ function CollectionEditorInternal<M extends Record<string, any>>({
224
+ isNewCollection,
225
+ configController,
226
+ editedCollectionId,
227
+ parentCollectionIds,
228
+ fullPath,
229
+ collectionInference,
230
+ handleClose,
231
+ reservedGroups,
232
+ extraView,
233
+ handleCancel,
234
+ setFormDirty,
235
+ pathSuggestions,
236
+ getUser,
237
+ parentCollection,
238
+ getData,
239
+ existingPaths,
240
+ existingIds,
241
+ includeTemplates,
242
+ collection,
243
+ setCollection,
244
+ initialValues,
245
+ propertyConfigs,
246
+ groups
247
+ }: CollectionEditorDialogProps & {
248
+ handleCancel: () => void,
249
+ setFormDirty: (dirty: boolean) => void,
250
+ initialValues: PersistedCollection<M>,
251
+ existingPaths: string[],
252
+ existingIds: string[],
253
+ includeTemplates: boolean,
254
+ collection: PersistedCollection<M> | undefined,
255
+ setCollection: (collection: PersistedCollection<M>) => void,
256
+ propertyConfigs: Record<string, PropertyConfig<any>>,
257
+ groups: string[],
258
+ }
259
+ ) {
260
+
261
+ const importConfig = useImportConfig();
262
+ const navigation = useNavigationController();
186
263
  const snackbarController = useSnackbarController();
187
- const authController = useAuthController();
188
264
 
189
265
  // Use this ref to store which properties have errors
190
266
  const propertyErrorsRef = useRef({});
@@ -194,32 +270,12 @@ export function CollectionEditorDialogInternal<M extends {
194
270
 
195
271
  const [error, setError] = React.useState<Error | undefined>();
196
272
 
197
- const [collection, setCollection] = React.useState<PersistedCollection<M> | undefined>();
198
- const [initialLoadingCompleted, setInitialLoadingCompleted] = React.useState(false);
199
- const [initialError, setInitialError] = React.useState<Error | undefined>();
200
-
201
- useEffect(() => {
202
- try {
203
- if (navigation.initialised) {
204
- if (editedCollectionPath) {
205
- setCollection(navigation.getCollectionFromPaths<PersistedCollection<M>>([...(parentCollectionIds ?? []), editedCollectionPath]));
206
- } else {
207
- setCollection(undefined);
208
- }
209
- setInitialLoadingCompleted(true);
210
- }
211
- } catch (e) {
212
- console.error(e);
213
- setInitialError(initialError);
214
- }
215
- }, [navigation.getCollectionFromPaths, editedCollectionPath, initialError, navigation.initialised]);
216
-
217
273
  const saveCollection = (updatedCollection: PersistedCollection<M>): Promise<boolean> => {
218
- const fullPath = updatedCollection.id || updatedCollection.path;
274
+ const id = updatedCollection.id || updatedCollection.path;
219
275
  return configController.saveCollection({
220
- id: fullPath,
276
+ id,
221
277
  collectionData: updatedCollection,
222
- previousPath: editedCollectionPath,
278
+ previousId: editedCollectionId,
223
279
  parentCollectionIds
224
280
  })
225
281
  .then(() => {
@@ -237,26 +293,6 @@ export function CollectionEditorDialogInternal<M extends {
237
293
  });
238
294
  };
239
295
 
240
- const initialCollection = collection
241
- ? {
242
- ...collection,
243
- id: collection.id ?? collection.path ?? randomString(16)
244
- }
245
- : undefined;
246
-
247
- const initialValues: PersistedCollection<M> = initialCollection
248
- ? applyPropertyConfigs(initialCollection, propertyConfigs)
249
- : {
250
- id: initialValuesProp?.path ?? randomString(16),
251
- path: initialValuesProp?.path ?? "",
252
- name: initialValuesProp?.name ?? "",
253
- group: initialValuesProp?.group ?? "",
254
- properties: {} as PropertiesOrBuilders<M>,
255
- propertiesOrder: [],
256
- icon: coolIconKeys[Math.floor(Math.random() * coolIconKeys.length)],
257
- ownerId: authController.user?.uid ?? ""
258
- };
259
-
260
296
  const setNextMode = useCallback(() => {
261
297
  if (currentView === "details") {
262
298
  if (importConfig.inUse) {
@@ -316,7 +352,7 @@ export function CollectionEditorDialogInternal<M extends {
316
352
  }
317
353
 
318
354
  setCollection(values);
319
- console.log("Inferred collection", {
355
+ console.debug("Inferred collection", {
320
356
  newCollection: newCollection ?? {},
321
357
  values
322
358
  });
@@ -331,13 +367,13 @@ export function CollectionEditorDialogInternal<M extends {
331
367
  }
332
368
  }, [parentCollectionIds, doCollectionInference]);
333
369
 
334
- const onSubmit = (newCollectionState: PersistedCollection<M>, formikHelpers: FormikHelpers<PersistedCollection<M>>) => {
370
+ const onSubmit = (newCollectionState: PersistedCollection<M>, formexController: FormexController<PersistedCollection<M>>) => {
371
+ console.log("Submitting collection", newCollectionState);
335
372
  try {
336
373
 
337
- console.log("Submitting collection", newCollectionState)
338
374
  if (!isNewCollection) {
339
375
  saveCollection(newCollectionState).then(() => {
340
- formikHelpers.resetForm({ values: initialValues });
376
+ formexController.resetForm({ values: initialValues });
341
377
  // setNextMode();
342
378
  handleClose(newCollectionState);
343
379
  });
@@ -346,15 +382,15 @@ export function CollectionEditorDialogInternal<M extends {
346
382
 
347
383
  if (currentView === "welcome") {
348
384
  setNextMode();
349
- formikHelpers.resetForm({ values: newCollectionState });
385
+ formexController.resetForm({ values: newCollectionState });
350
386
  } else if (currentView === "details") {
351
387
  if (extraView || importConfig.inUse) {
352
- formikHelpers.resetForm({ values: newCollectionState });
388
+ formexController.resetForm({ values: newCollectionState });
353
389
  setNextMode();
354
390
  } else if (isNewCollection) {
355
391
  inferCollectionFromData(newCollectionState)
356
392
  .then((values) => {
357
- formikHelpers.resetForm({
393
+ formexController.resetForm({
358
394
  values: values ?? newCollectionState,
359
395
  touched: {
360
396
  path: true,
@@ -365,25 +401,25 @@ export function CollectionEditorDialogInternal<M extends {
365
401
  setNextMode();
366
402
  });
367
403
  } else {
368
- formikHelpers.resetForm({ values: newCollectionState });
404
+ formexController.resetForm({ values: newCollectionState });
369
405
  setNextMode();
370
406
  }
371
407
  } else if (currentView === "extra_view") {
372
408
  setNextMode();
373
- formikHelpers.resetForm({ values: newCollectionState });
409
+ formexController.resetForm({ values: newCollectionState });
374
410
  } else if (currentView === "import_data_mapping") {
375
411
  setNextMode();
376
412
  } else if (currentView === "import_data_preview") {
377
413
  setNextMode();
378
414
  } else if (currentView === "properties") {
379
415
  saveCollection(newCollectionState).then(() => {
380
- formikHelpers.resetForm({ values: initialValues });
416
+ formexController.resetForm({ values: initialValues });
381
417
  setNextMode();
382
418
  handleClose(newCollectionState);
383
419
  });
384
420
  } else {
385
421
  setNextMode();
386
- formikHelpers.resetForm({ values: newCollectionState });
422
+ formexController.resetForm({ values: newCollectionState });
387
423
  }
388
424
  } catch (e: any) {
389
425
  snackbarController.open({
@@ -391,298 +427,308 @@ export function CollectionEditorDialogInternal<M extends {
391
427
  message: "Error persisting collection: " + (e.message ?? "Details in the console")
392
428
  });
393
429
  console.error(e);
394
- formikHelpers.resetForm({ values: newCollectionState });
430
+ formexController.resetForm({ values: newCollectionState });
395
431
  }
396
432
  };
397
433
 
398
- if (!isNewCollection && (!navigation.initialised || !initialLoadingCompleted)) {
399
- return <CircularProgressCenter/>;
434
+ const validation = (col: PersistedCollection) => {
435
+
436
+ let errors: Record<string, any> = {};
437
+ const schema = (currentView === "properties" || currentView === "subcollections" || currentView === "details") && YupSchema;
438
+ if (schema) {
439
+ try {
440
+ schema.validateSync(col, { abortEarly: false });
441
+ } catch (e: any) {
442
+ e.inner.forEach((err: any) => {
443
+ errors[err.path] = err.message;
444
+ });
445
+ }
446
+ }
447
+ if (currentView === "properties") {
448
+ errors = { ...errors, ...propertyErrorsRef.current };
449
+ }
450
+ if (currentView === "details") {
451
+ const pathError = validatePath(col.path, isNewCollection, existingPaths, col.id);
452
+ if (pathError) {
453
+ errors.path = pathError;
454
+ }
455
+ const idError = validateId(col.id, isNewCollection, existingPaths, existingIds);
456
+ if (idError) {
457
+ errors.id = idError;
458
+ }
459
+ }
460
+ return errors;
461
+ };
462
+
463
+ const formController = useCreateFormex<PersistedCollection<M>>({
464
+ initialValues,
465
+ onSubmit,
466
+ validation
467
+ });
468
+
469
+ const {
470
+ values,
471
+ setFieldValue,
472
+ isSubmitting,
473
+ dirty,
474
+ submitCount
475
+ } = formController;
476
+
477
+ const path = values.path ?? editedCollectionId;
478
+ const updatedFullPath = fullPath?.includes("/") ? fullPath?.split("/").slice(0, -1).join("/") + "/" + path : path; // TODO: this path is wrong
479
+ const pathError = validatePath(path, isNewCollection, existingPaths, values.id);
480
+
481
+ 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;
484
+
485
+ // eslint-disable-next-line react-hooks/rules-of-hooks
486
+ useEffect(() => {
487
+ setFormDirty(dirty);
488
+ }, [dirty]);
489
+
490
+ function onImportDataSet(data: object[]) {
491
+ importConfig.setInUse(true);
492
+ buildEntityPropertiesFromData(data, getInferenceType)
493
+ .then((properties) => {
494
+ const res = cleanPropertiesFromImport(properties);
495
+
496
+ setFieldValue("properties", res.properties);
497
+ setFieldValue("propertiesOrder", Object.keys(res.properties));
498
+
499
+ importConfig.setIdColumn(res.idColumn);
500
+ importConfig.setImportData(data);
501
+ importConfig.setHeadersMapping(res.headersMapping);
502
+ importConfig.setOriginProperties(res.properties);
503
+ });
400
504
  }
401
505
 
506
+ const validValues = Boolean(values.name) && Boolean(values.id);
507
+
508
+ const onImportMappingComplete = () => {
509
+ const updatedProperties = { ...values.properties };
510
+ if (importConfig.idColumn)
511
+ delete updatedProperties[importConfig.idColumn];
512
+ setFieldValue("properties", updatedProperties);
513
+ // setFieldValue("propertiesOrder", Object.values(importConfig.headersMapping));
514
+ setNextMode();
515
+ };
516
+
517
+ const editable = collection?.editable === undefined || collection?.editable === true;
518
+ const collectionEditable = editable || isNewCollection;
519
+
402
520
  return <DialogContent fullHeight={true}>
403
- <Formik
404
- initialValues={initialValues}
405
- validationSchema={(currentView === "properties" || currentView === "subcollections" || currentView === "details") && YupSchema}
406
- validate={(v) => {
407
- if (currentView === "properties") {
408
- // return the errors for the properties form
409
- return propertyErrorsRef.current;
410
- }
411
- const errors: Record<string, any> = {};
412
- if (currentView === "details") {
413
- const pathError = validatePath(v.path, isNewCollection, existingPaths, v.id);
414
- if (pathError) {
415
- errors.path = pathError;
416
- }
417
- const idError = validateId(v.id, isNewCollection, existingPaths, existingIds);
418
- if (idError) {
419
- errors.id = idError;
521
+ <Formex value={formController}>
522
+
523
+ <>
524
+ {!isNewCollection && <Tabs value={currentView}
525
+ className={cn(defaultBorderMixin, "justify-end bg-gray-50 dark:bg-gray-950 border-b")}
526
+ onValueChange={(v) => setCurrentView(v as EditorView)}>
527
+ <Tab value={"details"}>
528
+ Details
529
+ </Tab>
530
+ <Tab value={"properties"}>
531
+ Properties
532
+ </Tab>
533
+ <Tab value={"subcollections"}>
534
+ Additional views
535
+ </Tab>
536
+ </Tabs>}
537
+
538
+ <form noValidate
539
+ onSubmit={formController.handleSubmit}
540
+ className={cn(
541
+ isNewCollection ? "h-full" : "h-[calc(100%-48px)]",
542
+ "flex-grow flex flex-col relative")}>
543
+
544
+ {currentView === "loading" &&
545
+ <CircularProgressCenter/>}
546
+
547
+ {currentView === "extra_view" &&
548
+ path &&
549
+ extraView?.View &&
550
+ <extraView.View path={path}/>}
551
+
552
+ {currentView === "welcome" &&
553
+ <CollectionEditorWelcomeView
554
+ path={path}
555
+ onContinue={(data) => {
556
+ if (data) {
557
+ onImportDataSet(data);
558
+ setCurrentView("import_data_mapping");
559
+ } else {
560
+ setCurrentView("details");
561
+ }
562
+ }}
563
+ existingCollectionPaths={existingPaths}
564
+ parentCollection={parentCollection}
565
+ pathSuggestions={pathSuggestions}/>}
566
+
567
+ {currentView === "import_data_mapping" && importConfig &&
568
+ <CollectionEditorImportMapping importConfig={importConfig}
569
+ collectionEditable={collectionEditable}
570
+ propertyConfigs={propertyConfigs}/>}
571
+
572
+ {currentView === "import_data_preview" && importConfig &&
573
+ <CollectionEditorImportDataPreview importConfig={importConfig}
574
+ properties={values.properties as Properties}
575
+ propertiesOrder={values.propertiesOrder as string[]}/>}
576
+
577
+ {currentView === "import_data_saving" && importConfig &&
578
+ <ImportSaveInProgress importConfig={importConfig}
579
+ collection={values}
580
+ onImportSuccess={(importedCollection) => {
581
+ handleClose(importedCollection);
582
+ snackbarController.open({
583
+ type: "info",
584
+ message: "Data imported successfully"
585
+ });
586
+ }}
587
+ />}
588
+
589
+ {currentView === "details" &&
590
+ <CollectionDetailsForm
591
+ existingPaths={existingPaths}
592
+ existingIds={existingIds}
593
+ groups={groups}
594
+ parentCollectionIds={parentCollectionIds}
595
+ parentCollection={parentCollection}
596
+ isNewCollection={isNewCollection}/>}
597
+
598
+ {currentView === "subcollections" && collection &&
599
+ <SubcollectionsEditTab
600
+ parentCollection={parentCollection}
601
+ configController={configController}
602
+ getUser={getUser}
603
+ collectionInference={collectionInference}
604
+ parentCollectionIds={parentCollectionIds}
605
+ collection={collection}/>}
606
+
607
+ {currentView === "properties" &&
608
+ <CollectionPropertiesEditorForm
609
+ showErrors={submitCount > 0}
610
+ isNewCollection={isNewCollection}
611
+ reservedGroups={reservedGroups}
612
+ onPropertyError={(propertyKey, namespace, error) => {
613
+ const current = removeUndefined({
614
+ ...propertyErrorsRef.current,
615
+ [getFullIdPath(propertyKey, namespace)]: removeUndefined(error, true)
616
+ }, true);
617
+ propertyErrorsRef.current = current;
618
+ formController.validate();
619
+ }}
620
+ getUser={getUser}
621
+ getData={getDataWithPath}
622
+ doCollectionInference={doCollectionInference}
623
+ propertyConfigs={propertyConfigs}
624
+ collectionEditable={collectionEditable}
625
+ extraIcon={extraView?.icon &&
626
+ <IconButton
627
+ color={"primary"}
628
+ onClick={() => setCurrentView("extra_view")}>
629
+ {extraView.icon}
630
+ </IconButton>}/>
420
631
  }
421
- }
422
- return errors;
423
- }}
424
- onSubmit={onSubmit}
425
- >
426
- {(formikHelpers) => {
427
- const {
428
- values,
429
- errors,
430
- setFieldValue,
431
- isSubmitting,
432
- dirty,
433
- submitCount
434
- } = formikHelpers;
435
-
436
- const path = values.path ?? editedCollectionPath;
437
- const updatedFullPath = fullPath?.includes("/") ? fullPath?.split("/").slice(0, -1).join("/") + "/" + path : path; // TODO: this path is wrong
438
- const pathError = validatePath(path, isNewCollection, existingPaths, values.id);
439
-
440
- const parentPaths = !pathError && parentCollectionIds ? navigation.convertIdsToPaths(parentCollectionIds) : undefined;
441
- const resolvedPath = !pathError ? navigation.resolveAliasesFrom(updatedFullPath) : undefined;
442
- const getDataWithPath = resolvedPath && getData ? () => getData(resolvedPath, parentPaths ?? []) : undefined;
443
-
444
- // eslint-disable-next-line react-hooks/rules-of-hooks
445
- useEffect(() => {
446
- setFormDirty(dirty);
447
- }, [dirty]);
448
-
449
- function onImportDataSet(data: object[]) {
450
- importConfig.setInUse(true);
451
- buildEntityPropertiesFromData(data, getInferenceType)
452
- .then((properties) => {
453
- const res = cleanPropertiesFromImport(properties);
454
-
455
- setFieldValue("properties", res.properties);
456
- setFieldValue("propertiesOrder", Object.keys(res.properties));
457
-
458
- importConfig.setIdColumn(res.idColumn);
459
- importConfig.setImportData(data);
460
- importConfig.setHeadersMapping(res.headersMapping);
461
- importConfig.setOriginProperties(res.properties);
462
- });
463
- }
464
632
 
465
- const validValues = Boolean(values.name) && Boolean(values.id);
633
+ {currentView !== "welcome" && <DialogActions
634
+ position={"absolute"}>
635
+ {error && <ErrorView error={error}/>}
636
+
637
+ {isNewCollection && includeTemplates && currentView === "import_data_mapping" &&
638
+ <Button variant={"text"}
639
+ type="button"
640
+ onClick={() => {
641
+ importConfig.setInUse(false);
642
+ return setCurrentView("welcome");
643
+ }}>
644
+ <ArrowBackIcon/>
645
+ Back
646
+ </Button>}
647
+
648
+ {isNewCollection && includeTemplates && currentView === "import_data_preview" &&
649
+ <Button variant={"text"}
650
+ type="button"
651
+ onClick={() => {
652
+ saveCollection(values);
653
+ setCurrentView("import_data_mapping");
654
+ }}>
655
+ <ArrowBackIcon/>
656
+ Back
657
+ </Button>}
658
+
659
+ {isNewCollection && includeTemplates && currentView === "details" &&
660
+ <Button variant={"text"}
661
+ type="button"
662
+ onClick={() => setCurrentView("welcome")}>
663
+ <ArrowBackIcon/>
664
+ Back
665
+ </Button>}
666
+
667
+ {isNewCollection && currentView === "properties" && <Button variant={"text"}
668
+ type="button"
669
+ onClick={() => setCurrentView("details")}>
670
+ <ArrowBackIcon/>
671
+ Back
672
+ </Button>}
673
+
674
+ <Button variant={"text"}
675
+ onClick={() => {
676
+ handleCancel();
677
+ }}>
678
+ Cancel
679
+ </Button>
680
+
681
+ {isNewCollection && currentView === "import_data_mapping" &&
682
+ <Button
683
+ variant={"filled"}
684
+ color="primary"
685
+ onClick={onImportMappingComplete}
686
+ >
687
+ Next
688
+ </Button>}
689
+
690
+ {isNewCollection && currentView === "import_data_preview" &&
691
+ <Button
692
+ variant={"filled"}
693
+ color="primary"
694
+ onClick={() => {
695
+ setNextMode();
696
+ }}
697
+ >
698
+ Next
699
+ </Button>}
700
+
701
+ {isNewCollection && (currentView === "details" || currentView === "properties") &&
702
+ <LoadingButton
703
+ variant={"filled"}
704
+ color="primary"
705
+ type="submit"
706
+ loading={isSubmitting}
707
+ disabled={isSubmitting || (currentView === "details" && !validValues)}
708
+ startIcon={currentView === "properties"
709
+ ? <DoneIcon/>
710
+ : undefined}
711
+ >
712
+ {currentView === "details" && "Next"}
713
+ {currentView === "properties" && "Create collection"}
714
+ </LoadingButton>}
715
+
716
+ {!isNewCollection && <LoadingButton
717
+ variant="filled"
718
+ color="primary"
719
+ type="submit"
720
+ loading={isSubmitting}
721
+ // disabled={isSubmitting || !dirty}
722
+ >
723
+ Update collection
724
+ </LoadingButton>}
725
+
726
+ </DialogActions>}
727
+ </form>
728
+ </>
729
+
730
+ </Formex>
466
731
 
467
- const onImportMappingComplete = () => {
468
- const updatedProperties = { ...values.properties };
469
- if (importConfig.idColumn)
470
- delete updatedProperties[importConfig.idColumn];
471
- setFieldValue("properties", updatedProperties);
472
- // setFieldValue("propertiesOrder", Object.values(importConfig.headersMapping));
473
- setNextMode();
474
- };
475
-
476
- const editable = collection?.editable === undefined || collection?.editable === true;
477
- const collectionEditable = editable || isNewCollection;
478
- return (
479
- <>
480
- {!isNewCollection && <Tabs value={currentView}
481
- className={cn(defaultBorderMixin, "justify-end bg-gray-50 dark:bg-gray-950 border-b")}
482
- onValueChange={(v) => setCurrentView(v as EditorView)}>
483
- <Tab value={"details"}>
484
- Details
485
- </Tab>
486
- <Tab value={"properties"}>
487
- Properties
488
- </Tab>
489
- <Tab value={"subcollections"}>
490
- Additional views
491
- </Tab>
492
- </Tabs>}
493
-
494
- <Form noValidate
495
- className={cn(
496
- isNewCollection ? "h-full" : "h-[calc(100%-48px)]",
497
- "flex-grow flex flex-col relative")}>
498
-
499
- {currentView === "loading" &&
500
- <CircularProgressCenter/>}
501
-
502
- {currentView === "extra_view" &&
503
- path &&
504
- extraView?.View &&
505
- <extraView.View path={path}/>}
506
-
507
- {currentView === "welcome" &&
508
- <CollectionEditorWelcomeView
509
- path={path}
510
- onContinue={(data) => {
511
- if (data) {
512
- onImportDataSet(data);
513
- setCurrentView("import_data_mapping");
514
- } else {
515
- setCurrentView("details");
516
- }
517
- }}
518
- collections={collections}
519
- parentCollection={parentCollection}
520
- pathSuggestions={pathSuggestions}/>}
521
-
522
- {currentView === "import_data_mapping" && importConfig &&
523
- <CollectionEditorImportMapping importConfig={importConfig}
524
- collectionEditable={collectionEditable}
525
- propertyConfigs={propertyConfigs}/>}
526
-
527
- {currentView === "import_data_preview" && importConfig &&
528
- <CollectionEditorImportDataPreview importConfig={importConfig}
529
- properties={values.properties as Properties}
530
- propertiesOrder={values.propertiesOrder as string[]}/>}
531
-
532
- {currentView === "import_data_saving" && importConfig &&
533
- <ImportSaveInProgress importConfig={importConfig}
534
- collection={values}
535
- onImportSuccess={(importedCollection) => {
536
- handleClose(importedCollection);
537
- snackbarController.open({
538
- type: "info",
539
- message: "Data imported successfully"
540
- });
541
- }}
542
- />}
543
-
544
- {currentView === "details" &&
545
- <CollectionDetailsForm
546
- existingPaths={existingPaths}
547
- existingIds={existingIds}
548
- groups={groups}
549
- parentCollectionIds={parentCollectionIds}
550
- parentCollection={parentCollection}
551
- isNewCollection={isNewCollection}/>}
552
-
553
- {currentView === "subcollections" && collection &&
554
- <SubcollectionsEditTab
555
- parentCollection={parentCollection}
556
- configController={configController}
557
- getUser={getUser}
558
- collectionInference={collectionInference}
559
- parentCollectionIds={parentCollectionIds}
560
- collection={collection}/>}
561
-
562
- {currentView === "properties" &&
563
- <CollectionPropertiesEditorForm
564
- showErrors={submitCount > 0}
565
- isNewCollection={isNewCollection}
566
- reservedGroups={reservedGroups}
567
- onPropertyError={(propertyKey, namespace, error) => {
568
- propertyErrorsRef.current = removeUndefined({
569
- ...propertyErrorsRef.current,
570
- [propertyKey]: error
571
- }, true);
572
- }}
573
- getUser={getUser}
574
- getData={getDataWithPath}
575
- doCollectionInference={doCollectionInference}
576
- propertyConfigs={propertyConfigs}
577
- collectionEditable={collectionEditable}
578
- extraIcon={extraView?.icon &&
579
- <IconButton
580
- color={"primary"}
581
- onClick={() => setCurrentView("extra_view")}>
582
- {extraView.icon}
583
- </IconButton>}/>
584
- }
585
-
586
- {currentView !== "welcome" && <DialogActions
587
- position={"absolute"}>
588
- {error && <ErrorView error={error}/>}
589
-
590
- {isNewCollection && includeTemplates && currentView === "import_data_mapping" &&
591
- <Button variant={"text"}
592
- type="button"
593
- onClick={() => {
594
- importConfig.setInUse(false);
595
- return setCurrentView("welcome");
596
- }}>
597
- <ArrowBackIcon/>
598
- Back
599
- </Button>}
600
-
601
- {isNewCollection && includeTemplates && currentView === "import_data_preview" &&
602
- <Button variant={"text"}
603
- type="button"
604
- onClick={() => {
605
- saveCollection(values);
606
- setCurrentView("import_data_mapping");
607
- }}>
608
- <ArrowBackIcon/>
609
- Back
610
- </Button>}
611
-
612
- {isNewCollection && includeTemplates && currentView === "details" &&
613
- <Button variant={"text"}
614
- type="button"
615
- onClick={() => setCurrentView("welcome")}>
616
- <ArrowBackIcon/>
617
- Back
618
- </Button>}
619
-
620
- {isNewCollection && currentView === "properties" && <Button variant={"text"}
621
- type="button"
622
- onClick={() => setCurrentView("details")}>
623
- <ArrowBackIcon/>
624
- Back
625
- </Button>}
626
-
627
- <Button variant={"text"}
628
- onClick={() => {
629
- handleCancel();
630
- }}>
631
- Cancel
632
- </Button>
633
-
634
- {isNewCollection && currentView === "import_data_mapping" &&
635
- <Button
636
- variant={"filled"}
637
- color="primary"
638
- onClick={onImportMappingComplete}
639
- >
640
- Next
641
- </Button>}
642
-
643
- {isNewCollection && currentView === "import_data_preview" &&
644
- <Button
645
- variant={"filled"}
646
- color="primary"
647
- onClick={() => {
648
- setNextMode();
649
- }}
650
- >
651
- Next
652
- </Button>}
653
-
654
- {isNewCollection && (currentView === "details" || currentView === "properties") &&
655
- <LoadingButton
656
- variant={"filled"}
657
- color="primary"
658
- type="submit"
659
- loading={isSubmitting}
660
- disabled={isSubmitting || (currentView === "details" && !validValues)}
661
- startIcon={currentView === "properties"
662
- ? <DoneIcon/>
663
- : undefined}
664
- >
665
- {currentView === "details" && "Next"}
666
- {currentView === "properties" && "Create collection"}
667
- </LoadingButton>}
668
-
669
- {!isNewCollection && <LoadingButton
670
- variant="filled"
671
- color="primary"
672
- type="submit"
673
- loading={isSubmitting}
674
- // disabled={isSubmitting || !dirty}
675
- >
676
- Update collection
677
- </LoadingButton>}
678
-
679
- </DialogActions>}
680
- </Form>
681
- </>
682
- );
683
- }}
684
-
685
- </Formik>
686
732
  </DialogContent>
687
733
 
688
734
  }