@firecms/core 3.0.0-canary.5 → 3.0.0-canary.51

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 (218) hide show
  1. package/README.md +2 -2
  2. package/dist/components/ClearFilterSortButton.d.ts +5 -0
  3. package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +11 -11
  4. package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +2 -2
  5. package/dist/components/EntityCollectionTable/EntityCollectionTableProps.d.ts +5 -3
  6. package/dist/components/EntityCollectionTable/PropertyTableCell.d.ts +3 -2
  7. package/dist/components/EntityCollectionTable/column_utils.d.ts +1 -2
  8. package/dist/components/EntityCollectionTable/internal/CollectionTableToolbar.d.ts +1 -4
  9. package/dist/components/EntityCollectionTable/internal/popup_field/PopupFormField.d.ts +1 -1
  10. package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +12 -3
  11. package/dist/components/EntityCollectionView/EntityCollectionViewStartActions.d.ts +11 -0
  12. package/dist/components/EntityCollectionView/useSelectionController.d.ts +2 -0
  13. package/dist/components/EntityPreview.d.ts +25 -7
  14. package/dist/components/EntityView.d.ts +11 -0
  15. package/dist/components/FieldCaption.d.ts +5 -0
  16. package/dist/components/FireCMSAppBar.d.ts +3 -2
  17. package/dist/components/HomePage/NavigationCard.d.ts +8 -0
  18. package/dist/components/HomePage/{NavigationCollectionCard.d.ts → NavigationCardBinding.d.ts} +2 -2
  19. package/dist/components/HomePage/SmallNavigationCard.d.ts +6 -0
  20. package/dist/components/HomePage/index.d.ts +3 -1
  21. package/dist/components/SelectableTable/SelectableTable.d.ts +1 -1
  22. package/dist/components/VirtualTable/VirtualTableProps.d.ts +1 -1
  23. package/dist/components/common/types.d.ts +4 -6
  24. package/dist/components/common/useDataSourceEntityCollectionTableController.d.ts +3 -0
  25. package/dist/components/index.d.ts +4 -2
  26. package/dist/contexts/AuthControllerContext.d.ts +1 -1
  27. package/dist/core/Drawer.d.ts +5 -12
  28. package/dist/core/DrawerNavigationItem.d.ts +9 -0
  29. package/dist/core/{EntityView.d.ts → EntityEditView.d.ts} +2 -2
  30. package/dist/core/NavigationRoutes.d.ts +1 -1
  31. package/dist/core/Scaffold.d.ts +7 -10
  32. package/dist/core/index.d.ts +3 -4
  33. package/dist/form/EntityForm.d.ts +1 -1
  34. package/dist/form/components/ErrorFocus.d.ts +1 -1
  35. package/dist/form/components/StorageItemPreview.d.ts +3 -2
  36. package/dist/form/components/StorageUploadProgress.d.ts +1 -1
  37. package/dist/form/field_bindings/KeyValueFieldBinding.d.ts +1 -1
  38. package/dist/form/field_bindings/MapFieldBinding.d.ts +1 -1
  39. package/dist/form/field_bindings/StorageUploadFieldBinding.d.ts +4 -3
  40. package/dist/form/field_bindings/TextFieldBinding.d.ts +2 -2
  41. package/dist/form/validation.d.ts +1 -1
  42. package/dist/hooks/data/delete.d.ts +2 -2
  43. package/dist/hooks/data/save.d.ts +2 -3
  44. package/dist/hooks/data/useDataSource.d.ts +2 -2
  45. package/dist/hooks/data/useEntityFetch.d.ts +3 -3
  46. package/dist/hooks/index.d.ts +2 -0
  47. package/dist/hooks/useBuildNavigationController.d.ts +6 -4
  48. package/dist/hooks/useProjectLog.d.ts +6 -2
  49. package/dist/hooks/useStorageSource.d.ts +2 -2
  50. package/dist/hooks/useValidateAuthenticator.d.ts +21 -0
  51. package/dist/index.es.js +9644 -9122
  52. package/dist/index.es.js.map +1 -1
  53. package/dist/index.umd.js +5 -5
  54. package/dist/index.umd.js.map +1 -1
  55. package/dist/internal/useBuildDataSource.d.ts +1 -12
  56. package/dist/preview/PropertyPreview.d.ts +1 -1
  57. package/dist/preview/PropertyPreviewProps.d.ts +1 -4
  58. package/dist/preview/components/BooleanPreview.d.ts +5 -1
  59. package/dist/preview/components/EnumValuesChip.d.ts +1 -1
  60. package/dist/preview/components/ReferencePreview.d.ts +1 -7
  61. package/dist/types/analytics.d.ts +1 -1
  62. package/dist/types/auth.d.ts +37 -1
  63. package/dist/types/collections.d.ts +29 -5
  64. package/dist/types/datasource.d.ts +3 -6
  65. package/dist/types/entities.d.ts +5 -1
  66. package/dist/types/entity_actions.d.ts +14 -0
  67. package/dist/types/entity_callbacks.d.ts +2 -2
  68. package/dist/types/entity_overrides.d.ts +6 -0
  69. package/dist/types/index.d.ts +2 -1
  70. package/dist/types/navigation.d.ts +14 -13
  71. package/dist/types/permissions.d.ts +5 -1
  72. package/dist/types/plugins.d.ts +20 -20
  73. package/dist/types/properties.d.ts +4 -4
  74. package/dist/types/property_config.d.ts +2 -2
  75. package/dist/types/roles.d.ts +31 -0
  76. package/dist/types/storage.d.ts +11 -3
  77. package/dist/types/user.d.ts +5 -0
  78. package/dist/util/collections.d.ts +9 -1
  79. package/dist/util/entities.d.ts +1 -1
  80. package/dist/util/icon_synonyms.d.ts +2 -4
  81. package/dist/util/icons.d.ts +8 -2
  82. package/dist/util/navigation_utils.d.ts +2 -2
  83. package/dist/util/permissions.d.ts +4 -4
  84. package/dist/util/references.d.ts +4 -2
  85. package/dist/util/resolutions.d.ts +9 -13
  86. package/dist/util/useTraceUpdate.d.ts +1 -0
  87. package/package.json +139 -119
  88. package/src/components/ClearFilterSortButton.tsx +41 -0
  89. package/src/components/DeleteEntityDialog.tsx +4 -4
  90. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +2 -2
  91. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +275 -278
  92. package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +9 -5
  93. package/src/components/EntityCollectionTable/PropertyTableCell.tsx +44 -44
  94. package/src/components/EntityCollectionTable/column_utils.tsx +3 -3
  95. package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +9 -16
  96. package/src/components/EntityCollectionTable/fields/TableStorageUpload.tsx +3 -3
  97. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +27 -32
  98. package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +11 -6
  99. package/src/components/EntityCollectionTable/internal/default_entity_actions.tsx +9 -5
  100. package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +2 -4
  101. package/src/components/EntityCollectionView/EntityCollectionView.tsx +560 -554
  102. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +5 -6
  103. package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +68 -0
  104. package/src/components/EntityCollectionView/useSelectionController.tsx +30 -0
  105. package/src/components/EntityPreview.tsx +207 -70
  106. package/src/components/EntityView.tsx +84 -0
  107. package/src/components/FieldCaption.tsx +14 -0
  108. package/src/components/FireCMSAppBar.tsx +33 -11
  109. package/src/components/HomePage/DefaultHomePage.tsx +15 -11
  110. package/src/components/HomePage/NavigationCard.tsx +69 -0
  111. package/src/components/HomePage/NavigationCardBinding.tsx +116 -0
  112. package/src/components/HomePage/SmallNavigationCard.tsx +45 -0
  113. package/src/components/HomePage/index.tsx +3 -1
  114. package/src/components/PropertyIdCopyTooltipContent.tsx +2 -3
  115. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +4 -4
  116. package/src/components/ReferenceWidget.tsx +5 -5
  117. package/src/components/SearchIconsView.tsx +4 -4
  118. package/src/components/SelectableTable/SelectableTable.tsx +1 -1
  119. package/src/components/SelectableTable/filters/BooleanFilterField.tsx +2 -3
  120. package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +23 -8
  121. package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +35 -24
  122. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +35 -15
  123. package/src/components/VirtualTable/VirtualTable.tsx +28 -20
  124. package/src/components/VirtualTable/VirtualTableProps.tsx +1 -1
  125. package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +1 -1
  126. package/src/components/common/types.tsx +4 -6
  127. package/src/components/common/useDataSourceEntityCollectionTableController.tsx +12 -1
  128. package/src/components/index.tsx +4 -2
  129. package/src/contexts/AuthControllerContext.tsx +1 -1
  130. package/src/core/Drawer.tsx +78 -103
  131. package/src/core/DrawerNavigationItem.tsx +62 -0
  132. package/src/core/{EntityView.tsx → EntityEditView.tsx} +21 -40
  133. package/src/core/EntitySidePanel.tsx +2 -2
  134. package/src/core/FireCMS.tsx +22 -7
  135. package/src/core/NavigationRoutes.tsx +11 -4
  136. package/src/core/Scaffold.tsx +76 -61
  137. package/src/core/field_configs.tsx +1 -2
  138. package/src/core/index.tsx +3 -4
  139. package/src/form/EntityForm.tsx +42 -22
  140. package/src/form/PropertyFieldBinding.tsx +0 -2
  141. package/src/form/components/StorageItemPreview.tsx +5 -3
  142. package/src/form/components/StorageUploadProgress.tsx +7 -6
  143. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +8 -12
  144. package/src/form/field_bindings/DateTimeFieldBinding.tsx +1 -1
  145. package/src/form/field_bindings/KeyValueFieldBinding.tsx +15 -15
  146. package/src/form/field_bindings/MapFieldBinding.tsx +15 -15
  147. package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +1 -1
  148. package/src/form/field_bindings/ReferenceFieldBinding.tsx +1 -0
  149. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +14 -5
  150. package/src/form/field_bindings/TextFieldBinding.tsx +7 -5
  151. package/src/form/validation.ts +3 -4
  152. package/src/hooks/data/delete.ts +3 -3
  153. package/src/hooks/data/save.ts +2 -2
  154. package/src/hooks/data/useCollectionFetch.tsx +1 -1
  155. package/src/hooks/data/useDataSource.tsx +8 -3
  156. package/src/hooks/data/useEntityFetch.tsx +4 -4
  157. package/src/hooks/index.tsx +3 -0
  158. package/src/hooks/useBuildLocalConfigurationPersistence.tsx +9 -10
  159. package/src/hooks/useBuildModeController.tsx +11 -5
  160. package/src/hooks/useBuildNavigationController.tsx +199 -81
  161. package/src/hooks/useProjectLog.tsx +17 -7
  162. package/src/hooks/useReferenceDialog.tsx +2 -2
  163. package/src/hooks/useStorageSource.tsx +7 -2
  164. package/src/hooks/useValidateAuthenticator.tsx +115 -0
  165. package/src/internal/useBuildDataSource.ts +42 -44
  166. package/src/internal/useBuildSideEntityController.tsx +86 -20
  167. package/src/preview/PropertyPreview.tsx +3 -14
  168. package/src/preview/PropertyPreviewProps.tsx +1 -11
  169. package/src/preview/components/BooleanPreview.tsx +19 -4
  170. package/src/preview/components/EnumValuesChip.tsx +1 -1
  171. package/src/preview/components/ReferencePreview.tsx +55 -147
  172. package/src/preview/property_previews/ArrayOfMapsPreview.tsx +0 -1
  173. package/src/preview/property_previews/ArrayOfReferencesPreview.tsx +0 -1
  174. package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +0 -1
  175. package/src/preview/property_previews/ArrayOfStringsPreview.tsx +0 -1
  176. package/src/preview/property_previews/ArrayOneOfPreview.tsx +0 -1
  177. package/src/preview/property_previews/ArrayPropertyPreview.tsx +0 -1
  178. package/src/preview/property_previews/StringPropertyPreview.tsx +8 -7
  179. package/src/types/analytics.ts +1 -0
  180. package/src/types/auth.tsx +50 -1
  181. package/src/types/collections.ts +33 -5
  182. package/src/types/datasource.ts +8 -5
  183. package/src/types/entities.ts +9 -1
  184. package/src/types/entity_actions.tsx +17 -0
  185. package/src/types/entity_callbacks.ts +2 -2
  186. package/src/types/entity_overrides.tsx +7 -0
  187. package/src/types/firecms.tsx +0 -1
  188. package/src/types/index.ts +2 -1
  189. package/src/types/navigation.ts +17 -16
  190. package/src/types/permissions.ts +6 -1
  191. package/src/types/plugins.tsx +26 -28
  192. package/src/types/properties.ts +8 -6
  193. package/src/types/property_config.tsx +2 -2
  194. package/src/types/roles.ts +41 -0
  195. package/src/types/side_entity_controller.tsx +1 -0
  196. package/src/types/storage.ts +12 -3
  197. package/src/types/user.ts +7 -0
  198. package/src/util/collections.ts +22 -0
  199. package/src/util/entities.ts +1 -1
  200. package/src/util/icon_list.ts +2 -2
  201. package/src/util/icon_synonyms.ts +4 -6
  202. package/src/util/icons.tsx +11 -3
  203. package/src/util/navigation_utils.ts +6 -6
  204. package/src/util/objects.ts +0 -14
  205. package/src/util/permissions.ts +11 -8
  206. package/src/util/references.ts +36 -5
  207. package/src/util/resolutions.ts +6 -24
  208. package/src/util/strings.ts +2 -2
  209. package/src/util/useTraceUpdate.tsx +2 -1
  210. package/dist/core/SideEntityView.d.ts +0 -7
  211. package/dist/internal/useBuildCustomizationController.d.ts +0 -2
  212. package/dist/internal/useLocaleConfig.d.ts +0 -1
  213. package/dist/types/appcheck.d.ts +0 -26
  214. package/src/components/HomePage/NavigationCollectionCard.tsx +0 -146
  215. package/src/core/SideEntityView.tsx +0 -38
  216. package/src/internal/useBuildCustomizationController.tsx +0 -5
  217. package/src/internal/useLocaleConfig.tsx +0 -18
  218. package/src/types/appcheck.ts +0 -29
