@firecms/collection_editor 3.0.0-alpha.21 → 3.0.0-alpha.25

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 (92) hide show
  1. package/dist/ConfigControllerProvider.d.ts +36 -0
  2. package/dist/components/CollectionViewHeaderAction.d.ts +8 -0
  3. package/dist/components/EditorCollectionAction.d.ts +2 -0
  4. package/dist/components/HomePageEditorCollectionAction.d.ts +2 -0
  5. package/dist/components/MissingReferenceWidget.d.ts +3 -0
  6. package/dist/components/NewCollectionCard.d.ts +2 -0
  7. package/dist/components/PropertyAddColumnComponent.d.ts +6 -0
  8. package/dist/components/RootCollectionSuggestions.d.ts +1 -0
  9. package/dist/components/collection_editor/CollectionDetailsForm.d.ts +9 -0
  10. package/dist/components/collection_editor/CollectionEditorDialog.d.ts +37 -0
  11. package/dist/components/collection_editor/CollectionEditorWelcomeView.d.ts +15 -0
  12. package/dist/components/collection_editor/CollectionPropertiesEditorForm.d.ts +19 -0
  13. package/dist/components/collection_editor/CollectionYupValidation.d.ts +11 -0
  14. package/dist/components/collection_editor/EntityCustomViewsSelectDialog.d.ts +4 -0
  15. package/dist/components/collection_editor/EnumForm.d.ts +13 -0
  16. package/dist/components/collection_editor/PropertyEditView.d.ts +39 -0
  17. package/dist/components/collection_editor/PropertyFieldPreview.d.ts +15 -0
  18. package/dist/components/collection_editor/PropertySelectItem.d.ts +8 -0
  19. package/dist/components/collection_editor/PropertyTree.d.ts +30 -0
  20. package/dist/components/collection_editor/SelectIcons.d.ts +6 -0
  21. package/dist/components/collection_editor/SubcollectionsEditTab.d.ts +12 -0
  22. package/dist/components/collection_editor/UnsavedChangesDialog.d.ts +9 -0
  23. package/dist/components/collection_editor/import/CollectionEditorImportDataPreview.d.ts +7 -0
  24. package/dist/components/collection_editor/import/CollectionEditorImportMapping.d.ts +6 -0
  25. package/dist/components/collection_editor/import/clean_import_data.d.ts +7 -0
  26. package/dist/components/collection_editor/properties/BlockPropertyField.d.ts +7 -0
  27. package/dist/components/collection_editor/properties/BooleanPropertyField.d.ts +3 -0
  28. package/dist/components/collection_editor/properties/CommonPropertyFields.d.ts +10 -0
  29. package/dist/components/collection_editor/properties/DateTimePropertyField.d.ts +3 -0
  30. package/dist/components/collection_editor/properties/EnumPropertyField.d.ts +8 -0
  31. package/dist/components/collection_editor/properties/FieldHelperView.d.ts +4 -0
  32. package/dist/components/collection_editor/properties/KeyValuePropertyField.d.ts +3 -0
  33. package/dist/components/collection_editor/properties/MapPropertyField.d.ts +7 -0
  34. package/dist/components/collection_editor/properties/NumberPropertyField.d.ts +3 -0
  35. package/dist/components/collection_editor/properties/ReferencePropertyField.d.ts +13 -0
  36. package/dist/components/collection_editor/properties/RepeatPropertyField.d.ts +9 -0
  37. package/dist/components/collection_editor/properties/StoragePropertyField.d.ts +5 -0
  38. package/dist/components/collection_editor/properties/StringPropertyField.d.ts +5 -0
  39. package/dist/components/collection_editor/properties/advanced/AdvancedPropertyValidation.d.ts +3 -0
  40. package/dist/components/collection_editor/properties/validation/ArrayPropertyValidation.d.ts +5 -0
  41. package/dist/components/collection_editor/properties/validation/GeneralPropertyValidation.d.ts +4 -0
  42. package/dist/components/collection_editor/properties/validation/NumberPropertyValidation.d.ts +3 -0
  43. package/dist/components/collection_editor/properties/validation/StringPropertyValidation.d.ts +11 -0
  44. package/dist/components/collection_editor/properties/validation/ValidationPanel.d.ts +2 -0
  45. package/dist/components/collection_editor/templates/blog_template.d.ts +10 -0
  46. package/dist/components/collection_editor/templates/products_template.d.ts +12 -0
  47. package/dist/components/collection_editor/templates/users_template.d.ts +7 -0
  48. package/dist/components/collection_editor/util.d.ts +4 -0
  49. package/dist/components/collection_editor/utils/supported_fields.d.ts +3 -0
  50. package/dist/components/collection_editor/utils/update_property_for_widget.d.ts +2 -0
  51. package/dist/components/collection_editor/utils/useTraceUpdate.d.ts +1 -0
  52. package/dist/index.d.ts +11 -0
  53. package/dist/index.es.js +6754 -0
  54. package/dist/index.es.js.map +1 -0
  55. package/dist/index.umd.js +2 -0
  56. package/dist/index.umd.js.map +1 -0
  57. package/dist/types/collection_editor_controller.d.ts +33 -0
  58. package/dist/types/collection_inference.d.ts +2 -0
  59. package/dist/types/config_controller.d.ts +33 -0
  60. package/dist/types/config_permissions.d.ts +19 -0
  61. package/dist/types/persisted_collection.d.ts +6 -0
  62. package/dist/useCollectionEditorController.d.ts +6 -0
  63. package/dist/useCollectionEditorPlugin.d.ts +44 -0
  64. package/dist/useCollectionsConfigController.d.ts +6 -0
  65. package/dist/utils/arrays.d.ts +1 -0
  66. package/dist/utils/entities.d.ts +3 -0
  67. package/dist/utils/icons.d.ts +1 -0
  68. package/dist/utils/join_collections.d.ts +13 -0
  69. package/dist/utils/synonyms.d.ts +1951 -0
  70. package/package.json +6 -5
  71. package/src/ConfigControllerProvider.tsx +131 -8
  72. package/src/components/CollectionViewHeaderAction.tsx +38 -0
  73. package/src/components/MissingReferenceWidget.tsx +2 -1
  74. package/src/components/NewCollectionCard.tsx +2 -1
  75. package/src/components/PropertyAddColumnComponent.tsx +39 -0
  76. package/src/components/RootCollectionSuggestions.tsx +2 -1
  77. package/src/components/collection_editor/CollectionDetailsForm.tsx +1 -0
  78. package/src/components/collection_editor/CollectionEditorDialog.tsx +1 -1
  79. package/src/components/collection_editor/CollectionPropertiesEditorForm.tsx +3 -3
  80. package/src/components/collection_editor/PropertyEditView.tsx +112 -97
  81. package/src/components/collection_editor/SubcollectionsEditTab.tsx +14 -3
  82. package/src/components/collection_editor/import/CollectionEditorImportMapping.tsx +1 -1
  83. package/src/components/collection_editor/properties/BlockPropertyField.tsx +1 -1
  84. package/src/components/collection_editor/properties/CommonPropertyFields.tsx +0 -1
  85. package/src/components/collection_editor/properties/MapPropertyField.tsx +1 -1
  86. package/src/components/collection_editor/properties/RepeatPropertyField.tsx +1 -1
  87. package/src/index.ts +1 -0
  88. package/src/types/collection_editor_controller.tsx +11 -2
  89. package/src/types/config_controller.tsx +13 -2
  90. package/src/types/persisted_collection.ts +2 -1
  91. package/src/useCollectionEditorPlugin.tsx +22 -2
  92. package/src/utils/join_collections.ts +11 -48
