@firecms/core 3.0.0-alpha.80 → 3.0.0-alpha.82

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 (81) hide show
  1. package/dist/components/EntityCollectionTable/EntityCollectionTableProps.d.ts +3 -3
  2. package/dist/components/EntityCollectionTable/column_utils.d.ts +2 -2
  3. package/dist/components/EntityCollectionTable/fields/TableReferenceField.d.ts +5 -1
  4. package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +1 -1
  5. package/dist/components/EntityCollectionView/EntityCollectionViewActions.d.ts +1 -1
  6. package/dist/components/VirtualTable/VirtualTableProps.d.ts +1 -1
  7. package/dist/contexts/AnalyticsContext.d.ts +3 -0
  8. package/dist/contexts/CustomizationControllerContext.d.ts +3 -0
  9. package/dist/core/field_configs.d.ts +1 -1
  10. package/dist/hooks/index.d.ts +1 -0
  11. package/dist/hooks/useAnalyticsController.d.ts +5 -0
  12. package/dist/hooks/useCustomizationController.d.ts +11 -0
  13. package/dist/hooks/useFireCMSContext.d.ts +0 -1
  14. package/dist/index.es.js +4614 -4592
  15. package/dist/index.es.js.map +1 -1
  16. package/dist/index.umd.js +5 -5
  17. package/dist/index.umd.js.map +1 -1
  18. package/dist/internal/useBuildCustomizationController.d.ts +2 -0
  19. package/dist/types/analytics_controller.d.ts +7 -0
  20. package/dist/types/collections.d.ts +3 -0
  21. package/dist/types/customization_controller.d.ts +47 -0
  22. package/dist/types/entity_callbacks.d.ts +17 -3
  23. package/dist/types/firecms_context.d.ts +6 -46
  24. package/dist/types/index.d.ts +2 -0
  25. package/dist/util/icons.d.ts +4 -1
  26. package/dist/util/useTraceUpdate.d.ts +1 -1
  27. package/package.json +3 -3
  28. package/src/components/DeleteEntityDialog.tsx +9 -3
  29. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +28 -5
  30. package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +4 -5
  31. package/src/components/EntityCollectionTable/column_utils.tsx +3 -3
  32. package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +125 -122
  33. package/src/components/EntityCollectionTable/filters/DateTimeFilterField.tsx +2 -2
  34. package/src/components/EntityCollectionTable/internal/default_entity_actions.tsx +3 -3
  35. package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +4 -3
  36. package/src/components/EntityCollectionView/EntityCollectionView.tsx +74 -50
  37. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +5 -3
  38. package/src/components/EntityPreview.tsx +11 -7
  39. package/src/components/HomePage/DefaultHomePage.tsx +9 -8
  40. package/src/components/HomePage/NavigationCollectionCard.tsx +6 -5
  41. package/src/components/ReferenceSelectionInner.tsx +13 -9
  42. package/src/components/VirtualTable/VirtualTable.tsx +23 -20
  43. package/src/components/VirtualTable/VirtualTableCell.tsx +27 -9
  44. package/src/components/VirtualTable/VirtualTableHeader.tsx +1 -1
  45. package/src/components/VirtualTable/VirtualTableProps.tsx +2 -2
  46. package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +2 -2
  47. package/src/contexts/AnalyticsContext.tsx +4 -0
  48. package/src/contexts/CustomizationControllerContext.tsx +4 -0
  49. package/src/core/Drawer.tsx +7 -6
  50. package/src/core/FireCMS.tsx +39 -29
  51. package/src/form/EntityForm.tsx +16 -5
  52. package/src/form/PropertyFieldBinding.tsx +8 -10
  53. package/src/form/components/CustomIdField.tsx +7 -5
  54. package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +1 -2
  55. package/src/form/field_bindings/DateTimeFieldBinding.tsx +2 -2
  56. package/src/form/field_bindings/KeyValueFieldBinding.tsx +4 -6
  57. package/src/hooks/data/save.ts +8 -3
  58. package/src/hooks/index.tsx +1 -0
  59. package/src/hooks/useAnalyticsController.tsx +8 -0
  60. package/src/hooks/useCustomizationController.tsx +14 -0
  61. package/src/hooks/useFireCMSContext.tsx +8 -7
  62. package/src/internal/EntityView.tsx +4 -2
  63. package/src/internal/useBuildCustomizationController.tsx +5 -0
  64. package/src/internal/useBuildSideDialogsController.tsx +0 -1
  65. package/src/preview/PropertyPreview.tsx +4 -3
  66. package/src/preview/components/DatePreview.tsx +4 -5
  67. package/src/preview/components/ReferencePreview.tsx +17 -8
  68. package/src/preview/property_previews/ArrayOfMapsPreview.tsx +3 -3
  69. package/src/preview/property_previews/ArrayOfReferencesPreview.tsx +3 -3
  70. package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +3 -3
  71. package/src/preview/property_previews/ArrayOfStringsPreview.tsx +4 -4
  72. package/src/preview/property_previews/ArrayOneOfPreview.tsx +3 -3
  73. package/src/preview/property_previews/ArrayPropertyPreview.tsx +3 -3
  74. package/src/types/analytics_controller.tsx +10 -0
  75. package/src/types/collections.ts +3 -0
  76. package/src/types/customization_controller.tsx +58 -0
  77. package/src/types/entity_callbacks.ts +22 -3
  78. package/src/types/firecms_context.tsx +6 -51
  79. package/src/types/index.ts +2 -0
  80. package/src/util/icons.tsx +22 -19
  81. package/src/util/useTraceUpdate.tsx +5 -5