@@ -12,8 +12,7 @@ import {
12
12
  PartialEntityCollection,
13
13
  PropertyOrBuilder,
14
14
  ResolvedProperty,
15
- SaveEntityProps,
16
- SelectionController
15
+ SaveEntityProps
17
16
  } from "../../types";
18
17
  import {
19
18
  EntityCollectionRowActions,
@@ -25,7 +24,6 @@ import {
25
24
  canCreateEntity,
26
25
  canDeleteEntity,
27
26
  canEditEntity,
28
- fullPathToCollectionSegments,
29
27
  getPropertyInPath,
30
28
  mergeDeep,
31
29
  resolveCollection,
@@ -37,6 +35,7 @@ import {
37
35
  useAuthController,
38
36
  useCustomizationController,
39
37
  useDataSource,
38
+ useFireCMSContext,
40
39
  useLargeLayout,
41
40
  useNavigationController,
42
41
  useSideEntityController
@@ -74,6 +73,8 @@ import {
74
73
  } from "../EntityCollectionTable/internal/default_entity_actions";
75
74
  import { DeleteEntityDialog } from "../DeleteEntityDialog";
76
75
  import { useAnalyticsController } from "../../hooks/useAnalyticsController";
76
+ import { useSelectionController } from "./useSelectionController";
77
+ import { EntityCollectionViewStartActions } from "./EntityCollectionViewStartActions";
77
78
 
78
79
  const COLLECTION_GROUP_PARENT_ID = "collectionGroupParent";
79
80
 
@@ -81,10 +82,22 @@ const COLLECTION_GROUP_PARENT_ID = "collectionGroupParent";
81
82
  * @group Components
82
83
  */
83
84
  export type EntityCollectionViewProps<M extends Record<string, any>> = {
84
- fullPath: string;
85
+ /**
86
+ * Complete path where this collection is located.
87
+ * It defaults to the collection path if not provided.
88
+ */
89
+ fullPath?: string;
90
+ /**
91
+ * If this is a subcollection, specify the parent collection ids.
92
+ */
85
93
  parentCollectionIds?: string[];
94
+ /**
95
+ * Whether this is a subcollection or not.
96
+ */
86
97
  isSubCollection?: boolean;
98
+
87
99
  className?: string;
100
+
88
101
  } & EntityCollection<M>;
89
102
 
90
103
  /**
@@ -113,7 +126,7 @@ export type EntityCollectionViewProps<M extends Record<string, any>> = {
113
126
  */
114
127
  export const EntityCollectionView = React.memo(
115
128
  function EntityCollectionView<M extends Record<string, any>>({
116
- fullPath,
129
+ fullPath: fullPathProp,
117
130
  parentCollectionIds,
118
131
  isSubCollection,
119
132
  className,
@@ -121,545 +134,560 @@ export const EntityCollectionView = React.memo(
121
134
  }: EntityCollectionViewProps<M>
122
135
  ) {
123
136
 
124
- const dataSource = useDataSource();
125
- const navigation = useNavigationController();
126
- const sideEntityController = useSideEntityController();
127
- const authController = useAuthController();
128
- const userConfigPersistence = useUserConfigurationPersistence();
129
- const analyticsController = useAnalyticsController();
130
- const customizationController = useCustomizationController();
131
-
132
- const containerRef = React.useRef<HTMLDivElement>(null);
133
-
134
- const collection = useMemo(() => {
135
- const userOverride = userConfigPersistence?.getCollectionConfig<M>(fullPath);
136
- return (userOverride ? mergeDeep(collectionProp, userOverride) : collectionProp) as EntityCollection<M>;
137
- }, [collectionProp, fullPath, userConfigPersistence?.getCollectionConfig]);
138
-
139
- const collectionRef = React.useRef(collection);
140
- useEffect(() => {
141
- collectionRef.current = collection;
142
- }, [collection]);
143
-
144
- const canCreateEntities = canCreateEntity(collection, authController, fullPathToCollectionSegments(fullPath), null);
145
- const [selectedNavigationEntity, setSelectedNavigationEntity] = useState<Entity<M> | undefined>(undefined);
146
- const [deleteEntityClicked, setDeleteEntityClicked] = React.useState<Entity<M> | Entity<M>[] | undefined>(undefined);
147
-
148
- const [lastDeleteTimestamp, setLastDeleteTimestamp] = React.useState<number>(0);
149
-
150
- // number of entities in the collection
151
- const [docsCount, setDocsCount] = useState<number>(0);
152
-
153
- const unselectNavigatedEntity = useCallback(() => {
154
- const currentSelection = selectedNavigationEntity;
155
- setTimeout(() => {
156
- if (currentSelection === selectedNavigationEntity)
157
- setSelectedNavigationEntity(undefined);
158
- }, 2400);
159
- }, [selectedNavigationEntity]);
160
-
161
- const checkInlineEditing = useCallback((entity?: Entity<any>): boolean => {
162
- const collection = collectionRef.current;
163
- if (!canEditEntity(collection, authController, fullPathToCollectionSegments(fullPath), entity ?? null)) {
164
- return false;
165
- }
166
- return collection.inlineEditing === undefined || collection.inlineEditing;
167
- }, [authController, fullPath]);
168
-
169
- const selectionEnabled = collection.selectionEnabled === undefined || collection.selectionEnabled;
170
- const hoverRow = !checkInlineEditing();
171
-
172
- const [popOverOpen, setPopOverOpen] = useState(false);
173
-
174
- const selectionController = useSelectionController<M>();
175
- const usedSelectionController = collection.selectionController ?? selectionController;
176
- const {
177
- selectedEntities,
178
- toggleEntitySelection,
179
- isEntitySelected,
180
- setSelectedEntities
181
- } = usedSelectionController;
182
-
183
- useEffect(() => {
184
- setDeleteEntityClicked(undefined);
185
- }, [selectedEntities]);
186
-
187
- const tableController = useDataSourceEntityCollectionTableController<M>({
188
- fullPath,
189
- collection: collectionRef.current,
190
- entitiesDisplayedFirst: [],
191
- lastDeleteTimestamp
137
+ const context = useFireCMSContext();
138
+ console.debug("EntityCollectionView context", context.customizationController.propertyConfigs);
139
+ const fullPath = fullPathProp ?? collectionProp.path;
140
+ const dataSource = useDataSource(collectionProp);
141
+ const navigation = useNavigationController();
142
+ const sideEntityController = useSideEntityController();
143
+ const authController = useAuthController();
144
+ const userConfigPersistence = useUserConfigurationPersistence();
145
+ const analyticsController = useAnalyticsController();
146
+ const customizationController = useCustomizationController();
147
+
148
+ const containerRef = React.useRef<HTMLDivElement>(null);
149
+
150
+ const collection = useMemo(() => {
151
+ const userOverride = userConfigPersistence?.getCollectionConfig<M>(fullPath);
152
+ return (userOverride ? mergeDeep(collectionProp, userOverride) : collectionProp) as EntityCollection<M>;
153
+ }, [collectionProp, fullPath, userConfigPersistence?.getCollectionConfig]);
154
+
155
+ const collectionRef = React.useRef(collection);
156
+ useEffect(() => {
157
+ collectionRef.current = collection;
158
+ }, [collection]);
159
+
160
+ const canCreateEntities = canCreateEntity(collection, authController, fullPath, null);
161
+ const [selectedNavigationEntity, setSelectedNavigationEntity] = useState<Entity<M> | undefined>(undefined);
162
+ const [deleteEntityClicked, setDeleteEntityClicked] = React.useState<Entity<M> | Entity<M>[] | undefined>(undefined);
163
+
164
+ const [lastDeleteTimestamp, setLastDeleteTimestamp] = React.useState<number>(0);
165
+
166
+ // number of entities in the collection
167
+ const [docsCount, setDocsCount] = useState<number>(0);
168
+
169
+ const unselectNavigatedEntity = useCallback(() => {
170
+ const currentSelection = selectedNavigationEntity;
171
+ setTimeout(() => {
172
+ if (currentSelection === selectedNavigationEntity)
173
+ setSelectedNavigationEntity(undefined);
174
+ }, 2400);
175
+ }, [selectedNavigationEntity]);
176
+
177
+ const checkInlineEditing = useCallback((entity?: Entity<any>): boolean => {
178
+ const collection = collectionRef.current;
179
+ if (!canEditEntity(collection, authController, fullPath, entity ?? null)) {
180
+ return false;
181
+ }
182
+ return collection.inlineEditing === undefined || collection.inlineEditing;
183
+ }, [authController, fullPath]);
184
+
185
+ const selectionEnabled = collection.selectionEnabled === undefined || collection.selectionEnabled;
186
+ const hoverRow = !checkInlineEditing();
187
+
188
+ const [popOverOpen, setPopOverOpen] = useState(false);
189
+
190
+ const selectionController = useSelectionController<M>();
191
+ const usedSelectionController = collection.selectionController ?? selectionController;
192
+ const {
193
+ selectedEntities,
194
+ isEntitySelected,
195
+ setSelectedEntities
196
+ } = usedSelectionController;
197
+
198
+ useEffect(() => {
199
+ setDeleteEntityClicked(undefined);
200
+ }, [selectedEntities]);
201
+
202
+ const tableController = useDataSourceEntityCollectionTableController<M>({
203
+ fullPath,
204
+ collection,
205
+ lastDeleteTimestamp
206
+ });
207
+
208
+ const tableKey = React.useRef<string>(Math.random().toString(36));
209
+ const popupCell = tableController.popupCell;
210
+
211
+ const onPopupClose = useCallback(() => {
212
+ tableController.setPopupCell?.(undefined);
213
+ }, [tableController.setPopupCell]);
214
+
215
+ const onEntityClick = useCallback((clickedEntity: Entity<M>) => {
216
+ console.log("Entity clicked", clickedEntity)
217
+ const collection = collectionRef.current;
218
+ setSelectedNavigationEntity(clickedEntity);
219
+ analyticsController.onAnalyticsEvent?.("edit_entity_clicked", {
220
+ path: clickedEntity.path,
221
+ entityId: clickedEntity.id
192
222
  });
223
+ return sideEntityController.open({
224
+ entityId: clickedEntity.id,
225
+ path: clickedEntity.path,
226
+ collection,
227
+ updateUrl: true,
228
+ onClose: unselectNavigatedEntity,
229
+ });
230
+ }, [unselectNavigatedEntity, sideEntityController]);
193
231
 
194
- const tableKey = React.useRef<string>(Math.random().toString(36));
195
- const popupCell = tableController.popupCell;
196
-
197
- const onPopupClose = useCallback(() => {
198
- tableController.setPopupCell?.(undefined);
199
- }, [tableController.setPopupCell]);
200
-
201
- const onEntityClick = useCallback((clickedEntity: Entity<M>) => {
202
- const collection = collectionRef.current;
203
- setSelectedNavigationEntity(clickedEntity);
204
- analyticsController.onAnalyticsEvent?.("edit_entity_clicked", {
205
- path: clickedEntity.path,
206
- entityId: clickedEntity.id
207
- });
208
- return sideEntityController.open({
209
- entityId: clickedEntity.id,
210
- path: clickedEntity.path,
211
- collection,
212
- updateUrl: true,
213
- onClose: unselectNavigatedEntity
214
- });
215
- }, [unselectNavigatedEntity]);
216
-
217
- const onNewClick = useCallback(() => {
218
-
219
- const collection = collectionRef.current;
220
- analyticsController.onAnalyticsEvent?.("new_entity_click", {
221
- path: fullPath
222
- });
223
- sideEntityController.open({
224
- path: fullPath,
225
- collection,
226
- updateUrl: true,
227
- onClose: unselectNavigatedEntity
228
- });
229
- }, [fullPath]);
230
-
231
- const onMultipleDeleteClick = () => {
232
- analyticsController.onAnalyticsEvent?.("multiple_delete_dialog_open", {
233
- path: fullPath
234
- });
235
- setDeleteEntityClicked(selectedEntities);
236
- };
232
+ const onNewClick = useCallback(() => {
237
233
 
238
- const internalOnEntityDelete = (_path: string, entity: Entity<M>) => {
239
- analyticsController.onAnalyticsEvent?.("single_entity_deleted", {
240
- path: fullPath
241
- });
242
- setSelectedEntities((selectedEntities) => selectedEntities.filter((e) => e.id !== entity.id));
243
- setLastDeleteTimestamp(Date.now());
244
- };
234
+ const collection = collectionRef.current;
235
+ analyticsController.onAnalyticsEvent?.("new_entity_click", {
236
+ path: fullPath
237
+ });
238
+ sideEntityController.open({
239
+ path: fullPath,
240
+ collection,
241
+ updateUrl: true,
242
+ onClose: unselectNavigatedEntity,
243
+ });
244
+ }, [fullPath, sideEntityController]);
245
245
 
246
- const internalOnMultipleEntitiesDelete = (_path: string, entities: Entity<M>[]) => {
247
- analyticsController.onAnalyticsEvent?.("multiple_entities_deleted", {
248
- path: fullPath
249
- });
250
- setSelectedEntities([]);
251
- setDeleteEntityClicked(undefined);
252
- setLastDeleteTimestamp(Date.now());
253
- };
246
+ const onMultipleDeleteClick = () => {
247
+ analyticsController.onAnalyticsEvent?.("multiple_delete_dialog_open", {
248
+ path: fullPath
249
+ });
250
+ setDeleteEntityClicked(selectedEntities);
251
+ };
254
252
 
255
- let AddColumnComponent: React.ComponentType<{
256
- fullPath: string,
257
- parentCollectionIds: string[],
258
- collection: EntityCollection;
259
- }> | undefined
253
+ const internalOnEntityDelete = (_path: string, entity: Entity<M>) => {
254
+ analyticsController.onAnalyticsEvent?.("single_entity_deleted", {
255
+ path: fullPath
256
+ });
257
+ setSelectedEntities((selectedEntities) => selectedEntities.filter((e) => e.id !== entity.id));
258
+ setLastDeleteTimestamp(Date.now());
259
+ };
260
260
 
261
- // we are only using the first plugin that implements this
262
- if (customizationController?.plugins) {
263
- AddColumnComponent = customizationController.plugins.find(plugin => plugin.collectionView?.AddColumnComponent)?.collectionView?.AddColumnComponent;
264
- }
261
+ const internalOnMultipleEntitiesDelete = (_path: string, entities: Entity<M>[]) => {
262
+ analyticsController.onAnalyticsEvent?.("multiple_entities_deleted", {
263
+ path: fullPath
264
+ });
265
+ setSelectedEntities([]);
266
+ setDeleteEntityClicked(undefined);
267
+ setLastDeleteTimestamp(Date.now());
268
+ };
265
269
 
266
- const onCollectionModifiedForUser = useCallback((path: string, partialCollection: PartialEntityCollection<M>) => {
267
- if (userConfigPersistence) {
268
- const currentStoredConfig = userConfigPersistence.getCollectionConfig(path);
269
- const updatedConfig = mergeDeep(currentStoredConfig, partialCollection);
270
- userConfigPersistence.onCollectionModified(path, updatedConfig);
271
- }
272
- }, [userConfigPersistence]);
273
-
274
- const onColumnResize = useCallback(({
275
- width,
276
- key
277
- }: OnColumnResizeParams) => {
278
-
279
- const collection = collectionRef.current;
280
- // Only for property columns
281
- if (!getPropertyInPath(collection.properties, key)) return;
282
- const localCollection = buildPropertyWidthOverwrite(key, width);
283
- onCollectionModifiedForUser(fullPath, localCollection);
284
- }, [onCollectionModifiedForUser, fullPath]);
285
-
286
- const onSizeChanged = useCallback((size: CollectionSize) => {
287
- if (userConfigPersistence)
288
- onCollectionModifiedForUser(fullPath, { defaultSize: size })
289
- }, [onCollectionModifiedForUser, fullPath, userConfigPersistence]);
290
-
291
- const createEnabled = canCreateEntity(collection, authController, fullPathToCollectionSegments(fullPath), null);
292
-
293
- const uniqueFieldValidator: UniqueFieldValidator = useCallback(
294
- ({
295
- name,
296
- value,
297
- property,
298
- entityId
299
- }) => dataSource.checkUniqueField(fullPath, name, value, entityId),
300
- [fullPath]);
301
-
302
- const onValueChange: OnCellValueChange<any, any> = ({
303
- fullPath,
304
- context,
305
- value,
306
- propertyKey,
307
- onValueUpdated,
308
- setError,
309
- entity,
310
- }) => {
311
-
312
- const updatedValues = setIn({ ...entity.values }, propertyKey, value);
313
-
314
- const saveProps: SaveEntityProps = {
315
- path: fullPath,
316
- entityId: entity.id,
317
- values: updatedValues,
318
- previousValues: entity.values,
319
- collection,
320
- status: "existing"
321
- };
270
+ let AddColumnComponent: React.ComponentType<{
271
+ fullPath: string,
272
+ parentCollectionIds: string[],
273
+ collection: EntityCollection;
274
+ }> | undefined
322
275
 
323
- return saveEntityWithCallbacks({
324
- ...saveProps,
325
- collection,
326
- callbacks: collection.callbacks,
327
- dataSource,
328
- context,
329
- onSaveSuccess: () => onValueUpdated(),
330
- onSaveFailure: (e: Error) => {
331
- console.error("Save failure");
332
- console.error(e);
333
- setError(e);
334
- }
335
- });
276
+ // we are only using the first plugin that implements this
277
+ if (customizationController?.plugins) {
278
+ AddColumnComponent = customizationController.plugins.find(plugin => plugin.collectionView?.AddColumnComponent)?.collectionView?.AddColumnComponent;
279
+ }
336
280
 
281
+ const onCollectionModifiedForUser = useCallback((path: string, partialCollection: PartialEntityCollection<M>) => {
282
+ if (userConfigPersistence) {
283
+ const currentStoredConfig = userConfigPersistence.getCollectionConfig(path);
284
+ const updatedConfig = mergeDeep(currentStoredConfig, partialCollection);
285
+ userConfigPersistence.onCollectionModified(path, updatedConfig);
286
+ }
287
+ }, [userConfigPersistence]);
288
+
289
+ const onColumnResize = useCallback(({
290
+ width,
291
+ key
292
+ }: OnColumnResizeParams) => {
293
+
294
+ const collection = collectionRef.current;
295
+ // Only for property columns
296
+ if (!getPropertyInPath(collection.properties, key)) return;
297
+ const localCollection = buildPropertyWidthOverwrite(key, width);
298
+ onCollectionModifiedForUser(fullPath, localCollection);
299
+ }, [onCollectionModifiedForUser, fullPath]);
300
+
301
+ const onSizeChanged = useCallback((size: CollectionSize) => {
302
+ if (userConfigPersistence)
303
+ onCollectionModifiedForUser(fullPath, { defaultSize: size })
304
+ }, [onCollectionModifiedForUser, fullPath, userConfigPersistence]);
305
+
306
+ const createEnabled = canCreateEntity(collection, authController, fullPath, null);
307
+
308
+ const uniqueFieldValidator: UniqueFieldValidator = useCallback(
309
+ ({
310
+ name,
311
+ value,
312
+ property,
313
+ entityId
314
+ }) => dataSource.checkUniqueField(fullPath, name, value, entityId),
315
+ [fullPath]);
316
+
317
+ const onValueChange: OnCellValueChange<any, any> = ({
318
+ value,
319
+ propertyKey,
320
+ onValueUpdated,
321
+ setError,
322
+ data: entity,
323
+ }) => {
324
+
325
+ const updatedValues = setIn({ ...entity.values }, propertyKey, value);
326
+
327
+ const saveProps: SaveEntityProps = {
328
+ path: fullPath,
329
+ entityId: entity.id,
330
+ values: updatedValues,
331
+ previousValues: entity.values,
332
+ collection,
333
+ status: "existing"
337
334
  };
338
335
 
339
- const resolvedFullPath = navigation.resolveAliasesFrom(fullPath);
340
- const resolvedCollection = useMemo(() => resolveCollection<M>({
336
+ return saveEntityWithCallbacks({
337
+ ...saveProps,
341
338
  collection,
342
- path: fullPath,
343
- fields: customizationController.propertyConfigs
344
- }), [collection, fullPath]);
345
-
346
- const getPropertyFor = useCallback(({
347
- propertyKey,
348
- propertyValue,
349
- entity
350
- }: GetPropertyForProps<M>) => {
351
- let propertyOrBuilder: PropertyOrBuilder<any, M> | undefined = getPropertyInPath<M>(collection.properties, propertyKey);
352
-
353
- // we might not find the property in the collection if combining property builders and map spread
354
- if (!propertyOrBuilder) {
355
- // these 2 properties are coming from the resolved collection with default values
356
- propertyOrBuilder = getPropertyInPath<M>(resolvedCollection.properties, propertyKey);
339
+ dataSource,
340
+ context,
341
+ onSaveSuccess: () => {
342
+ setError(undefined);
343
+ onValueUpdated();
344
+ },
345
+ onSaveFailure: (e: Error) => {
346
+ console.error("Save failure");
347
+ console.error(e);
348
+ setError(e);
357
349
  }
350
+ });
358
351
 
359
- return resolveProperty({
360
- propertyKey,
361
- propertyOrBuilder,
362
- path: fullPath,
363
- propertyValue,
364
- values: entity.values,
365
- entityId: entity.id,
366
- fields: customizationController.propertyConfigs
367
- });
368
- }, [collection.properties, customizationController.propertyConfigs, fullPath, resolvedCollection.properties]);
369
-
370
- const displayedColumnIds = useColumnIds(resolvedCollection, true);
371
-
372
- const additionalFields = useMemo(() => {
373
- const subcollectionColumns: AdditionalFieldDelegate<M, any>[] = collection.subcollections?.map((subcollection) => {
374
- return {
375
- key: getSubcollectionColumnId(subcollection),
376
- name: subcollection.name,
377
- width: 200,
378
- dependencies: [],
379
- Builder: ({ entity }) => (
380
- <Button color={"primary"}
381
- variant={"outlined"}
382
- startIcon={<KeyboardTabIcon size={"small"}/>}
383
- onClick={(event: any) => {
384
- event.stopPropagation();
385
- sideEntityController.open({
386
- path: fullPath,
387
- entityId: entity.id,
388
- selectedSubPath: subcollection.id ?? subcollection.path,
389
- collection,
390
- updateUrl: true
391
- });
392
- }}>
393
- {subcollection.name}
394
- </Button>
395
- )
396
- };
397
- }) ?? [];
398
-
399
- const collectionGroupParentCollections: AdditionalFieldDelegate<M, any>[] = collection.collectionGroup
400
- ? [{
401
- key: COLLECTION_GROUP_PARENT_ID,
402
- name: "Parent entities",
403
- width: 260,
404
- dependencies: [],
405
- Builder: ({ entity }) => {
406
- const collectionsWithPath = navigation.getParentReferencesFromPath(entity.path);
407
- return (
408
- <>
409
- {collectionsWithPath.map((reference) => {
410
- return (
411
- <ReferencePreview
412
- key={reference.path + "/" + reference.id}
413
- reference={reference}
414
- size={"tiny"}/>
415
- );
416
- })}
417
- </>
418
- );
419
- }
420
- }]
421
- : [];
422
-
423
- return [
424
- ...(collection.additionalFields ?? []),
425
- ...subcollectionColumns,
426
- ...collectionGroupParentCollections
427
- ];
428
- }, [collection, fullPath]);
429
-
430
- const updateLastDeleteTimestamp = useCallback(() => {
431
- setLastDeleteTimestamp(Date.now());
432
- }, []);
433
-
434
- const largeLayout = useLargeLayout();
435
-
436
- const getActionsForEntity = ({ entity, customEntityActions }: {
437
- entity?: Entity<M>,
438
- customEntityActions?: EntityAction[]
439
- }): EntityAction[] => {
440
- const deleteEnabled = entity ? canDeleteEntity(collection, authController, fullPathToCollectionSegments(fullPath), entity) : true;
441
- const actions: EntityAction[] = [editEntityAction];
442
- if (createEnabled)
443
- actions.push(copyEntityAction);
444
- if (deleteEnabled)
445
- actions.push(deleteEntityAction);
446
- if (customEntityActions)
447
- actions.push(...customEntityActions);
448
- return actions;
449
- };
352
+ };
450
353
 
451
- const getIdColumnWidth = useCallback(() => {
452
- const entityActions = getActionsForEntity({});
453
- const collapsedActions = entityActions.filter(a => a.collapsed !== false);
454
- const uncollapsedActions = entityActions.filter(a => a.collapsed === false);
455
- const actionsWidth = uncollapsedActions.length * (largeLayout ? 40 : 30);
456
- return (largeLayout ? (80 + actionsWidth) : (70 + actionsWidth)) + (collapsedActions.length > 0 ? (largeLayout ? 40 : 30) : 0);
457
- }, [largeLayout]);
458
-
459
- const tableRowActionsBuilder = ({
460
- entity,
461
- size,
462
- width,
463
- frozen
464
- }: {
465
- entity: Entity<any>,
466
- size: CollectionSize,
467
- width: number,
468
- frozen?: boolean
469
- }) => {
470
-
471
- const isSelected = isEntitySelected(entity);
472
-
473
- const actions = getActionsForEntity({ entity, customEntityActions: collection.entityActions });
474
-
475
- return (
476
- <EntityCollectionRowActions
477
- entity={entity}
478
- width={width}
479
- frozen={frozen}
480
- isSelected={isSelected}
481
- selectionEnabled={selectionEnabled}
482
- size={size}
483
- highlightEntity={setSelectedNavigationEntity}
484
- unhighlightEntity={unselectNavigatedEntity}
485
- collection={collection}
486
- fullPath={fullPath}
487
- actions={actions}
488
- hideId={collection?.hideIdFromCollection}
489
- onCollectionChange={updateLastDeleteTimestamp}
490
- selectionController={usedSelectionController}
491
- />
492
- );
354
+ const resolvedFullPath = navigation.resolveAliasesFrom(fullPath);
355
+ const resolvedCollection = useMemo(() => resolveCollection<M>({
356
+ collection,
357
+ path: fullPath,
358
+ fields: customizationController.propertyConfigs
359
+ }), [collection, fullPath]);
360
+
361
+ const getPropertyFor = useCallback(({
362
+ propertyKey,
363
+ entity
364
+ }: GetPropertyForProps<M>) => {
365
+ let propertyOrBuilder: PropertyOrBuilder<any, M> | undefined = getPropertyInPath<M>(collection.properties, propertyKey);
366
+
367
+ // we might not find the property in the collection if combining property builders and map spread
368
+ if (!propertyOrBuilder) {
369
+ // these 2 properties are coming from the resolved collection with default values
370
+ propertyOrBuilder = getPropertyInPath<M>(resolvedCollection.properties, propertyKey);
371
+ }
493
372
 
494
- };
373
+ return resolveProperty({
374
+ propertyKey,
375
+ propertyOrBuilder,
376
+ path: fullPath,
377
+ values: entity.values,
378
+ entityId: entity.id,
379
+ fields: customizationController.propertyConfigs
380
+ });
381
+ }, [collection.properties, customizationController.propertyConfigs, fullPath, resolvedCollection.properties]);
382
+
383
+ const displayedColumnIds = useColumnIds(resolvedCollection, true);
384
+
385
+ const additionalFields = useMemo(() => {
386
+ const subcollectionColumns: AdditionalFieldDelegate<M, any>[] = collection.subcollections?.map((subcollection) => {
387
+ return {
388
+ key: getSubcollectionColumnId(subcollection),
389
+ name: subcollection.name,
390
+ width: 200,
391
+ dependencies: [],
392
+ Builder: ({ entity }) => (
393
+ <Button color={"primary"}
394
+ variant={"outlined"}
395
+ startIcon={<KeyboardTabIcon size={"small"}/>}
396
+ onClick={(event: any) => {
397
+ event.stopPropagation();
398
+ sideEntityController.open({
399
+ path: fullPath,
400
+ entityId: entity.id,
401
+ selectedSubPath: subcollection.id ?? subcollection.path,
402
+ collection,
403
+ updateUrl: true,
404
+ });
405
+ }}>
406
+ {subcollection.name}
407
+ </Button>
408
+ )
409
+ };
410
+ }) ?? [];
411
+
412
+ const collectionGroupParentCollections: AdditionalFieldDelegate<M, any>[] = collection.collectionGroup
413
+ ? [{
414
+ key: COLLECTION_GROUP_PARENT_ID,
415
+ name: "Parent entities",
416
+ width: 260,
417
+ dependencies: [],
418
+ Builder: ({ entity }) => {
419
+ const collectionsWithPath = navigation.getParentReferencesFromPath(entity.path);
420
+ return (
421
+ <>
422
+ {collectionsWithPath.map((reference) => {
423
+ return (
424
+ <ReferencePreview
425
+ key={reference.path + "/" + reference.id}
426
+ reference={reference}
427
+ size={"tiny"}/>
428
+ );
429
+ })}
430
+ </>
431
+ );
432
+ }
433
+ }]
434
+ : [];
435
+
436
+ return [
437
+ ...(collection.additionalFields ?? []),
438
+ ...subcollectionColumns,
439
+ ...collectionGroupParentCollections
440
+ ];
441
+ }, [collection, fullPath, sideEntityController]);
442
+
443
+ const updateLastDeleteTimestamp = useCallback(() => {
444
+ setLastDeleteTimestamp(Date.now());
445
+ }, []);
446
+
447
+ const largeLayout = useLargeLayout();
448
+
449
+ const getActionsForEntity = ({
450
+ entity,
451
+ customEntityActions
452
+ }: {
453
+ entity?: Entity<M>,
454
+ customEntityActions?: EntityAction[]
455
+ }): EntityAction[] => {
456
+ const deleteEnabled = entity ? canDeleteEntity(collection, authController, fullPath, entity) : true;
457
+ const actions: EntityAction[] = [editEntityAction];
458
+ if (createEnabled)
459
+ actions.push(copyEntityAction);
460
+ if (deleteEnabled)
461
+ actions.push(deleteEntityAction);
462
+ if (customEntityActions)
463
+ actions.push(...customEntityActions);
464
+ return actions;
465
+ };
495
466
 
496
- const title = <Popover
497
- open={popOverOpen}
498
- onOpenChange={setPopOverOpen}
499
- enabled={Boolean(collection.description)}
500
- trigger={<div className="flex flex-col items-start">
501
- <Typography
502
- variant={"subtitle1"}
503
- className={`leading-none truncate max-w-[160px] lg:max-w-[240px] ${collection.description ? "cursor-pointer" : "cursor-auto"}`}
504
- onClick={collection.description
505
- ? (e) => {
506
- setPopOverOpen(true);
507
- e.stopPropagation();
508
- }
509
- : undefined}>
510
- {`${collection.name}`}
511
- </Typography>
512
-
513
- <EntitiesCount
514
- fullPath={fullPath}
515
- collection={collection}
516
- filter={tableController.filterValues}
517
- sortBy={tableController.sortBy}
518
- onCountChange={setDocsCount}
519
- />
520
-
521
- </div>}
522
- >
523
-
524
- {collection.description && <div className="m-4 text-gray-900 dark:text-white">
525
- <Markdown source={collection.description}/>
526
- </div>}
527
-
528
- </Popover>;
529
-
530
- const buildAdditionalHeaderWidget = useCallback(({
531
- property,
532
- propertyKey,
533
- onHover
534
- }: {
535
- property: ResolvedProperty,
536
- propertyKey: string,
537
- onHover: boolean
538
- }) => {
539
- const collection = collectionRef.current;
540
- if (!customizationController.plugins)
541
- return null;
542
- return <>
543
- {customizationController.plugins.filter(plugin => plugin.collectionView?.HeaderAction)
544
- .map((plugin, i) => {
545
- const HeaderAction = plugin.collectionView!.HeaderAction!;
546
- return <HeaderAction
547
- onHover={onHover}
548
- key={`plugin_header_action_${i}`}
549
- propertyKey={propertyKey}
550
- property={property}
551
- fullPath={fullPath}
552
- collection={collection}
553
- parentCollectionIds={parentCollectionIds ?? []}/>;
554
- })}
555
- </>;
556
- }, [customizationController.plugins, fullPath, parentCollectionIds]);
557
-
558
- const addColumnComponentInternal = AddColumnComponent
559
- ? function () {
560
- if (typeof AddColumnComponent === "function")
561
- return <AddColumnComponent fullPath={fullPath}
562
- parentCollectionIds={parentCollectionIds ?? []}
563
- collection={collection}/>;
564
- return null;
565
- }
566
- : undefined;
567
-
568
- const {
569
- textSearchLoading,
570
- textSearchInitialised,
571
- onTextSearchClick,
572
- textSearchEnabled
573
- } = useTableSearchHelper({
574
- collection,
575
- fullPath: resolvedFullPath,
576
- parentCollectionIds
467
+ const getIdColumnWidth = () => {
468
+ const entityActions = getActionsForEntity({});
469
+ const collapsedActions = entityActions.filter(a => a.collapsed !== false);
470
+ const uncollapsedActions = entityActions.filter(a => a.collapsed === false);
471
+ const actionsWidth = uncollapsedActions.length * (largeLayout ? 40 : 30);
472
+ return (largeLayout ? (80 + actionsWidth) : (70 + actionsWidth)) + (collapsedActions.length > 0 ? (largeLayout ? 40 : 30) : 0);
473
+ };
474
+
475
+ const tableRowActionsBuilder = ({
476
+ entity,
477
+ size,
478
+ width,
479
+ frozen
480
+ }: {
481
+ entity: Entity<any>,
482
+ size: CollectionSize,
483
+ width: number,
484
+ frozen?: boolean
485
+ }) => {
486
+
487
+ const isSelected = isEntitySelected(entity);
488
+
489
+ const actions = getActionsForEntity({
490
+ entity,
491
+ customEntityActions: collection.entityActions
577
492
  });
578
493
 
579
494
  return (
580
- <div className={cn("overflow-hidden h-full w-full", className)}
581
- ref={containerRef}>
582
- <EntityCollectionTable
583
- key={`collection_table_${fullPath}`}
584
- additionalFields={additionalFields}
495
+ <EntityCollectionRowActions
496
+ entity={entity}
497
+ width={width}
498
+ frozen={frozen}
499
+ isSelected={isSelected}
500
+ selectionEnabled={selectionEnabled}
501
+ size={size}
502
+ highlightEntity={setSelectedNavigationEntity}
503
+ unhighlightEntity={unselectNavigatedEntity}
504
+ collection={collection}
505
+ fullPath={fullPath}
506
+ actions={actions}
507
+ hideId={collection?.hideIdFromCollection}
508
+ onCollectionChange={updateLastDeleteTimestamp}
509
+ selectionController={usedSelectionController}
510
+ />
511
+ );
512
+
513
+ };
514
+
515
+ const title = <Popover
516
+ open={popOverOpen}
517
+ onOpenChange={setPopOverOpen}
518
+ enabled={Boolean(collection.description)}
519
+ trigger={<div className="flex flex-col items-start">
520
+ <Typography
521
+ variant={"subtitle1"}
522
+ className={`leading-none truncate max-w-[160px] lg:max-w-[240px] ${collection.description ? "cursor-pointer" : "cursor-auto"}`}
523
+ onClick={collection.description
524
+ ? (e) => {
525
+ setPopOverOpen(true);
526
+ e.stopPropagation();
527
+ }
528
+ : undefined}>
529
+ {`${collection.name}`}
530
+ </Typography>
531
+
532
+ <EntitiesCount
533
+ fullPath={fullPath}
534
+ collection={collection}
535
+ filter={tableController.filterValues}
536
+ sortBy={tableController.sortBy}
537
+ onCountChange={setDocsCount}
538
+ />
539
+
540
+ </div>}
541
+ >
542
+
543
+ {collection.description && <div className="m-4 text-gray-900 dark:text-white">
544
+ <Markdown source={collection.description}/>
545
+ </div>}
546
+
547
+ </Popover>;
548
+
549
+ const buildAdditionalHeaderWidget = useCallback(({
550
+ property,
551
+ propertyKey,
552
+ onHover
553
+ }: {
554
+ property: ResolvedProperty,
555
+ propertyKey: string,
556
+ onHover: boolean
557
+ }) => {
558
+ const collection = collectionRef.current;
559
+ if (!customizationController.plugins)
560
+ return null;
561
+ return <>
562
+ {customizationController.plugins.filter(plugin => plugin.collectionView?.HeaderAction)
563
+ .map((plugin, i) => {
564
+ const HeaderAction = plugin.collectionView!.HeaderAction!;
565
+ return <HeaderAction
566
+ onHover={onHover}
567
+ key={`plugin_header_action_${i}`}
568
+ propertyKey={propertyKey}
569
+ property={property}
570
+ fullPath={fullPath}
571
+ collection={collection}
572
+ parentCollectionIds={parentCollectionIds ?? []}/>;
573
+ })}
574
+ </>;
575
+ }, [customizationController.plugins, fullPath, parentCollectionIds]);
576
+
577
+ const addColumnComponentInternal = AddColumnComponent
578
+ ? function () {
579
+ if (typeof AddColumnComponent === "function")
580
+ return <AddColumnComponent fullPath={fullPath}
581
+ parentCollectionIds={parentCollectionIds ?? []}
582
+ collection={collection}/>;
583
+ return null;
584
+ }
585
+ : undefined;
586
+
587
+ const {
588
+ textSearchLoading,
589
+ textSearchInitialised,
590
+ onTextSearchClick,
591
+ textSearchEnabled
592
+ } = useTableSearchHelper({
593
+ collection,
594
+ fullPath: resolvedFullPath,
595
+ parentCollectionIds
596
+ });
597
+
598
+ return (
599
+ <div className={cn("overflow-hidden h-full w-full rounded-md", className)}
600
+ ref={containerRef}>
601
+ <EntityCollectionTable
602
+ key={`collection_table_${fullPath}`}
603
+ additionalFields={additionalFields}
604
+ tableController={tableController}
605
+ enablePopupIcon={true}
606
+ displayedColumnIds={displayedColumnIds}
607
+ onSizeChanged={onSizeChanged}
608
+ onEntityClick={onEntityClick}
609
+ onColumnResize={onColumnResize}
610
+ onValueChange={onValueChange}
611
+ tableRowActionsBuilder={tableRowActionsBuilder}
612
+ uniqueFieldValidator={uniqueFieldValidator}
613
+ title={title}
614
+ selectionController={usedSelectionController}
615
+ highlightedEntities={selectedNavigationEntity ? [selectedNavigationEntity] : []}
616
+ defaultSize={collection.defaultSize}
617
+ properties={resolvedCollection.properties}
618
+ getPropertyFor={getPropertyFor}
619
+ onTextSearchClick={textSearchInitialised ? undefined : onTextSearchClick}
620
+ textSearchLoading={textSearchLoading}
621
+ textSearchEnabled={textSearchEnabled}
622
+ actionsStart={<EntityCollectionViewStartActions
623
+ parentCollectionIds={parentCollectionIds ?? []}
624
+ collection={collection}
585
625
  tableController={tableController}
586
- displayedColumnIds={displayedColumnIds}
587
- onSizeChanged={onSizeChanged}
588
- onEntityClick={onEntityClick}
589
- onColumnResize={onColumnResize}
590
- onValueChange={onValueChange}
591
- tableRowActionsBuilder={tableRowActionsBuilder}
592
- uniqueFieldValidator={uniqueFieldValidator}
593
- title={title}
626
+ path={fullPath}
627
+ relativePath={collection.path}
594
628
  selectionController={usedSelectionController}
595
- highlightedEntities={selectedNavigationEntity ? [selectedNavigationEntity] : []}
596
- defaultSize={collection.defaultSize}
597
- properties={resolvedCollection.properties}
598
- getPropertyFor={getPropertyFor}
599
- onTextSearchClick={textSearchInitialised ? undefined : onTextSearchClick}
600
- textSearchLoading={textSearchLoading}
601
- textSearchEnabled={textSearchEnabled}
602
- actions={<EntityCollectionViewActions
603
- parentCollectionIds={parentCollectionIds ?? []}
604
- collection={collection}
605
- tableController={tableController}
606
- onMultipleDeleteClick={onMultipleDeleteClick}
607
- onNewClick={onNewClick}
608
- path={fullPath}
609
- relativePath={collection.path}
610
- selectionController={usedSelectionController}
611
- selectionEnabled={selectionEnabled}
612
- collectionEntitiesCount={docsCount}
613
- />}
614
- emptyComponent={canCreateEntities && tableController.filterValues === undefined && tableController.sortBy === undefined
615
- ? <div className="flex flex-col items-center justify-center">
616
- <Typography variant={"subtitle2"}>So empty...</Typography>
617
- <Button
618
- color={"primary"}
619
- variant={"outlined"}
620
- onClick={onNewClick}
621
- className="mt-4"
622
- >
623
- <AddIcon/>
624
- Create your first entity
625
- </Button>
626
- </div>
627
- : <Typography variant={"label"}>No results with the applied filter/sort</Typography>
628
- }
629
- hoverRow={hoverRow}
630
- inlineEditing={checkInlineEditing()}
631
- AdditionalHeaderWidget={buildAdditionalHeaderWidget}
632
- AddColumnComponent={addColumnComponentInternal}
633
- getIdColumnWidth={getIdColumnWidth}
634
- additionalIDHeaderWidget={<EntityIdHeaderWidget
635
- path={fullPath}
636
- collection={collection}/>}
637
- />
638
-
639
- <PopupFormField
640
- key={`popup_form_${popupCell?.propertyKey}_${popupCell?.entity?.id}`}
641
- open={Boolean(popupCell)}
642
- onClose={onPopupClose}
643
- cellRect={popupCell?.cellRect}
644
- propertyKey={popupCell?.propertyKey}
629
+ collectionEntitiesCount={docsCount}/>}
630
+ actions={<EntityCollectionViewActions
631
+ parentCollectionIds={parentCollectionIds ?? []}
645
632
  collection={collection}
646
- entity={popupCell?.entity}
647
- tableKey={tableKey.current}
648
- customFieldValidator={uniqueFieldValidator}
649
- path={resolvedFullPath}
650
- onCellValueChange={onValueChange}
651
- container={containerRef.current}/>
652
-
653
- {deleteEntityClicked &&
654
- <DeleteEntityDialog
655
- entityOrEntitiesToDelete={deleteEntityClicked}
656
- path={fullPath}
657
- collection={collection}
658
- callbacks={collection.callbacks}
659
- open={Boolean(deleteEntityClicked)}
660
- onEntityDelete={internalOnEntityDelete}
661
- onMultipleEntitiesDelete={internalOnMultipleEntitiesDelete}
662
- onClose={() => setDeleteEntityClicked(undefined)}/>}
633
+ tableController={tableController}
634
+ onMultipleDeleteClick={onMultipleDeleteClick}
635
+ onNewClick={onNewClick}
636
+ path={fullPath}
637
+ relativePath={collection.path}
638
+ selectionController={usedSelectionController}
639
+ selectionEnabled={selectionEnabled}
640
+ collectionEntitiesCount={docsCount}
641
+ />}
642
+ emptyComponent={canCreateEntities && tableController.filterValues === undefined && tableController.sortBy === undefined
643
+ ? <div className="flex flex-col items-center justify-center">
644
+ <Typography variant={"subtitle2"}>So empty...</Typography>
645
+ <Button
646
+ color={"primary"}
647
+ variant={"outlined"}
648
+ onClick={onNewClick}
649
+ className="mt-4"
650
+ >
651
+ <AddIcon/>
652
+ Create your first entity
653
+ </Button>
654
+ </div>
655
+ : <Typography variant={"label"}>No results with the applied filter/sort</Typography>
656
+ }
657
+ hoverRow={hoverRow}
658
+ inlineEditing={checkInlineEditing()}
659
+ AdditionalHeaderWidget={buildAdditionalHeaderWidget}
660
+ AddColumnComponent={addColumnComponentInternal}
661
+ getIdColumnWidth={getIdColumnWidth}
662
+ additionalIDHeaderWidget={<EntityIdHeaderWidget
663
+ path={fullPath}
664
+ collection={collection}/>}
665
+ />
666
+
667
+ <PopupFormField
668
+ key={`popup_form_${popupCell?.propertyKey}_${popupCell?.entity?.id}`}
669
+ open={Boolean(popupCell)}
670
+ onClose={onPopupClose}
671
+ cellRect={popupCell?.cellRect}
672
+ propertyKey={popupCell?.propertyKey}
673
+ collection={collection}
674
+ entity={popupCell?.entity}
675
+ tableKey={tableKey.current}
676
+ customFieldValidator={uniqueFieldValidator}
677
+ path={resolvedFullPath}
678
+ onCellValueChange={onValueChange}
679
+ container={containerRef.current}/>
680
+
681
+ {deleteEntityClicked &&
682
+ <DeleteEntityDialog
683
+ entityOrEntitiesToDelete={deleteEntityClicked}
684
+ path={fullPath}
685
+ collection={collection}
686
+ callbacks={collection.callbacks}
687
+ open={Boolean(deleteEntityClicked)}
688
+ onEntityDelete={internalOnEntityDelete}
689
+ onMultipleEntitiesDelete={internalOnMultipleEntitiesDelete}
690
+ onClose={() => setDeleteEntityClicked(undefined)}/>}
663
691
 
664
692
  </div>
665
693
  );
@@ -676,39 +704,14 @@ export const EntityCollectionView = React.memo(
676
704
  equal(a.selectionController, b.selectionController) &&
677
705
  equal(a.Actions, b.Actions) &&
678
706
  equal(a.defaultSize, b.defaultSize) &&
707
+ equal(a.initialFilter, b.initialFilter) &&
708
+ equal(a.initialSort, b.initialSort) &&
679
709
  equal(a.textSearchEnabled, b.textSearchEnabled) &&
680
710
  equal(a.additionalFields, b.additionalFields) &&
711
+ equal(a.sideDialogWidth, b.sideDialogWidth) &&
681
712
  equal(a.forceFilter, b.forceFilter);
682
713
  }) as React.FunctionComponent<EntityCollectionViewProps<any>>
683
714
 
684
- export function useSelectionController<M extends Record<string, any> = any>(
685
- onSelectionChange?: (entity: Entity<M>, selected: boolean) => void
686
- ): SelectionController<M> {
687
-
688
- const [selectedEntities, setSelectedEntities] = useState<Entity<M>[]>([]);
689
-
690
- const toggleEntitySelection = useCallback((entity: Entity<M>) => {
691
- let newValue;
692
- if (selectedEntities.map(e => e.id).includes(entity.id)) {
693
- onSelectionChange?.(entity, false);
694
- newValue = selectedEntities.filter((item: Entity<M>) => item.id !== entity.id);
695
- } else {
696
- onSelectionChange?.(entity, true);
697
- newValue = [...selectedEntities, entity];
698
- }
699
- setSelectedEntities(newValue);
700
- }, [selectedEntities]);
701
-
702
- const isEntitySelected = useCallback((entity: Entity<M>) => selectedEntities.map(e => e.id).includes(entity.id), [selectedEntities]);
703
-
704
- return {
705
- selectedEntities,
706
- setSelectedEntities,
707
- isEntitySelected,
708
- toggleEntitySelection
709
- };
710
- }
711
-
712
715
  function EntitiesCount({
713
716
  fullPath,
714
717
  collection,
@@ -723,7 +726,7 @@ function EntitiesCount({
723
726
  onCountChange?: (count: number) => void,
724
727
  }) {
725
728
 
726
- const dataSource = useDataSource();
729
+ const dataSource = useDataSource(collection);
727
730
  const navigation = useNavigationController();
728
731
  const [count, setCount] = useState<number | undefined>(undefined);
729
732
  const [error, setError] = useState<Error | undefined>(undefined);
@@ -745,6 +748,7 @@ function EntitiesCount({
745
748
 
746
749
  useEffect(() => {
747
750
  if (onCountChange) {
751
+ setError(undefined);
748
752
  onCountChange(count ?? 0);
749
753
  }
750
754
  }, [onCountChange, count]);
@@ -796,10 +800,10 @@ function EntityIdHeaderWidget({
796
800
  if (!searchString) return;
797
801
  setOpenPopup(false);
798
802
  return sideEntityController.open({
799
- entityId: searchString,
803
+ entityId: searchString.trim(),
800
804
  path,
801
805
  collection,
802
- updateUrl: true
806
+ updateUrl: true,
803
807
  });
804
808
  }}
805
809
  className={"text-gray-900 dark:text-white w-96 max-w-full"}>
@@ -809,11 +813,13 @@ function EntityIdHeaderWidget({
809
813
  autoFocus={openPopup}
810
814
  placeholder={"Find entity by ID"}
811
815
  // size={"small"}
812
- onChange={(e) => setSearchString(e.target.value)}
816
+ onChange={(e) => {
817
+ setSearchString(e.target.value);
818
+ }}
813
819
  value={searchString}
814
820
  className={"flex-grow bg-transparent outline-none p-1"}/>
815
821
  <Button variant={"outlined"}
816
- disabled={!searchString}
822
+ disabled={!(searchString.trim())}
817
823
  type={"submit"}
818
824
  >Go</Button>
819
825
  </div>