@@ -1,7 +1,7 @@
1
1
  import React, { useDeferredValue, useEffect, useRef, useState } from "react";
2
2
  import equal from "react-fast-compare"
3
3
 
4
- import { Form, Formik, FormikErrors, FormikProps, getIn } from "formik";
4
+ import { Formik, FormikErrors, FormikProps, getIn } from "formik";
5
5
  import {
6
6
  Button,
7
7
  cn,
@@ -14,10 +14,13 @@ import {
14
14
  FieldConfig,
15
15
  FieldConfigBadge,
16
16
  FieldConfigId,
17
+ getFieldConfig,
17
18
  getFieldId,
18
19
  IconButton,
19
20
  InfoLabel,
20
- isPropertyBuilder, Property,
21
+ isPropertyBuilder,
22
+ mergeDeep,
23
+ Property,
21
24
  Select,
22
25
  toSnakeCase,
23
26
  Typography
@@ -52,7 +55,7 @@ export type OnPropertyChangedParams = {
52
55
 
53
56
  export type PropertyFormProps = {
54
57
  includeIdAndName?: boolean;
55
- existing: boolean;
58
+ existingProperty: boolean;
56
59
  autoUpdateId?: boolean;
57
60
  autoOpenTypeSelect: boolean;
58
61
  inArray: boolean;
@@ -76,7 +79,7 @@ export const PropertyForm = React.memo(
76
79
  function PropertyForm({
77
80
  includeIdAndName = true,
78
81
  autoOpenTypeSelect,
79
- existing,
82
+ existingProperty,
80
83
  autoUpdateId,
81
84
  inArray,
82
85
  propertyKey,
@@ -138,7 +141,7 @@ export const PropertyForm = React.memo(
138
141
  id,
139
142
  property
140
143
  });
141
- if (!existing)
144
+ if (!existingProperty)
142
145
  helpers.resetForm({ values: initialValue });
143
146
  }}
144
147
  // validate={(values) => {
@@ -170,7 +173,7 @@ export const PropertyForm = React.memo(
170
173
  propertyNamespace={propertyNamespace}
171
174
  onError={onError}
172
175
  showErrors={forceShowErrors || props.submitCount > 0}
173
- existing={existing}
176
+ existing={existingProperty}
174
177
  autoUpdateId={autoUpdateId}
175
178
  inArray={inArray}
176
179
  autoOpenTypeSelect={autoOpenTypeSelect}
@@ -189,7 +192,7 @@ export const PropertyForm = React.memo(
189
192
  a.includeIdAndName === b.includeIdAndName &&
190
193
  a.autoOpenTypeSelect === b.autoOpenTypeSelect &&
191
194
  a.autoUpdateId === b.autoUpdateId &&
192
- a.existing === b.existing
195
+ a.existingProperty === b.existingProperty
193
196
  );
194
197
 
195
198
  export function PropertyFormDialog({
@@ -214,38 +217,36 @@ export function PropertyFormDialog({
214
217
  maxWidth={"xl"}
215
218
  fullWidth={true}
216
219
  >
217
- <Form noValidate style={{ height: "100%" }}>
218
-
219
- <DialogContent>
220
- <PropertyForm {...formProps}
221
- onPropertyChanged={(params) => {
222
- onPropertyChanged?.(params);
223
- onOkClicked?.();
224
- }}
225
- onPropertyChangedImmediate={false}
226
- getHelpers={getHelpers}
227
- getData={getData}
228
- />
229
- </DialogContent>
230
-
231
- <DialogActions>
232
-
233
- {onCancel && <Button
234
- variant={"text"}
235
- onClick={() => {
236
- onCancel();
237
- helpersRef.current?.resetForm();
238
- }}>
239
- Cancel
240
- </Button>}
241
-
242
- <Button variant="outlined"
243
- color="primary"
244
- onClick={() => helpersRef.current?.submitForm()}>
245
- Ok
246
- </Button>
247
- </DialogActions>
248
- </Form>
220
+
221
+ <DialogContent>
222
+ <PropertyForm {...formProps}
223
+ onPropertyChanged={(params) => {
224
+ onPropertyChanged?.(params);
225
+ onOkClicked?.();
226
+ }}
227
+ onPropertyChangedImmediate={false}
228
+ getHelpers={getHelpers}
229
+ getData={getData}
230
+ />
231
+ </DialogContent>
232
+
233
+ <DialogActions>
234
+
235
+ {onCancel && <Button
236
+ variant={"text"}
237
+ onClick={() => {
238
+ onCancel();
239
+ helpersRef.current?.resetForm();
240
+ }}>
241
+ Cancel
242
+ </Button>}
243
+
244
+ <Button variant="outlined"
245
+ color="primary"
246
+ onClick={() => helpersRef.current?.submitForm()}>
247
+ Ok
248
+ </Button>
249
+ </DialogActions>
249
250
  </Dialog>;
250
251
 
251
252
  }
@@ -289,10 +290,11 @@ function PropertyEditView({
289
290
  customFields: Record<string, FieldConfig>;
290
291
  } & FormikProps<PropertyWithId>) {
291
292
 
293
+ const [selectOpen, setSelectOpen] = useState(autoOpenTypeSelect);
292
294
  const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
293
295
  const [selectedFieldConfigId, setSelectedFieldConfigId] = useState<string | undefined>(values?.dataType ? getFieldId(values) : undefined);
294
296
 
295
- const allSupportedFields = Object.entries(customFields).concat(Object.entries(DEFAULT_FIELD_CONFIGS));
297
+ const allSupportedFields = Object.entries(DEFAULT_FIELD_CONFIGS).concat(Object.entries(customFields));
296
298
 
297
299
  const displayedWidgets = inArray
298
300
  ? allSupportedFields.filter(([_, fieldConfig]) => !isPropertyBuilder(fieldConfig.property) && fieldConfig.property?.dataType !== "array")
@@ -392,10 +394,12 @@ function PropertyEditView({
392
394
  childComponent = <NumberPropertyField disabled={disabled}/>;
393
395
  } else if (selectedFieldConfigId === "group") {
394
396
  childComponent =
395
- <MapPropertyField disabled={disabled} getData={getData} allowDataInference={allowDataInference} customFields={customFields}/>;
397
+ <MapPropertyField disabled={disabled} getData={getData} allowDataInference={allowDataInference}
398
+ customFields={customFields}/>;
396
399
  } else if (selectedFieldConfigId === "block") {
397
400
  childComponent =
398
- <BlockPropertyField disabled={disabled} getData={getData} allowDataInference={allowDataInference} customFields={customFields}/>;
401
+ <BlockPropertyField disabled={disabled} getData={getData} allowDataInference={allowDataInference}
402
+ customFields={customFields}/>;
399
403
  } else if (selectedFieldConfigId === "reference") {
400
404
  childComponent =
401
405
  <ReferencePropertyField showErrors={showErrors}
@@ -436,62 +440,73 @@ function PropertyEditView({
436
440
  </InfoLabel>}
437
441
 
438
442
  <div className="flex mt-2 justify-between">
439
- <Select
440
- className={"w-full"}
441
- error={Boolean(selectedWidgetError)}
442
- value={selectedFieldConfigId ?? ""}
443
- placeholder={"Select a property widget"}
444
- open={autoOpenTypeSelect}
445
- position={"item-aligned"}
446
- disabled={disabled}
447
- renderValue={(value) => {
448
- if (!value) {
449
- return <em>Select a property
450
- widget</em>;
451
- }
452
- const key = value as FieldConfigId;
453
- const fieldConfig = DEFAULT_FIELD_CONFIGS[key] ?? customFields[key];
454
- const baseProperty = fieldConfig.property;
455
- const optionDisabled = isPropertyBuilder(baseProperty) || (existing && baseProperty.dataType !== values?.dataType);
456
- return <div
457
- onClick={(e) => {
458
- if (optionDisabled) {
459
- e.stopPropagation();
460
- e.preventDefault();
461
- }
462
- }}
463
- className={cn(
464
- "flex items-center",
465
- optionDisabled ? "w-full pointer-events-none opacity-50" : "")}>
466
- <div className={"mr-8"}>
467
- <FieldConfigBadge fieldConfig={fieldConfig}/>
468
- </div>
469
- <div className={"flex flex-col items-start text-base text-left"}>
470
- <div>{fieldConfig.name}</div>
471
- <Typography variant={"caption"}
472
- color={"disabled"}>
473
- {optionDisabled ? "You can only switch to widgets that use the same data type" : fieldConfig.description}
474
- </Typography>
443
+ <div className={"w-full flex flex-col gap-2"}>
444
+ <Select
445
+ className={"w-full"}
446
+ error={Boolean(selectedWidgetError)}
447
+ value={selectedFieldConfigId ?? ""}
448
+ placeholder={"Select a property widget"}
449
+ open={selectOpen}
450
+ onOpenChange={setSelectOpen}
451
+ position={"item-aligned"}
452
+ disabled={disabled}
453
+ renderValue={(value) => {
454
+ if (!value) {
455
+ return <em>Select a property
456
+ widget</em>;
457
+ }
458
+ const key = value as FieldConfigId;
459
+ const fieldConfig = DEFAULT_FIELD_CONFIGS[key] ?? customFields[key];
460
+ const baseProperty = fieldConfig.property;
461
+ const baseFieldConfig = baseProperty && !isPropertyBuilder(baseProperty) ? getFieldConfig(baseProperty, customFields) : undefined;
462
+ const optionDisabled = isPropertyBuilder(baseProperty) || (existing && baseProperty.dataType !== values?.dataType);
463
+ const computedFieldConfig = baseFieldConfig ? mergeDeep(baseFieldConfig, fieldConfig) : fieldConfig;
464
+ return <div
465
+ onClick={(e) => {
466
+ if (optionDisabled) {
467
+ e.stopPropagation();
468
+ e.preventDefault();
469
+ }
470
+ }}
471
+ className={cn(
472
+ "flex items-center",
473
+ optionDisabled ? "w-full pointer-events-none opacity-50" : "")}>
474
+ <div className={"mr-8"}>
475
+ <FieldConfigBadge fieldConfig={computedFieldConfig}/>
476
+ </div>
477
+ <div className={"flex flex-col items-start text-base text-left"}>
478
+ <div>{computedFieldConfig.name}</div>
479
+ <Typography variant={"caption"}
480
+ color={"disabled"}>
481
+ {optionDisabled ? "You can only switch to widgets that use the same data type" : computedFieldConfig.description}
482
+ </Typography>
483
+ </div>
475
484
  </div>
476
- </div>
477
- }}
478
- onValueChange={(value) => {
479
- onWidgetSelectChanged(value as FieldConfigId);
480
- }}>
481
- {displayedWidgets.map(([key, fieldConfig]) => {
482
- const baseProperty = fieldConfig.property;
483
- const optionDisabled = existing && !isPropertyBuilder(baseProperty) && baseProperty.dataType !== values?.dataType;
484
- return <PropertySelectItem
485
- key={key}
486
- value={key}
487
- optionDisabled={optionDisabled}
488
- fieldConfig={fieldConfig}
489
- existing={existing}/>;
490
- })}
491
- </Select>
492
-
493
- {selectedWidgetError &&
494
- <Typography variant="caption" color={"error"}>Required</Typography>}
485
+ }}
486
+ onValueChange={(value) => {
487
+ onWidgetSelectChanged(value as FieldConfigId);
488
+ }}>
489
+ {displayedWidgets.map(([key, fieldConfig]) => {
490
+ const baseProperty = fieldConfig.property;
491
+ const optionDisabled = existing && !isPropertyBuilder(baseProperty) && baseProperty.dataType !== values?.dataType;
492
+ return <PropertySelectItem
493
+ key={key}
494
+ value={key}
495
+ optionDisabled={optionDisabled}
496
+ fieldConfig={fieldConfig}
497
+ existing={existing}/>;
498
+ })}
499
+ </Select>
500
+
501
+ {selectedWidgetError &&
502
+ <Typography variant="caption"
503
+ className={"ml-3.5"}
504
+ color={"error"}>Required</Typography>}
505
+
506
+ {/*<Typography variant="caption" className={"ml-3.5"}>Define your own custom properties and*/}
507
+ {/* components</Typography>*/}
508
+
509
+ </div>
495
510
 
496
511
  {onDelete && values?.id &&
497
512
  <IconButton
@@ -8,6 +8,7 @@ import {
8
8
  EntityCollection,
9
9
  EntityCustomView,
10
10
  IconButton,
11
+ InfoLabel,
11
12
  Paper,
12
13
  resolveEntityView,
13
14
  Table,
@@ -128,7 +129,7 @@ export function SubcollectionsEditTab({
128
129
  Custom views
129
130
  </Typography>
130
131
 
131
- {totalEntityViews > 0 &&
132
+ {totalEntityViews > 0 && <>
132
133
  <Paper className={"flex flex-col gap-4 p-2 w-full"}>
133
134
  <Table>
134
135
  <TableBody>
@@ -164,14 +165,23 @@ export function SubcollectionsEditTab({
164
165
  {view.name}
165
166
  </Typography>
166
167
  <Typography variant={"caption"} className={"flex-grow"}>
167
- This view is defined in code with key <code>{view.key}</code>
168
+ This view is defined in code with
169
+ key <code>{view.key}</code>
168
170
  </Typography>
169
171
  </TableCell>
170
172
  </TableRow>
171
173
  ))}
172
174
  </TableBody>
173
175
  </Table>
174
- </Paper>}
176
+ </Paper>
177
+
178
+ </>}
179
+
180
+ {totalEntityViews === 0 &&
181
+ <InfoLabel>
182
+ COMING SOON: You can define your own custom views by uploading it with the CLI
183
+ </InfoLabel>
184
+ }
175
185
 
176
186
  <Button
177
187
  onClick={() => {
@@ -181,6 +191,7 @@ export function SubcollectionsEditTab({
181
191
  startIcon={<AddIcon/>}>
182
192
  Add custom entity view
183
193
  </Button>
194
+
184
195
  </div>
185
196
 
186
197
  </div>
@@ -199,7 +199,7 @@ export function CollectionEditorImportMapping({
199
199
  setSelectedProperty(undefined);
200
200
  }}
201
201
  autoOpenTypeSelect={false}
202
- existing={false}
202
+ existingProperty={false}
203
203
  customFields={customFields}/>
204
204
 
205
205
  <div style={{ height: "52px" }}/>
@@ -120,7 +120,7 @@ export function BlockPropertyField({ disabled, getData, allowDataInference, cust
120
120
  propertyKey={selectedPropertyKey}
121
121
  propertyNamespace={selectedPropertyNamespace}
122
122
  property={selectedProperty}
123
- existing={Boolean(selectedPropertyKey)}
123
+ existingProperty={Boolean(selectedPropertyKey)}
124
124
  autoUpdateId={!selectedPropertyKey}
125
125
  autoOpenTypeSelect={!selectedPropertyKey}
126
126
  onPropertyChanged={onPropertyCreated}
@@ -42,7 +42,6 @@ export const CommonPropertyFields = React.forwardRef<HTMLDivElement, CommonPrope
42
42
  inputRef={ref}
43
43
  name={name}
44
44
  as={DebouncedTextField}
45
- invisible={!isNewProperty}
46
45
  style={{ fontSize: 20 }}
47
46
  validate={validateName}
48
47
  placeholder={"Field name"}
@@ -142,7 +142,7 @@ export function MapPropertyField({ disabled, getData, allowDataInference, custom
142
142
  propertyKey={selectedPropertyKey}
143
143
  propertyNamespace={selectedPropertyNamespace}
144
144
  property={selectedProperty}
145
- existing={Boolean(selectedPropertyKey)}
145
+ existingProperty={Boolean(selectedPropertyKey)}
146
146
  autoUpdateId={!selectedPropertyKey}
147
147
  autoOpenTypeSelect={!selectedPropertyKey}
148
148
  onPropertyChanged={onPropertyCreated}
@@ -85,7 +85,7 @@ export function RepeatPropertyField({
85
85
  <PropertyFormDialog
86
86
  inArray={true}
87
87
  open={propertyDialogOpen}
88
- existing={existing}
88
+ existingProperty={existing}
89
89
  getData={getData}
90
90
  autoUpdateId={!existing}
91
91
  autoOpenTypeSelect={!existing}
package/src/index.ts CHANGED
@@ -31,3 +31,4 @@ export type {
31
31
  } from "./types/collection_inference";
32
32
 
33
33
  export { MissingReferenceWidget } from "./components/MissingReferenceWidget";
34
+ export * from "./components/collection_editor/util";
@@ -1,5 +1,5 @@
1
1
  import { CollectionEditorPermissionsBuilder } from "./config_permissions";
2
- import { EntityCollection } from "@firecms/core";
2
+ import { EntityCollection, Property } from "@firecms/core";
3
3
 
4
4
  /**
5
5
  * Controller to open the collection editor dialog.
@@ -21,7 +21,16 @@ export interface CollectionEditorController {
21
21
  name?: string
22
22
  },
23
23
  parentPathSegments: string[],
24
- parentCollection?: EntityCollection<any, any, any>
24
+ parentCollection?: EntityCollection<any, any, any>,
25
+ redirect: boolean
26
+ }) => void;
27
+
28
+ editProperty: (props: {
29
+ propertyKey?: string,
30
+ property?: Property,
31
+ currentPropertiesOrder?: string[],
32
+ editedCollectionPath: string,
33
+ parentPathSegments: string[],
25
34
  }) => void;
26
35
 
27
36
  configPermissions: CollectionEditorPermissionsBuilder;
@@ -1,4 +1,4 @@
1
- import { CMSType, EntityCollection } from "@firecms/core";
1
+ import { CMSType, Property } from "@firecms/core";
2
2
  import { PersistedCollection } from "./persisted_collection";
3
3
 
4
4
  /**
@@ -11,7 +11,9 @@ export interface CollectionsConfigController {
11
11
 
12
12
  collections?: PersistedCollection[];
13
13
 
14
- saveCollection: <M extends { [Key: string]: CMSType }>(params:SaveCollectionParams<M>) => Promise<void>;
14
+ saveCollection: <M extends { [Key: string]: CMSType }>(params: SaveCollectionParams<M>) => Promise<void>;
15
+
16
+ saveProperty: (params: SavePropertyParams) => Promise<void>;
15
17
 
16
18
  deleteCollection: (props: DeleteCollectionParams) => Promise<void>;
17
19
 
@@ -24,6 +26,15 @@ export type SaveCollectionParams<M extends Record<string, any>> = {
24
26
  parentPathSegments?: string[]
25
27
  }
26
28
 
29
+ export type SavePropertyParams = {
30
+ path: string,
31
+ propertyKey: string,
32
+ namespace?: string,
33
+ newPropertiesOrder?: string[],
34
+ property: Property,
35
+ parentPathSegments?: string[]
36
+ }
37
+
27
38
  export type DeleteCollectionParams = {
28
39
  path: string,
29
40
  parentPathSegments?: string[]
@@ -1,7 +1,8 @@
1
1
  import { EntityCollection, Properties, User } from "@firecms/core";
2
2
 
3
3
  export type PersistedCollection<M extends Record<string, any> = any, AdditionalKey extends string = string, UserType extends User = User>
4
- = Omit<EntityCollection<M, AdditionalKey, UserType>, "properties"> & {
4
+ = Omit<EntityCollection<M, AdditionalKey, UserType>, "properties" | "subcollections"> & {
5
5
  properties: Properties<M>;
6
6
  ownerId: string;
7
+ subcollections?: PersistedCollection<any, any>[];
7
8
  }
@@ -1,5 +1,11 @@
1
1
  import React, { useCallback } from "react";
2
- import { EntityCollection, FireCMSPlugin, User } from "@firecms/core";
2
+ import {
3
+ EntityCollection,
4
+ FireCMSPlugin,
5
+ makePropertiesEditable,
6
+ makePropertiesNonEditable,
7
+ User
8
+ } from "@firecms/core";
3
9
  import { ConfigControllerProvider } from "./ConfigControllerProvider";
4
10
  import { CollectionEditorPermissionsBuilder } from "./types/config_permissions";
5
11
  import { EditorCollectionAction } from "./components/EditorCollectionAction";
@@ -10,6 +16,8 @@ import { CollectionInference } from "./types/collection_inference";
10
16
  import { CollectionsConfigController } from "./types/config_controller";
11
17
  import { RootCollectionSuggestions } from "./components/RootCollectionSuggestions";
12
18
  import { joinCollectionLists } from "./utils/join_collections";
19
+ import { CollectionViewHeaderAction } from "./components/CollectionViewHeaderAction";
20
+ import { PropertyAddColumnComponent } from "./components/PropertyAddColumnComponent";
13
21
 
14
22
  export interface CollectionConfigControllerProps<EC extends PersistedCollection = PersistedCollection, UserType extends User = User> {
15
23
 
@@ -71,7 +79,15 @@ export function useCollectionEditorPlugin<EC extends PersistedCollection = Persi
71
79
  }: CollectionConfigControllerProps<EC, UserType>): FireCMSPlugin {
72
80
 
73
81
  const injectCollections = useCallback(
74
- (collections: EntityCollection[]) => joinCollectionLists(collectionConfigController.collections ?? [], collections),
82
+ (collections: EntityCollection[]) => {
83
+ const markAsEditable = (c: PersistedCollection) => {
84
+ makePropertiesEditable(c.properties);
85
+ c.subcollections?.forEach(markAsEditable);
86
+ };
87
+ const editableCollections = collectionConfigController.collections ?? [];
88
+ editableCollections.forEach(markAsEditable);
89
+ return joinCollectionLists(editableCollections, collections);
90
+ },
75
91
  [collectionConfigController.collections]);
76
92
 
77
93
  return {
@@ -98,6 +114,10 @@ export function useCollectionEditorPlugin<EC extends PersistedCollection = Persi
98
114
  additionalChildrenStart: <RootCollectionSuggestions/>,
99
115
  CollectionActions: HomePageEditorCollectionAction,
100
116
  AdditionalCards: NewCollectionCard,
117
+ },
118
+ collectionView: {
119
+ HeaderAction: CollectionViewHeaderAction,
120
+ AddColumnComponent: PropertyAddColumnComponent
101
121
  }
102
122
  };
103
123
  }
@@ -1,86 +1,49 @@
1
1
  import {
2
2
  EntityCollection,
3
- isPropertyBuilder, MapProperty,
3
+ isPropertyBuilder,
4
+ MapProperty,
4
5
  mergeDeep,
5
6
  PropertiesOrBuilders,
6
- Property, PropertyOrBuilder,
7
+ Property,
8
+ PropertyOrBuilder,
7
9
  sortProperties
8
10
  } from "@firecms/core";
9
- import { PersistedCollection } from "../types/persisted_collection";
10
11
 
11
12
  /**
12
13
  *
13
14
  * @param storedCollections
14
15
  * @param codedCollections
15
16
  */
16
- export function joinCollectionLists(storedCollections: PersistedCollection[], codedCollections: EntityCollection[] | undefined): EntityCollection[] {
17
- const resolvedFetchedCollections: PersistedCollection[] = storedCollections.map(c => ({
18
- ...c,
19
- editable: true,
20
- deletable: true
21
- }));
17
+ export function joinCollectionLists(storedCollections: EntityCollection[], codedCollections: EntityCollection[] | undefined): EntityCollection[] {
22
18
 
23
19
  // merge collections that are in both lists
24
20
  const updatedCollections = (codedCollections ?? [])
25
21
  .map((codedCollection) => {
26
- const storedCollection = resolvedFetchedCollections?.find((collection) => {
22
+ const storedCollection = storedCollections?.find((collection) => {
27
23
  return collection.path === codedCollection.path || (collection.alias && codedCollection.alias && collection.alias === codedCollection.alias);
28
24
  });
29
25
  if (!storedCollection) {
30
- return {
31
- ...codedCollection,
32
- properties: markPropertiesAsNonEditable(codedCollection.properties as PropertiesOrBuilders),
33
- deletable: false
34
- };
26
+ return codedCollection;
35
27
  } else {
36
- Object.values(storedCollection.properties).forEach((property) => {
37
- property.editable = true
38
- });
39
- const mergedCollection = mergeCollections(storedCollection, codedCollection);
40
- return { ...mergedCollection, deletable: false };
28
+ return mergeCollection(storedCollection, codedCollection);
41
29
  }
42
30
  });
43
31
 
44
32
  // fetched collections that are not in the base collections
45
- const resultStoredCollections = resolvedFetchedCollections
33
+ const resultStoredCollections = storedCollections
46
34
  .filter((col) => !updatedCollections.map(c => c.path).includes(col.path) || !updatedCollections.map(c => c.alias).includes(col.alias));
47
35
 
48
36
  return [...updatedCollections, ...resultStoredCollections];
49
37
  }
50
38
 
51
- function markPropertiesAsNonEditable(properties: PropertiesOrBuilders): PropertiesOrBuilders {
52
- return Object.entries(properties).reduce((acc, [key, property]) => {
53
- if (!isPropertyBuilder(property) && property.dataType === "map" && property.properties) {
54
- const updated = { ...property, properties: markPropertiesAsNonEditable(property.properties as PropertiesOrBuilders) };
55
- acc[key] = updated;
56
- }
57
- if (isPropertyBuilder(property)) {
58
- acc[key] = property;
59
- } else {
60
- acc[key] = { ...property, editable: false };
61
- }
62
- return acc;
63
- }, {} as PropertiesOrBuilders);
64
-
65
- }
66
-
67
39
  /**
68
40
  *
69
41
  * @param target
70
42
  * @param source
71
43
  */
72
- export function mergeCollections(target: EntityCollection, source: EntityCollection): EntityCollection {
73
- const subcollectionsMerged = target.subcollections?.map((targetSubcollection) => {
74
- const modifiedCollection =
75
- source.subcollections?.find((sourceSubcollection) => sourceSubcollection.path === targetSubcollection.path) ??
76
- source.subcollections?.find((sourceSubcollection) => sourceSubcollection.alias === targetSubcollection.alias);
44
+ export function mergeCollection(target: EntityCollection, source: EntityCollection): EntityCollection {
77
45
 
78
- if (!modifiedCollection) {
79
- return targetSubcollection;
80
- } else {
81
- return mergeCollections(targetSubcollection, modifiedCollection);
82
- }
83
- });
46
+ const subcollectionsMerged = joinCollectionLists(target?.subcollections ?? [], source?.subcollections ?? []);
84
47
 
85
48
  const propertiesMerged: PropertiesOrBuilders = { ...target.properties } as PropertiesOrBuilders;
86
49
  Object.keys(source.properties).forEach((key) => {