@@ -1,10 +1,12 @@
1
1
  import React, { useCallback, useState } from "react";
2
+ import equal from "react-fast-compare"
3
+
2
4
  import { ReferencePreview, ReferencePreviewContainer } from "../../../preview";
3
5
  import { CollectionSize, Entity, EntityCollection, EntityReference, FilterValues } from "../../../types";
4
6
 
5
7
  import { getPreviewSizeFrom } from "../../../preview/util";
6
8
  import { getReferenceFrom } from "../../../util";
7
- import { useFireCMSContext, useNavigationController, useReferenceDialog } from "../../../hooks";
9
+ import { useCustomizationController, useNavigationController, useReferenceDialog } from "../../../hooks";
8
10
  import { ErrorView } from "../../ErrorView";
9
11
  import { Button } from "@firecms/ui";
10
12
 
@@ -22,14 +24,14 @@ type TableReferenceFieldProps = {
22
24
  };
23
25
 
24
26
  export function TableReferenceField(props: TableReferenceFieldProps) {
27
+ const customizationController = useCustomizationController();
25
28
 
26
- const context = useFireCMSContext();
27
29
  const navigationController = useNavigationController();
28
30
  const { path } = props;
29
31
  const collection = navigationController.getCollection<EntityCollection>(path);
30
32
  if (!collection) {
31
- if (context.components?.missingReference) {
32
- return <context.components.missingReference path={path}/>;
33
+ if (customizationController.components?.missingReference) {
34
+ return <customizationController.components.missingReference path={path}/>;
33
35
  } else {
34
36
  throw Error(`Couldn't find the corresponding collection view for the path: ${path}`);
35
37
  }
@@ -37,123 +39,124 @@ export function TableReferenceField(props: TableReferenceFieldProps) {
37
39
  return <TableReferenceFieldSuccess {...props} collection={collection}/>;
38
40
  }
39
41
 
40
- function TableReferenceFieldSuccess(props: TableReferenceFieldProps & {
41
- collection: EntityCollection;
42
- }) {
43
- const {
44
- name,
45
- internalValue,
46
- updateValue,
47
- multiselect,
48
- path,
49
- size,
50
- previewProperties,
51
- title,
52
- disabled,
53
- forceFilter,
54
- collection
55
- } = props;
56
-
57
- const [onHover, setOnHover] = useState(false);
58
-
59
- const hoverTrue = useCallback(() => setOnHover(true), []);
60
- const hoverFalse = useCallback(() => setOnHover(false), []);
61
-
62
- const onSingleEntitySelected = useCallback((entity: Entity<any>) => {
63
- updateValue(entity ? getReferenceFrom(entity) : null);
64
- }, [updateValue]);
65
-
66
- const onMultipleEntitiesSelected = useCallback((entities: Entity<any>[]) => {
67
- updateValue(entities.map((e) => getReferenceFrom(e)));
68
- }, [updateValue]);
69
-
70
- const selectedEntityIds = internalValue
71
- ? (Array.isArray(internalValue)
72
- ? internalValue.map((ref) => ref.id)
73
- : internalValue.id ? [internalValue.id] : [])
74
- : [];
75
-
76
- const referenceDialogController = useReferenceDialog({
42
+ export const TableReferenceFieldSuccess = React.memo(
43
+ function TableReferenceFieldSuccess(props: TableReferenceFieldProps & {
44
+ collection: EntityCollection;
45
+ }) {
46
+ const {
47
+ name,
48
+ internalValue,
49
+ updateValue,
77
50
  multiselect,
78
51
  path,
79
- collection,
80
- onMultipleEntitiesSelected,
81
- onSingleEntitySelected,
82
- selectedEntityIds,
83
- forceFilter
84
- }
85
- );
86
-
87
- const handleOpen = useCallback(() => {
88
- if (disabled)
89
- return;
90
- referenceDialogController.open();
91
- }, [disabled, referenceDialogController]);
92
-
93
- const valueNotSet = !internalValue || (Array.isArray(internalValue) && internalValue.length === 0);
94
-
95
- const buildSingleReferenceField = () => {
96
- if (internalValue instanceof EntityReference)
97
- return <ReferencePreview
98
- onClick={disabled ? undefined : handleOpen}
99
- size={getPreviewSizeFrom(size)}
100
- reference={internalValue as EntityReference}
101
- onHover={onHover}
102
- disabled={!path}
103
- previewProperties={previewProperties}
104
- />;
105
- else
106
- return <ReferencePreviewContainer
107
- onClick={disabled ? undefined : handleOpen}
108
- size={getPreviewSizeFrom(size)}>
109
- <ErrorView title="Value is not a reference." error={"Click to edit"}/>
110
- </ReferencePreviewContainer>;
111
- };
112
-
113
- const buildMultipleReferenceField = () => {
114
- if (Array.isArray(internalValue))
115
- return <>
116
- {internalValue.map((reference, index) =>
117
- <div className="m-1 w-full"
118
- key={`preview_array_ref_${name}_${index}`}>
119
- <ReferencePreview
120
- onClick={disabled ? undefined : handleOpen}
121
- size={"tiny"}
122
- reference={reference}
123
- onHover={onHover}
124
- disabled={!path}
125
- previewProperties={previewProperties}
126
- />
127
- </div>
128
- )
129
- }
130
- </>;
131
- else
132
- return <ErrorView error={"Data is not an array of references"}/>;
133
- };
134
-
135
- if (!collection)
136
- return <ErrorView error={"The specified collection does not exist"}/>;
137
-
138
- return (
139
- <div className="w-full"
140
- onMouseEnter={hoverTrue}
141
- onMouseMove={hoverTrue}
142
- onMouseLeave={hoverFalse}>
143
-
144
- {internalValue && !multiselect && buildSingleReferenceField()}
145
-
146
- {internalValue && multiselect && buildMultipleReferenceField()}
147
-
148
- {valueNotSet &&
149
- <Button
150
- onClick={handleOpen}
151
- size={"small"}
152
- variant="outlined"
153
- color="primary">
154
- Edit {title}
155
- </Button>}
156
-
157
- </div>
158
- );
159
- }
52
+ size,
53
+ previewProperties,
54
+ title,
55
+ disabled,
56
+ forceFilter,
57
+ collection
58
+ } = props;
59
+
60
+ const [onHover, setOnHover] = useState(false);
61
+
62
+ const hoverTrue = useCallback(() => setOnHover(true), []);
63
+ const hoverFalse = useCallback(() => setOnHover(false), []);
64
+
65
+ const onSingleEntitySelected = useCallback((entity: Entity<any>) => {
66
+ updateValue(entity ? getReferenceFrom(entity) : null);
67
+ }, [updateValue]);
68
+
69
+ const onMultipleEntitiesSelected = useCallback((entities: Entity<any>[]) => {
70
+ updateValue(entities.map((e) => getReferenceFrom(e)));
71
+ }, [updateValue]);
72
+
73
+ const selectedEntityIds = internalValue
74
+ ? (Array.isArray(internalValue)
75
+ ? internalValue.map((ref) => ref.id)
76
+ : internalValue.id ? [internalValue.id] : [])
77
+ : [];
78
+
79
+ const referenceDialogController = useReferenceDialog({
80
+ multiselect,
81
+ path,
82
+ collection,
83
+ onMultipleEntitiesSelected,
84
+ onSingleEntitySelected,
85
+ selectedEntityIds,
86
+ forceFilter
87
+ }
88
+ );
89
+
90
+ const handleOpen = useCallback(() => {
91
+ if (disabled)
92
+ return;
93
+ referenceDialogController.open();
94
+ }, [disabled, referenceDialogController]);
95
+
96
+ const valueNotSet = !internalValue || (Array.isArray(internalValue) && internalValue.length === 0);
97
+
98
+ const buildSingleReferenceField = () => {
99
+ if (internalValue instanceof EntityReference)
100
+ return <ReferencePreview
101
+ onClick={disabled ? undefined : handleOpen}
102
+ size={getPreviewSizeFrom(size)}
103
+ reference={internalValue as EntityReference}
104
+ onHover={onHover}
105
+ disabled={!path}
106
+ previewProperties={previewProperties}
107
+ />;
108
+ else
109
+ return <ReferencePreviewContainer
110
+ onClick={disabled ? undefined : handleOpen}
111
+ size={getPreviewSizeFrom(size)}>
112
+ <ErrorView title="Value is not a reference." error={"Click to edit"}/>
113
+ </ReferencePreviewContainer>;
114
+ };
115
+
116
+ const buildMultipleReferenceField = () => {
117
+ if (Array.isArray(internalValue))
118
+ return <>
119
+ {internalValue.map((reference, index) =>
120
+ <div className="m-1 w-full"
121
+ key={`preview_array_ref_${name}_${index}`}>
122
+ <ReferencePreview
123
+ onClick={disabled ? undefined : handleOpen}
124
+ size={"tiny"}
125
+ reference={reference}
126
+ onHover={onHover}
127
+ disabled={!path}
128
+ previewProperties={previewProperties}
129
+ />
130
+ </div>
131
+ )
132
+ }
133
+ </>;
134
+ else
135
+ return <ErrorView error={"Data is not an array of references"}/>;
136
+ };
137
+
138
+ if (!collection)
139
+ return <ErrorView error={"The specified collection does not exist"}/>;
140
+
141
+ return (
142
+ <div className="w-full"
143
+ onMouseEnter={hoverTrue}
144
+ onMouseMove={hoverTrue}
145
+ onMouseLeave={hoverFalse}>
146
+
147
+ {internalValue && !multiselect && buildSingleReferenceField()}
148
+
149
+ {internalValue && multiselect && buildMultipleReferenceField()}
150
+
151
+ {valueNotSet &&
152
+ <Button
153
+ onClick={handleOpen}
154
+ size={"small"}
155
+ variant="outlined"
156
+ color="primary">
157
+ Edit {title}
158
+ </Button>}
159
+
160
+ </div>
161
+ );
162
+ }, equal);
@@ -1,7 +1,7 @@
1
1
  import React, { useState } from "react";
2
2
  import { VirtualTableWhereFilterOp } from "../../VirtualTable";
3
3
  import { DateTimeField, Select, SelectItem } from "@firecms/ui";
4
- import { useFireCMSContext } from "../../../hooks";
4
+ import { useCustomizationController } from "../../../hooks";
5
5
 
6
6
  interface DateTimeFilterFieldProps {
7
7
  name: string,
@@ -36,7 +36,7 @@ export function DateTimeFilterField({
36
36
  title
37
37
  }: DateTimeFilterFieldProps) {
38
38
 
39
- const { locale } = useFireCMSContext();
39
+ const { locale } = useCustomizationController();
40
40
  const possibleOperations: (keyof typeof operationLabels) [] = isArray
41
41
  ? ["array-contains"]
42
42
  : ["==", "!=", ">", "<", ">=", "<="];
@@ -14,7 +14,7 @@ export const editEntityAction: EntityAction = {
14
14
  unhighlightEntity
15
15
  }): Promise<void> {
16
16
  highlightEntity?.(entity);
17
- context.onAnalyticsEvent?.("entity_click", {
17
+ context.analyticsController?.onAnalyticsEvent?.("entity_click", {
18
18
  path: entity.path,
19
19
  entityId: entity.id
20
20
  });
@@ -40,7 +40,7 @@ export const copyEntityAction: EntityAction = {
40
40
  unhighlightEntity
41
41
  }): Promise<void> {
42
42
  highlightEntity?.(entity);
43
- context.onAnalyticsEvent?.("copy_entity_click", {
43
+ context.analyticsController?.onAnalyticsEvent?.("copy_entity_click", {
44
44
  path: entity.path,
45
45
  entityId: entity.id
46
46
  });
@@ -107,7 +107,7 @@ export const deleteEntityAction: EntityAction = {
107
107
  callbacks={collection.callbacks}
108
108
  open={open}
109
109
  onEntityDelete={() => {
110
- context.onAnalyticsEvent?.("single_entity_deleted", {
110
+ context.analyticsController?.onAnalyticsEvent?.("single_entity_deleted", {
111
111
  path: fullPath
112
112
  });
113
113
  selectionController?.setSelectedEntities(selectionController.selectedEntities.filter(e => e.id !== entity.id));
@@ -23,7 +23,7 @@ import { ErrorView, OnCellValueChangeParams } from "../../../../components";
23
23
  import { getPropertyInPath, isReadOnly, resolveCollection } from "../../../../util";
24
24
  import { Button, ClearIcon, DialogActions, IconButton, Typography } from "@firecms/ui";
25
25
  import { PropertyFieldBinding } from "../../../../form";
26
- import { useDataSource, useFireCMSContext } from "../../../../hooks";
26
+ import { useCustomizationController, useFireCMSContext } from "../../../../hooks";
27
27
 
28
28
  interface PopupFormFieldProps<M extends Record<string, any>> {
29
29
  entity?: Entity<M>;
@@ -65,6 +65,7 @@ export function PopupFormFieldInternal<M extends Record<string, any>>({
65
65
 
66
66
  // const dataSource = useDataSource();
67
67
  const fireCMSContext = useFireCMSContext();
68
+ const customizationController = useCustomizationController();
68
69
 
69
70
  const [savingError, setSavingError] = React.useState<any>();
70
71
  const [popupLocation, setPopupLocation] = useState<{
@@ -99,7 +100,7 @@ export function PopupFormFieldInternal<M extends Record<string, any>>({
99
100
  path,
100
101
  values: internalValue,
101
102
  entityId,
102
- fields: fireCMSContext.propertyConfigs
103
+ fields: customizationController.propertyConfigs
103
104
  })
104
105
  : undefined;
105
106
 
@@ -315,7 +316,7 @@ export function PopupFormFieldInternal<M extends Record<string, any>>({
315
316
  </div>
316
317
  </>;
317
318
 
318
- const plugins = fireCMSContext.plugins;
319
+ const plugins = customizationController.plugins;
319
320
  if (plugins) {
320
321
  // const formController: FormContext<M> = {
321
322
  // values,
@@ -38,15 +38,17 @@ import { ReferencePreview } from "../../preview";
38
38
  import {
39
39
  saveEntityWithCallbacks,
40
40
  useAuthController,
41
- useDataSource,
42
- useFireCMSContext,
41
+ useCustomizationController,
42
+ useDataSource, useFireCMSContext,
43
43
  useLargeLayout,
44
44
  useNavigationController,
45
45
  useSideEntityController
46
46
  } from "../../hooks";
47
47
  import { useUserConfigurationPersistence } from "../../hooks/useUserConfigurationPersistence";
48
48
  import { EntityCollectionViewActions } from "./EntityCollectionViewActions";
49
- import { useDataSourceEntityCollectionTableController } from "../EntityCollectionTable/useDataSourceEntityCollectionTableController";
49
+ import {
50
+ useDataSourceEntityCollectionTableController
51
+ } from "../EntityCollectionTable/useDataSourceEntityCollectionTableController";
50
52
  import {
51
53
  AddIcon,
52
54
  Button,
@@ -72,6 +74,7 @@ import {
72
74
  editEntityAction
73
75
  } from "../EntityCollectionTable/internal/default_entity_actions";
74
76
  import { DeleteEntityDialog } from "../DeleteEntityDialog";
77
+ import { useAnalyticsController } from "../../hooks/useAnalyticsController";
75
78
 
76
79
  const COLLECTION_GROUP_PARENT_ID = "collectionGroupParent";
77
80
 
@@ -124,6 +127,8 @@ export const EntityCollectionView = React.memo(
124
127
  const sideEntityController = useSideEntityController();
125
128
  const authController = useAuthController();
126
129
  const userConfigPersistence = useUserConfigurationPersistence();
130
+ const analyticsController = useAnalyticsController();
131
+ const customizationController = useCustomizationController();
127
132
  const context = useFireCMSContext();
128
133
 
129
134
  const containerRef = React.useRef<HTMLDivElement>(null);
@@ -133,6 +138,11 @@ export const EntityCollectionView = React.memo(
133
138
  return (userOverride ? mergeDeep(collectionProp, userOverride) : collectionProp) as EntityCollection<M>;
134
139
  }, [collectionProp, fullPath, userConfigPersistence?.getCollectionConfig]);
135
140
 
141
+ const collectionRef = React.useRef(collection);
142
+ useEffect(() => {
143
+ collectionRef.current = collection;
144
+ }, [collection]);
145
+
136
146
  const canCreateEntities = canCreateEntity(collection, authController, fullPathToCollectionSegments(fullPath), null);
137
147
  const [selectedNavigationEntity, setSelectedNavigationEntity] = useState<Entity<M> | undefined>(undefined);
138
148
  const [deleteEntityClicked, setDeleteEntityClicked] = React.useState<Entity<M> | Entity<M>[] | undefined>(undefined);
@@ -152,11 +162,12 @@ export const EntityCollectionView = React.memo(
152
162
  }, [selectedNavigationEntity]);
153
163
 
154
164
  const checkInlineEditing = useCallback((entity?: Entity<any>): boolean => {
165
+ const collection = collectionRef.current;
155
166
  if (!canEditEntity(collection, authController, fullPathToCollectionSegments(fullPath), entity ?? null)) {
156
167
  return false;
157
168
  }
158
169
  return collection.inlineEditing === undefined || collection.inlineEditing;
159
- }, [collection, authController, fullPath]);
170
+ }, [authController, fullPath]);
160
171
 
161
172
  const selectionEnabled = collection.selectionEnabled === undefined || collection.selectionEnabled;
162
173
  const hoverRow = !checkInlineEditing();
@@ -178,7 +189,7 @@ export const EntityCollectionView = React.memo(
178
189
 
179
190
  const tableController = useDataSourceEntityCollectionTableController<M>({
180
191
  fullPath,
181
- collection,
192
+ collection: collectionRef.current,
182
193
  entitiesDisplayedFirst: [],
183
194
  lastDeleteTimestamp
184
195
  });
@@ -191,8 +202,9 @@ export const EntityCollectionView = React.memo(
191
202
  }, [tableController.setPopupCell]);
192
203
 
193
204
  const onEntityClick = useCallback((clickedEntity: Entity<M>) => {
205
+ const collection = collectionRef.current;
194
206
  setSelectedNavigationEntity(clickedEntity);
195
- context.onAnalyticsEvent?.("edit_entity_clicked", {
207
+ analyticsController.onAnalyticsEvent?.("edit_entity_clicked", {
196
208
  path: clickedEntity.path,
197
209
  entityId: clickedEntity.id
198
210
  });
@@ -203,10 +215,12 @@ export const EntityCollectionView = React.memo(
203
215
  updateUrl: true,
204
216
  onClose: unselectNavigatedEntity
205
217
  });
206
- }, [fullPath, collection, sideEntityController]);
218
+ }, [unselectNavigatedEntity]);
207
219
 
208
220
  const onNewClick = useCallback(() => {
209
- context.onAnalyticsEvent?.("new_entity_click", {
221
+
222
+ const collection = collectionRef.current;
223
+ analyticsController.onAnalyticsEvent?.("new_entity_click", {
210
224
  path: fullPath
211
225
  });
212
226
  sideEntityController.open({
@@ -215,38 +229,31 @@ export const EntityCollectionView = React.memo(
215
229
  updateUrl: true,
216
230
  onClose: unselectNavigatedEntity
217
231
  });
218
- }, [fullPath, collection, sideEntityController]);
219
-
220
- const onSingleDeleteClick = useCallback((selectedEntity: Entity<any>) => {
221
- context.onAnalyticsEvent?.("single_delete_dialog_open", {
222
- path: fullPath
223
- });
224
- setDeleteEntityClicked(selectedEntity);
225
- }, [context, fullPath]);
232
+ }, [fullPath]);
226
233
 
227
- const onMultipleDeleteClick = useCallback(() => {
228
- context.onAnalyticsEvent?.("multiple_delete_dialog_open", {
234
+ const onMultipleDeleteClick = () => {
235
+ analyticsController.onAnalyticsEvent?.("multiple_delete_dialog_open", {
229
236
  path: fullPath
230
237
  });
231
238
  setDeleteEntityClicked(selectedEntities);
232
- }, [context, fullPath, selectedEntities]);
239
+ };
233
240
 
234
- const internalOnEntityDelete = useCallback((_path: string, entity: Entity<M>) => {
235
- context.onAnalyticsEvent?.("single_entity_deleted", {
241
+ const internalOnEntityDelete = (_path: string, entity: Entity<M>) => {
242
+ analyticsController.onAnalyticsEvent?.("single_entity_deleted", {
236
243
  path: fullPath
237
244
  });
238
245
  setSelectedEntities((selectedEntities) => selectedEntities.filter((e) => e.id !== entity.id));
239
246
  setLastDeleteTimestamp(Date.now());
240
- }, [context, fullPath, setSelectedEntities]);
247
+ };
241
248
 
242
- const internalOnMultipleEntitiesDelete = useCallback((_path: string, entities: Entity<M>[]) => {
243
- context.onAnalyticsEvent?.("multiple_entities_deleted", {
249
+ const internalOnMultipleEntitiesDelete = (_path: string, entities: Entity<M>[]) => {
250
+ analyticsController.onAnalyticsEvent?.("multiple_entities_deleted", {
244
251
  path: fullPath
245
252
  });
246
253
  setSelectedEntities([]);
247
254
  setDeleteEntityClicked(undefined);
248
255
  setLastDeleteTimestamp(Date.now());
249
- }, [setSelectedEntities]);
256
+ };
250
257
 
251
258
  let AddColumnComponent: React.ComponentType<{
252
259
  fullPath: string,
@@ -255,8 +262,8 @@ export const EntityCollectionView = React.memo(
255
262
  }> | undefined
256
263
 
257
264
  // we are only using the first plugin that implements this
258
- if (context?.plugins) {
259
- AddColumnComponent = context.plugins.find(plugin => plugin.collectionView?.AddColumnComponent)?.collectionView?.AddColumnComponent;
265
+ if (customizationController?.plugins) {
266
+ AddColumnComponent = customizationController.plugins.find(plugin => plugin.collectionView?.AddColumnComponent)?.collectionView?.AddColumnComponent;
260
267
  }
261
268
 
262
269
  const onCollectionModifiedForUser = useCallback((path: string, partialCollection: PartialEntityCollection<M>) => {
@@ -271,11 +278,13 @@ export const EntityCollectionView = React.memo(
271
278
  width,
272
279
  key
273
280
  }: OnColumnResizeParams) => {
281
+
282
+ const collection = collectionRef.current;
274
283
  // Only for property columns
275
284
  if (!getPropertyInPath(collection.properties, key)) return;
276
285
  const localCollection = buildPropertyWidthOverwrite(key, width);
277
286
  onCollectionModifiedForUser(fullPath, localCollection);
278
- }, [collection, onCollectionModifiedForUser, fullPath]);
287
+ }, [onCollectionModifiedForUser, fullPath]);
279
288
 
280
289
  const onSizeChanged = useCallback((size: CollectionSize) => {
281
290
  if (userConfigPersistence)
@@ -334,7 +343,7 @@ export const EntityCollectionView = React.memo(
334
343
  const resolvedCollection = useMemo(() => resolveCollection<M>({
335
344
  collection,
336
345
  path: fullPath,
337
- fields: context.propertyConfigs
346
+ fields: customizationController.propertyConfigs
338
347
  }), [collection, fullPath]);
339
348
 
340
349
  const getPropertyFor = useCallback(({
@@ -357,9 +366,9 @@ export const EntityCollectionView = React.memo(
357
366
  propertyValue,
358
367
  values: entity.values,
359
368
  entityId: entity.id,
360
- fields: context.propertyConfigs
369
+ fields: customizationController.propertyConfigs
361
370
  });
362
- }, [collection.properties, context.propertyConfigs, fullPath, resolvedCollection.properties]);
371
+ }, [collection.properties, customizationController.propertyConfigs, fullPath, resolvedCollection.properties]);
363
372
 
364
373
  const displayedColumnIds = useColumnIds(resolvedCollection, true);
365
374
 
@@ -448,7 +457,7 @@ export const EntityCollectionView = React.memo(
448
457
  const uncollapsedActions = entityActions.filter(a => a.collapsed === false);
449
458
  const actionsWidth = uncollapsedActions.length * (largeLayout ? 40 : 30);
450
459
  return (largeLayout ? (80 + actionsWidth) : (70 + actionsWidth)) + (collapsedActions.length > 0 ? (largeLayout ? 40 : 30) : 0);
451
- }, [largeLayout, getActionsForEntity]);
460
+ }, [largeLayout]);
452
461
 
453
462
  const tableRowActionsBuilder = useCallback(({
454
463
  entity,
@@ -500,8 +509,7 @@ export const EntityCollectionView = React.memo(
500
509
  setPopOverOpen(true);
501
510
  e.stopPropagation();
502
511
  }
503
- : undefined}
504
- >
512
+ : undefined}>
505
513
  {`${collection.name}`}
506
514
  </Typography>
507
515
 
@@ -522,19 +530,20 @@ export const EntityCollectionView = React.memo(
522
530
 
523
531
  </Popover>;
524
532
 
525
- function buildAdditionalHeaderWidget({
526
- property,
527
- propertyKey,
528
- onHover
529
- }: {
533
+ const buildAdditionalHeaderWidget = useCallback(({
534
+ property,
535
+ propertyKey,
536
+ onHover
537
+ }: {
530
538
  property: ResolvedProperty,
531
539
  propertyKey: string,
532
540
  onHover: boolean
533
- }) {
534
- if (!context.plugins)
541
+ }) => {
542
+ const collection = collectionRef.current;
543
+ if (!customizationController.plugins)
535
544
  return null;
536
545
  return <>
537
- {context.plugins.filter(plugin => plugin.collectionView?.HeaderAction)
546
+ {customizationController.plugins.filter(plugin => plugin.collectionView?.HeaderAction)
538
547
  .map((plugin, i) => {
539
548
  const HeaderAction = plugin.collectionView!.HeaderAction!;
540
549
  return <HeaderAction
@@ -547,7 +556,7 @@ export const EntityCollectionView = React.memo(
547
556
  parentCollectionIds={parentCollectionIds ?? []}/>;
548
557
  })}
549
558
  </>;
550
- }
559
+ }, [customizationController.plugins, fullPath, parentCollectionIds]);
551
560
 
552
561
  const addColumnComponentInternal = AddColumnComponent
553
562
  ? function () {
@@ -562,13 +571,13 @@ export const EntityCollectionView = React.memo(
562
571
  const [textSearchInitialised, setTextSearchInitialised] = useState<boolean>(false);
563
572
  let onTextSearchClick: (() => void) | undefined;
564
573
  let textSearchEnabled = Boolean(collection.textSearchEnabled);
565
- if (context?.plugins) {
566
- const addTextSearchClickListener = context.plugins?.find(p => Boolean(p.collectionView?.onTextSearchClick));
574
+ if (customizationController?.plugins) {
575
+ const addTextSearchClickListener = customizationController.plugins?.find(p => Boolean(p.collectionView?.onTextSearchClick));
567
576
 
568
577
  onTextSearchClick = addTextSearchClickListener
569
578
  ? () => {
570
579
  setTextSearchLoading(true);
571
- Promise.all(context.plugins?.map(p => {
580
+ Promise.all(customizationController.plugins?.map(p => {
572
581
  if (p.collectionView?.onTextSearchClick)
573
582
  return p.collectionView.onTextSearchClick({ context, path: resolvedFullPath, collection, parentCollectionIds });
574
583
  return Promise.resolve(true);
@@ -580,7 +589,7 @@ export const EntityCollectionView = React.memo(
580
589
  }
581
590
  : undefined;
582
591
 
583
- context.plugins?.forEach(p => {
592
+ customizationController.plugins?.forEach(p => {
584
593
  if (!textSearchEnabled)
585
594
  if (p.collectionView?.showTextSearchBar) {
586
595
  textSearchEnabled = p.collectionView.showTextSearchBar({ context, path: resolvedFullPath, collection, parentCollectionIds });
@@ -675,9 +684,24 @@ export const EntityCollectionView = React.memo(
675
684
 
676
685
  </div>
677
686
  );
678
- }, equal) as React.FunctionComponent<EntityCollectionViewProps<any>>
679
-
680
- export function useSelectionController<M extends Record<string, any>>(): SelectionController<M> {
687
+ }, (a, b) => {
688
+ return equal(a.fullPath, b.fullPath) &&
689
+ equal(a.parentCollectionIds, b.parentCollectionIds) &&
690
+ equal(a.isSubCollection, b.isSubCollection) &&
691
+ equal(a.className, b.className) &&
692
+ equal(a.properties, b.properties) &&
693
+ equal(a.hideIdFromCollection, b.hideIdFromCollection) &&
694
+ equal(a.inlineEditing, b.inlineEditing) &&
695
+ equal(a.selectionEnabled, b.selectionEnabled) &&
696
+ equal(a.selectionController, b.selectionController) &&
697
+ equal(a.Actions, b.Actions) &&
698
+ equal(a.defaultSize, b.defaultSize) &&
699
+ equal(a.textSearchEnabled, b.textSearchEnabled) &&
700
+ equal(a.additionalFields, b.additionalFields) &&
701
+ equal(a.forceFilter, b.forceFilter);
702
+ }) as React.FunctionComponent<EntityCollectionViewProps<any>>
703
+
704
+ export function useSelectionController<M extends Record<string, any> = any>(): SelectionController<M> {
681
705
 
682
706
  const [selectedEntities, setSelectedEntities] = useState<Entity<M>[]>([]);
683
707