@firecms/core 3.0.0-canary.99 → 3.0.0-rc.2

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 (371) hide show
  1. package/README.md +2 -2
  2. package/dist/app/Drawer.d.ts +0 -1
  3. package/dist/app/Scaffold.d.ts +4 -0
  4. package/dist/components/ArrayContainer.d.ts +31 -12
  5. package/dist/components/{DeleteConfirmationDialog.d.ts → ConfirmationDialog.d.ts} +1 -1
  6. package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +3 -1
  7. package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +2 -2
  8. package/dist/components/EntityCollectionTable/EntityCollectionTableProps.d.ts +17 -3
  9. package/dist/components/EntityCollectionTable/fields/TableReferenceField.d.ts +1 -1
  10. package/dist/components/EntityCollectionTable/index.d.ts +1 -1
  11. package/dist/components/EntityCollectionTable/internal/popup_field/PopupFormField.d.ts +6 -3
  12. package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +8 -0
  13. package/dist/components/EntityCollectionView/utils.d.ts +3 -0
  14. package/dist/components/EntityJsonPreview.d.ts +3 -0
  15. package/dist/components/EntityPreview.d.ts +8 -6
  16. package/dist/components/HomePage/DefaultHomePage.d.ts +2 -15
  17. package/dist/components/HomePage/HomePageDnD.d.ts +76 -0
  18. package/dist/components/HomePage/NavigationCard.d.ts +3 -1
  19. package/dist/components/HomePage/NavigationCardBinding.d.ts +3 -2
  20. package/dist/components/HomePage/NavigationGroup.d.ts +8 -1
  21. package/dist/components/HomePage/RenameGroupDialog.d.ts +9 -0
  22. package/dist/components/PropertyConfigBadge.d.ts +2 -1
  23. package/dist/components/PropertyIdCopyTooltip.d.ts +8 -0
  24. package/dist/components/SelectableTable/SelectableTable.d.ts +13 -3
  25. package/dist/components/SelectableTable/filters/ReferenceFilterField.d.ts +1 -1
  26. package/dist/components/UnsavedChangesDialog.d.ts +8 -0
  27. package/dist/components/UserDisplay.d.ts +7 -0
  28. package/dist/components/VirtualTable/VirtualTableProps.d.ts +11 -2
  29. package/dist/components/VirtualTable/fields/VirtualTableUserSelect.d.ts +12 -0
  30. package/dist/components/common/default_entity_actions.d.ts +0 -2
  31. package/dist/components/common/index.d.ts +1 -1
  32. package/dist/components/common/useColumnsIds.d.ts +1 -0
  33. package/dist/components/common/{useDataSourceEntityCollectionTableController.d.ts → useDataSourceTableController.d.ts} +10 -2
  34. package/dist/components/common/useDebouncedCallback.d.ts +1 -0
  35. package/dist/components/common/useScrollRestoration.d.ts +14 -0
  36. package/dist/components/index.d.ts +3 -1
  37. package/dist/contexts/BreacrumbsContext.d.ts +8 -0
  38. package/dist/contexts/InternalUserManagementContext.d.ts +3 -0
  39. package/dist/core/DefaultAppBar.d.ts +8 -2
  40. package/dist/core/DrawerNavigationItem.d.ts +2 -1
  41. package/dist/core/EntityEditView.d.ts +40 -22
  42. package/dist/core/EntityEditViewFormActions.d.ts +2 -0
  43. package/dist/core/FireCMS.d.ts +2 -3
  44. package/dist/core/FireCMSRouter.d.ts +4 -0
  45. package/dist/core/NavigationRoutes.d.ts +0 -1
  46. package/dist/core/SideDialogs.d.ts +4 -2
  47. package/dist/core/field_configs.d.ts +1 -1
  48. package/dist/core/index.d.ts +2 -1
  49. package/dist/form/EntityForm.d.ts +50 -0
  50. package/dist/form/EntityFormActions.d.ts +21 -0
  51. package/dist/form/PropertyFieldBinding.d.ts +1 -1
  52. package/dist/form/components/FormEntry.d.ts +6 -0
  53. package/dist/form/components/FormLayout.d.ts +5 -0
  54. package/dist/form/components/LabelWithIcon.d.ts +1 -1
  55. package/dist/form/components/LabelWithIconAndTooltip.d.ts +15 -0
  56. package/dist/form/components/index.d.ts +3 -1
  57. package/dist/form/field_bindings/ArrayCustomShapedFieldBinding.d.ts +1 -1
  58. package/dist/form/field_bindings/ArrayOfReferencesFieldBinding.d.ts +1 -1
  59. package/dist/form/field_bindings/BlockFieldBinding.d.ts +1 -1
  60. package/dist/form/field_bindings/KeyValueFieldBinding.d.ts +1 -1
  61. package/dist/form/field_bindings/MapFieldBinding.d.ts +1 -1
  62. package/dist/form/field_bindings/MarkdownEditorFieldBinding.d.ts +11 -0
  63. package/dist/form/field_bindings/{MultiSelectBinding.d.ts → MultiSelectFieldBinding.d.ts} +1 -1
  64. package/dist/form/field_bindings/ReadOnlyFieldBinding.d.ts +1 -1
  65. package/dist/form/field_bindings/ReferenceAsStringFieldBinding.d.ts +9 -0
  66. package/dist/form/field_bindings/ReferenceFieldBinding.d.ts +2 -2
  67. package/dist/form/field_bindings/RepeatFieldBinding.d.ts +1 -1
  68. package/dist/form/field_bindings/SelectFieldBinding.d.ts +1 -1
  69. package/dist/form/field_bindings/StorageUploadFieldBinding.d.ts +4 -10
  70. package/dist/form/field_bindings/SwitchFieldBinding.d.ts +1 -2
  71. package/dist/form/field_bindings/TextFieldBinding.d.ts +1 -1
  72. package/dist/form/field_bindings/UserSelectFieldBinding.d.ts +12 -0
  73. package/dist/form/index.d.ts +17 -16
  74. package/dist/form/useClearRestoreValue.d.ts +2 -2
  75. package/dist/hooks/data/delete.d.ts +4 -4
  76. package/dist/hooks/data/save.d.ts +3 -3
  77. package/dist/hooks/data/useCollectionFetch.d.ts +1 -1
  78. package/dist/hooks/data/useEntityFetch.d.ts +4 -3
  79. package/dist/hooks/index.d.ts +2 -0
  80. package/dist/hooks/useAuthController.d.ts +1 -1
  81. package/dist/hooks/useBreadcrumbsController.d.ts +26 -0
  82. package/dist/hooks/useBuildNavigationController.d.ts +57 -12
  83. package/dist/hooks/useCollapsedGroups.d.ts +9 -0
  84. package/dist/hooks/useFireCMSContext.d.ts +1 -1
  85. package/dist/hooks/useInternalUserManagementController.d.ts +12 -0
  86. package/dist/hooks/useModeController.d.ts +1 -2
  87. package/dist/hooks/useProjectLog.d.ts +7 -1
  88. package/dist/hooks/useResolvedNavigationFrom.d.ts +3 -3
  89. package/dist/hooks/useValidateAuthenticator.d.ts +3 -3
  90. package/dist/index.es.js +20480 -14434
  91. package/dist/index.es.js.map +1 -1
  92. package/dist/index.umd.js +20250 -14209
  93. package/dist/index.umd.js.map +1 -1
  94. package/dist/internal/useBuildDataSource.d.ts +3 -2
  95. package/dist/internal/useBuildSideEntityController.d.ts +3 -3
  96. package/dist/internal/useUnsavedChangesDialog.d.ts +7 -9
  97. package/dist/preview/PropertyPreviewProps.d.ts +1 -1
  98. package/dist/preview/components/EnumValuesChip.d.ts +1 -1
  99. package/dist/preview/components/ReferencePreview.d.ts +2 -2
  100. package/dist/preview/components/UserPreview.d.ts +8 -0
  101. package/dist/preview/index.d.ts +1 -0
  102. package/dist/preview/util.d.ts +3 -3
  103. package/dist/routes/CustomCMSRoute.d.ts +4 -0
  104. package/dist/routes/FireCMSRoute.d.ts +1 -0
  105. package/dist/routes/HomePageRoute.d.ts +3 -0
  106. package/dist/types/analytics.d.ts +1 -1
  107. package/dist/types/auth.d.ts +7 -9
  108. package/dist/types/collections.d.ts +88 -25
  109. package/dist/types/customization_controller.d.ts +8 -0
  110. package/dist/types/datasource.d.ts +19 -17
  111. package/dist/types/dialogs_controller.d.ts +7 -3
  112. package/dist/types/entities.d.ts +7 -2
  113. package/dist/types/entity_actions.d.ts +58 -8
  114. package/dist/types/entity_callbacks.d.ts +16 -16
  115. package/dist/types/entity_overrides.d.ts +2 -2
  116. package/dist/types/export_import.d.ts +4 -4
  117. package/dist/types/fields.d.ts +43 -17
  118. package/dist/types/firecms.d.ts +31 -3
  119. package/dist/types/firecms_context.d.ts +17 -1
  120. package/dist/types/index.d.ts +1 -0
  121. package/dist/types/internal_user_management.d.ts +20 -0
  122. package/dist/types/navigation.d.ts +60 -17
  123. package/dist/types/permissions.d.ts +4 -4
  124. package/dist/types/plugins.d.ts +44 -9
  125. package/dist/types/properties.d.ts +74 -22
  126. package/dist/types/property_config.d.ts +1 -3
  127. package/dist/types/roles.d.ts +3 -0
  128. package/dist/types/side_dialogs_controller.d.ts +10 -0
  129. package/dist/types/side_entity_controller.d.ts +14 -1
  130. package/dist/types/storage.d.ts +75 -0
  131. package/dist/types/user.d.ts +2 -1
  132. package/dist/util/builders.d.ts +3 -3
  133. package/dist/util/callbacks.d.ts +2 -0
  134. package/dist/util/createFormexStub.d.ts +2 -0
  135. package/dist/util/entities.d.ts +2 -2
  136. package/dist/util/entity_actions.d.ts +2 -0
  137. package/dist/util/entity_cache.d.ts +23 -0
  138. package/dist/util/icon_synonyms.d.ts +0 -1
  139. package/dist/util/icons.d.ts +5 -2
  140. package/dist/util/index.d.ts +3 -0
  141. package/dist/util/navigation_from_path.d.ts +10 -1
  142. package/dist/util/navigation_utils.d.ts +13 -1
  143. package/dist/util/objects.d.ts +2 -1
  144. package/dist/util/permissions.d.ts +4 -4
  145. package/dist/util/property_utils.d.ts +4 -4
  146. package/dist/util/references.d.ts +2 -2
  147. package/dist/util/resolutions.d.ts +30 -6
  148. package/dist/util/storage.d.ts +1 -1
  149. package/dist/util/useStorageUploadController.d.ts +2 -2
  150. package/package.json +133 -125
  151. package/src/app/Drawer.tsx +0 -1
  152. package/src/app/Scaffold.tsx +33 -29
  153. package/src/components/ArrayContainer.tsx +447 -229
  154. package/src/components/CircularProgressCenter.tsx +1 -1
  155. package/src/components/ClearFilterSortButton.tsx +1 -1
  156. package/src/components/{DeleteConfirmationDialog.tsx → ConfirmationDialog.tsx} +12 -11
  157. package/src/components/DeleteEntityDialog.tsx +13 -20
  158. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +59 -25
  159. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +23 -17
  160. package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +20 -3
  161. package/src/components/EntityCollectionTable/PropertyTableCell.tsx +47 -9
  162. package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +21 -16
  163. package/src/components/EntityCollectionTable/fields/TableStorageUpload.tsx +6 -12
  164. package/src/components/EntityCollectionTable/index.tsx +1 -1
  165. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +6 -6
  166. package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +35 -26
  167. package/src/components/EntityCollectionTable/internal/EntityTableCellActions.tsx +20 -8
  168. package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +132 -101
  169. package/src/components/EntityCollectionTable/internal/popup_field/useDraggable.tsx +9 -9
  170. package/src/components/EntityCollectionView/EntityCollectionView.tsx +178 -85
  171. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +7 -4
  172. package/src/components/EntityCollectionView/useSelectionController.tsx +5 -4
  173. package/src/components/EntityCollectionView/utils.ts +19 -0
  174. package/src/components/EntityJsonPreview.tsx +66 -0
  175. package/src/components/EntityPreview.tsx +75 -57
  176. package/src/components/EntityView.tsx +8 -5
  177. package/src/components/ErrorView.tsx +3 -3
  178. package/src/components/FireCMSLogo.tsx +7 -51
  179. package/src/components/HomePage/DefaultHomePage.tsx +506 -161
  180. package/src/components/HomePage/FavouritesView.tsx +9 -14
  181. package/src/components/HomePage/HomePageDnD.tsx +600 -0
  182. package/src/components/HomePage/NavigationCard.tsx +47 -38
  183. package/src/components/HomePage/NavigationCardBinding.tsx +16 -15
  184. package/src/components/HomePage/NavigationGroup.tsx +144 -30
  185. package/src/components/HomePage/RenameGroupDialog.tsx +123 -0
  186. package/src/components/HomePage/SmallNavigationCard.tsx +1 -2
  187. package/src/components/NotFoundPage.tsx +2 -2
  188. package/src/components/PropertyConfigBadge.tsx +10 -4
  189. package/src/components/PropertyIdCopyTooltip.tsx +47 -0
  190. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +22 -13
  191. package/src/components/SearchIconsView.tsx +2 -2
  192. package/src/components/SelectableTable/SelectableTable.tsx +154 -142
  193. package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +4 -2
  194. package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +10 -8
  195. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +60 -11
  196. package/src/components/UnsavedChangesDialog.tsx +46 -0
  197. package/src/components/UserDisplay.tsx +55 -0
  198. package/src/components/VirtualTable/VirtualTable.tsx +65 -44
  199. package/src/components/VirtualTable/VirtualTableCell.tsx +0 -8
  200. package/src/components/VirtualTable/VirtualTableHeader.tsx +8 -8
  201. package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +1 -1
  202. package/src/components/VirtualTable/VirtualTableProps.tsx +12 -2
  203. package/src/components/VirtualTable/VirtualTableRow.tsx +1 -1
  204. package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +4 -4
  205. package/src/components/VirtualTable/fields/VirtualTableInput.tsx +2 -2
  206. package/src/components/VirtualTable/fields/VirtualTableNumberInput.tsx +2 -1
  207. package/src/components/VirtualTable/fields/VirtualTableSelect.tsx +16 -28
  208. package/src/components/VirtualTable/fields/VirtualTableUserSelect.tsx +99 -0
  209. package/src/components/common/default_entity_actions.tsx +62 -42
  210. package/src/components/common/index.ts +1 -1
  211. package/src/components/common/useColumnsIds.tsx +2 -9
  212. package/src/components/common/useDataSourceTableController.tsx +420 -0
  213. package/src/components/common/useDebouncedCallback.tsx +20 -0
  214. package/src/components/common/useScrollRestoration.tsx +68 -0
  215. package/src/components/common/useTableSearchHelper.ts +1 -0
  216. package/src/components/index.tsx +4 -1
  217. package/src/contexts/BreacrumbsContext.tsx +38 -0
  218. package/src/contexts/DialogsProvider.tsx +3 -2
  219. package/src/contexts/InternalUserManagementContext.tsx +4 -0
  220. package/src/contexts/ModeController.tsx +1 -3
  221. package/src/contexts/SnackbarProvider.tsx +2 -0
  222. package/src/core/DefaultAppBar.tsx +124 -85
  223. package/src/core/DefaultDrawer.tsx +30 -22
  224. package/src/core/DrawerNavigationItem.tsx +32 -28
  225. package/src/core/EntityEditView.tsx +388 -995
  226. package/src/core/EntityEditViewFormActions.tsx +329 -0
  227. package/src/core/EntitySidePanel.tsx +88 -20
  228. package/src/core/FireCMS.tsx +58 -28
  229. package/src/core/FireCMSRouter.tsx +17 -0
  230. package/src/core/NavigationRoutes.tsx +23 -32
  231. package/src/core/SideDialogs.tsx +22 -12
  232. package/src/core/field_configs.tsx +39 -11
  233. package/src/core/index.tsx +4 -2
  234. package/src/form/EntityForm.tsx +814 -0
  235. package/src/form/EntityFormActions.tsx +211 -0
  236. package/src/form/PropertyFieldBinding.tsx +59 -41
  237. package/src/form/components/CustomIdField.tsx +9 -3
  238. package/src/form/components/FieldHelperText.tsx +1 -1
  239. package/src/form/components/FormEntry.tsx +22 -0
  240. package/src/form/components/FormLayout.tsx +16 -0
  241. package/src/form/components/LabelWithIcon.tsx +30 -19
  242. package/src/form/components/LabelWithIconAndTooltip.tsx +28 -0
  243. package/src/form/components/StorageItemPreview.tsx +5 -4
  244. package/src/form/components/StorageUploadProgress.tsx +2 -3
  245. package/src/form/components/index.tsx +3 -1
  246. package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +30 -18
  247. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +47 -36
  248. package/src/form/field_bindings/BlockFieldBinding.tsx +55 -33
  249. package/src/form/field_bindings/DateTimeFieldBinding.tsx +18 -14
  250. package/src/form/field_bindings/KeyValueFieldBinding.tsx +19 -15
  251. package/src/form/field_bindings/MapFieldBinding.tsx +72 -62
  252. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +159 -0
  253. package/src/form/field_bindings/{MultiSelectBinding.tsx → MultiSelectFieldBinding.tsx} +26 -21
  254. package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +10 -8
  255. package/src/form/field_bindings/ReferenceAsStringFieldBinding.tsx +135 -0
  256. package/src/form/field_bindings/ReferenceFieldBinding.tsx +28 -19
  257. package/src/form/field_bindings/RepeatFieldBinding.tsx +56 -32
  258. package/src/form/field_bindings/SelectFieldBinding.tsx +22 -13
  259. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +247 -168
  260. package/src/form/field_bindings/SwitchFieldBinding.tsx +29 -24
  261. package/src/form/field_bindings/TextFieldBinding.tsx +28 -24
  262. package/src/form/field_bindings/UserSelectFieldBinding.tsx +94 -0
  263. package/src/form/index.tsx +17 -37
  264. package/src/form/useClearRestoreValue.tsx +2 -2
  265. package/src/form/validation.ts +12 -6
  266. package/src/hooks/data/delete.ts +6 -5
  267. package/src/hooks/data/save.ts +26 -35
  268. package/src/hooks/data/useCollectionFetch.tsx +3 -3
  269. package/src/hooks/data/useDataSource.tsx +10 -2
  270. package/src/hooks/data/useEntityFetch.tsx +10 -6
  271. package/src/hooks/index.tsx +3 -0
  272. package/src/hooks/useAuthController.tsx +1 -1
  273. package/src/hooks/useBreadcrumbsController.tsx +31 -0
  274. package/src/hooks/useBrowserTitleAndIcon.tsx +1 -1
  275. package/src/hooks/useBuildModeController.tsx +15 -28
  276. package/src/hooks/useBuildNavigationController.tsx +386 -124
  277. package/src/hooks/useCollapsedGroups.ts +64 -0
  278. package/src/hooks/useFireCMSContext.tsx +9 -35
  279. package/src/hooks/useInternalUserManagementController.tsx +16 -0
  280. package/src/hooks/useLargeLayout.tsx +0 -35
  281. package/src/hooks/useModeController.tsx +1 -2
  282. package/src/hooks/useProjectLog.tsx +16 -5
  283. package/src/hooks/useResolvedNavigationFrom.tsx +9 -11
  284. package/src/hooks/useValidateAuthenticator.tsx +3 -3
  285. package/src/internal/useBuildDataSource.ts +67 -80
  286. package/src/internal/useBuildSideDialogsController.tsx +4 -2
  287. package/src/internal/useBuildSideEntityController.tsx +149 -86
  288. package/src/internal/useUnsavedChangesDialog.tsx +127 -91
  289. package/src/preview/PropertyPreview.tsx +36 -12
  290. package/src/preview/PropertyPreviewProps.tsx +1 -1
  291. package/src/preview/components/BooleanPreview.tsx +1 -1
  292. package/src/preview/components/EmptyValue.tsx +1 -1
  293. package/src/preview/components/EnumValuesChip.tsx +1 -1
  294. package/src/preview/components/ImagePreview.tsx +10 -9
  295. package/src/preview/components/ReferencePreview.tsx +10 -18
  296. package/src/preview/components/UrlComponentPreview.tsx +20 -21
  297. package/src/preview/components/UserPreview.tsx +27 -0
  298. package/src/preview/index.ts +1 -0
  299. package/src/preview/property_previews/ArrayOfMapsPreview.tsx +6 -5
  300. package/src/preview/property_previews/ArrayOfReferencesPreview.tsx +5 -4
  301. package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +5 -3
  302. package/src/preview/property_previews/ArrayOfStringsPreview.tsx +4 -3
  303. package/src/preview/property_previews/ArrayOneOfPreview.tsx +6 -4
  304. package/src/preview/property_previews/ArrayPropertyPreview.tsx +6 -4
  305. package/src/preview/property_previews/MapPropertyPreview.tsx +7 -6
  306. package/src/preview/property_previews/SkeletonPropertyComponent.tsx +13 -13
  307. package/src/preview/property_previews/StringPropertyPreview.tsx +2 -2
  308. package/src/preview/util.ts +10 -10
  309. package/src/routes/CustomCMSRoute.tsx +21 -0
  310. package/src/routes/FireCMSRoute.tsx +246 -0
  311. package/src/routes/HomePageRoute.tsx +17 -0
  312. package/src/types/analytics.ts +3 -0
  313. package/src/types/auth.tsx +8 -12
  314. package/src/types/collections.ts +103 -28
  315. package/src/types/customization_controller.tsx +9 -0
  316. package/src/types/datasource.ts +21 -20
  317. package/src/types/dialogs_controller.tsx +7 -3
  318. package/src/types/entities.ts +10 -2
  319. package/src/types/entity_actions.tsx +71 -8
  320. package/src/types/entity_callbacks.ts +18 -18
  321. package/src/types/entity_overrides.tsx +2 -2
  322. package/src/types/export_import.ts +4 -4
  323. package/src/types/fields.tsx +52 -19
  324. package/src/types/firecms.tsx +34 -4
  325. package/src/types/firecms_context.tsx +18 -1
  326. package/src/types/index.ts +1 -0
  327. package/src/types/internal_user_management.ts +24 -0
  328. package/src/types/navigation.ts +76 -22
  329. package/src/types/permissions.ts +5 -5
  330. package/src/types/plugins.tsx +53 -9
  331. package/src/types/properties.ts +84 -22
  332. package/src/types/property_config.tsx +2 -2
  333. package/src/types/roles.ts +3 -0
  334. package/src/types/side_dialogs_controller.tsx +15 -0
  335. package/src/types/side_entity_controller.tsx +16 -1
  336. package/src/types/storage.ts +82 -0
  337. package/src/types/user.ts +3 -1
  338. package/src/util/builders.ts +10 -8
  339. package/src/util/callbacks.ts +119 -0
  340. package/src/util/createFormexStub.tsx +62 -0
  341. package/src/util/entities.ts +6 -4
  342. package/src/util/entity_actions.ts +28 -0
  343. package/src/util/entity_cache.ts +204 -0
  344. package/src/util/icon_list.ts +1 -1
  345. package/src/util/icon_synonyms.ts +0 -1
  346. package/src/util/icons.tsx +36 -11
  347. package/src/util/index.ts +3 -0
  348. package/src/util/join_collections.ts +9 -2
  349. package/src/util/make_properties_editable.ts +13 -5
  350. package/src/util/navigation_from_path.ts +33 -12
  351. package/src/util/navigation_utils.ts +135 -19
  352. package/src/util/objects.ts +74 -14
  353. package/src/util/parent_references_from_path.ts +3 -3
  354. package/src/util/permissions.ts +8 -8
  355. package/src/util/property_utils.tsx +17 -6
  356. package/src/util/references.ts +19 -8
  357. package/src/util/resolutions.ts +93 -24
  358. package/src/util/storage.ts +6 -2
  359. package/src/util/useStorageUploadController.tsx +74 -29
  360. package/dist/components/EntityCollectionTable/internal/popup_field/ElementResizeListener.d.ts +0 -5
  361. package/dist/components/PropertyIdCopyTooltipContent.d.ts +0 -3
  362. package/dist/form/PropertiesForm.d.ts +0 -8
  363. package/dist/form/components/FormikArrayContainer.d.ts +0 -18
  364. package/dist/form/field_bindings/MarkdownFieldBinding.d.ts +0 -9
  365. package/src/components/EntityCollectionTable/internal/popup_field/ElementResizeListener.tsx +0 -59
  366. package/src/components/PropertyIdCopyTooltipContent.tsx +0 -27
  367. package/src/components/common/useDataSourceEntityCollectionTableController.tsx +0 -236
  368. package/src/form/PropertiesForm.tsx +0 -81
  369. package/src/form/components/FormikArrayContainer.tsx +0 -44
  370. package/src/form/field_bindings/MarkdownFieldBinding.tsx +0 -695
  371. /package/src/util/{common.tsx → common.ts} +0 -0
@@ -1,5 +1,6 @@
1
1
  import { useCallback, useEffect, useRef, useState } from "react";
2
2
  import equal from "react-fast-compare"
3
+ import { useBlocker, useNavigate } from "react-router-dom";
3
4
 
4
5
  import {
5
6
  AuthController,
@@ -9,10 +10,13 @@ import {
9
10
  EntityCollection,
10
11
  EntityCollectionsBuilder,
11
12
  EntityReference,
13
+ FireCMSPlugin,
14
+ NavigationBlocker,
12
15
  NavigationController,
16
+ NavigationEntry,
17
+ NavigationGroupMapping,
18
+ NavigationResult,
13
19
  PermissionsBuilder,
14
- TopNavigationEntry,
15
- TopNavigationResult,
16
20
  User,
17
21
  UserConfigurationPersistence
18
22
  } from "../types";
@@ -20,6 +24,7 @@ import {
20
24
  applyPermissionsFunctionIfEmpty,
21
25
  getCollectionByPathOrId,
22
26
  mergeDeep,
27
+ removeFunctions,
23
28
  removeInitialAndTrailingSlashes,
24
29
  resolveCollectionPathIds,
25
30
  resolvePermissions
@@ -29,28 +34,76 @@ import { getParentReferencesFromPath } from "../util/parent_references_from_path
29
34
  const DEFAULT_BASE_PATH = "/";
30
35
  const DEFAULT_COLLECTION_PATH = "/c";
31
36
 
32
- export type BuildNavigationContextProps<EC extends EntityCollection, UserType extends User> = {
37
+ export const NAVIGATION_DEFAULT_GROUP_NAME = "Views";
38
+ export const NAVIGATION_ADMIN_GROUP_NAME = "Admin";
39
+
40
+ export type BuildNavigationContextProps<EC extends EntityCollection, USER extends User> = {
41
+ /**
42
+ * Base path for the CMS, used to build the all the URLs.
43
+ * Defaults to "/".
44
+ */
33
45
  basePath?: string,
46
+ /**
47
+ * Base path for the collections, used to build the collection URLs.
48
+ * Defaults to "c" (e.g. "/c/products").
49
+ */
34
50
  baseCollectionPath?: string,
35
- authController: AuthController<UserType>;
51
+ /**
52
+ * The auth controller used to manage the user authentication and permissions.
53
+ */
54
+ authController: AuthController<USER>;
55
+ /**
56
+ * The collections to be used in the CMS.
57
+ * This can be a static array of collections or a function that returns a promise
58
+ * resolving to an array of collections.
59
+ */
36
60
  collections?: EC[] | EntityCollectionsBuilder<EC>;
61
+ /**
62
+ * Optional permissions builder to be applied to the collections.
63
+ * If not provided, the permissions will be resolved from the collection configuration.
64
+ */
37
65
  collectionPermissions?: PermissionsBuilder;
66
+ /**
67
+ * Custom views to be added to the CMS, these will be available in the main navigation.
68
+ * This can be a static array of views or a function that returns a promise
69
+ * resolving to an array of views.
70
+ */
38
71
  views?: CMSView[] | CMSViewsBuilder;
72
+ /**
73
+ * Custom views to be added to the CMS admin navigation.
74
+ * This can be a static array of views or a function that returns a promise
75
+ * resolving to an array of views.
76
+ */
39
77
  adminViews?: CMSView[] | CMSViewsBuilder;
40
- viewsOrder?: string[];
78
+ /**
79
+ * Controller for storing user preferences.
80
+ */
41
81
  userConfigPersistence?: UserConfigurationPersistence;
82
+ /**
83
+ * Delegate for data source operations, used to resolve collections and views.
84
+ */
42
85
  dataSourceDelegate: DataSourceDelegate;
43
86
  /**
44
- * Use this method to inject collections to the CMS.
45
- * You receive the current collections as a parameter, and you can return
46
- * a new list of collections.
47
- * @see {@link joinCollectionLists}
48
- * @param collections
87
+ * Plugins to be used in the CMS.
88
+ */
89
+ plugins?: FireCMSPlugin[];
90
+ /**
91
+ * Used to define the name of groups and order of the navigation entries.
92
+ */
93
+ navigationGroupMappings?: NavigationGroupMapping[];
94
+ /**
95
+ * If true, the navigation logic will not be updated until this flag is false
49
96
  */
50
- injectCollections?: (collections: EntityCollection[]) => EntityCollection[];
97
+ disabled?: boolean;
98
+
99
+ /**
100
+ * @deprecated
101
+ * Use `navigationGroupMappings` instead.
102
+ */
103
+ viewsOrder?: string[];
51
104
  };
52
105
 
53
- export function useBuildNavigationController<EC extends EntityCollection, UserType extends User>(props: BuildNavigationContextProps<EC, UserType>): NavigationController {
106
+ export function useBuildNavigationController<EC extends EntityCollection, USER extends User>(props: BuildNavigationContextProps<EC, USER>): NavigationController {
54
107
  const {
55
108
  basePath = DEFAULT_BASE_PATH,
56
109
  baseCollectionPath = DEFAULT_COLLECTION_PATH,
@@ -60,18 +113,23 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
60
113
  views: viewsProp,
61
114
  adminViews: adminViewsProp,
62
115
  viewsOrder,
116
+ plugins,
63
117
  userConfigPersistence,
64
118
  dataSourceDelegate,
65
- injectCollections
119
+ disabled,
120
+ navigationGroupMappings
66
121
  } = props;
67
122
 
123
+ const navigate = useNavigate();
124
+
68
125
  const collectionsRef = useRef<EntityCollection[] | undefined>();
69
126
  const viewsRef = useRef<CMSView[] | undefined>();
70
127
  const adminViewsRef = useRef<CMSView[] | undefined>();
128
+ const navigationEntriesOrderRef = useRef<string[] | undefined>();
71
129
 
72
130
  const [initialised, setInitialised] = useState<boolean>(false);
73
131
 
74
- const [topLevelNavigation, setTopLevelNavigation] = useState<TopNavigationResult | undefined>(undefined);
132
+ const [topLevelNavigation, setTopLevelNavigation] = useState<NavigationResult | undefined>(undefined);
75
133
  const [navigationLoading, setNavigationLoading] = useState<boolean>(true);
76
134
  const [navigationLoadingError, setNavigationLoadingError] = useState<Error | undefined>(undefined);
77
135
 
@@ -88,103 +146,159 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
88
146
  const buildUrlCollectionPath = useCallback((path: string): string => `${removeInitialAndTrailingSlashes(baseCollectionPath)}/${encodePath(path)}`,
89
147
  [baseCollectionPath]);
90
148
 
91
- const computeTopNavigation = useCallback((collections: EntityCollection[], views: CMSView[], adminViews: CMSView[], viewsOrder?: string[]): TopNavigationResult => {
92
- let navigationEntries: TopNavigationEntry[] = [
93
- ...(collections ?? []).map(collection => (!collection.hideFromNavigation
94
- ? ({
95
- url: buildUrlCollectionPath(collection.id ?? collection.path),
149
+ const allPluginGroups = plugins?.flatMap(plugin => plugin.homePage?.navigationEntries ? plugin.homePage.navigationEntries.map(e => e.name) : []) ?? [];
150
+ const pluginGroups = [...new Set(allPluginGroups)];
151
+
152
+ const onNavigationEntriesOrderUpdate = useCallback((entries: NavigationGroupMapping[]) => {
153
+ if (!plugins) {
154
+ return;
155
+ }
156
+ // remove all groups that have no entries
157
+ const filteredEntries = entries.filter(entry => entry.entries.length > 0);
158
+ if (plugins.some(plugin => plugin.homePage?.onNavigationEntriesUpdate)) {
159
+ plugins.forEach(plugin => {
160
+ if (plugin.homePage?.onNavigationEntriesUpdate) {
161
+ plugin.homePage.onNavigationEntriesUpdate(filteredEntries);
162
+ }
163
+ });
164
+ }
165
+
166
+ }, [plugins]);
167
+
168
+ const computeTopNavigation = useCallback((collections: EntityCollection[], views: CMSView[], adminViews: CMSView[], viewsOrder?: string[]): NavigationResult => {
169
+
170
+ const finalNavigationGroupMappings: NavigationGroupMapping[] = computeNavigationGroups({
171
+ navigationGroupMappings: navigationGroupMappings,
172
+ collections,
173
+ views,
174
+ plugins: plugins
175
+ });
176
+
177
+ const allPluginNavigationEntries = finalNavigationGroupMappings.map((g) => g.entries).flat() ?? [];
178
+ const navigationEntriesOrder = ([...new Set(allPluginNavigationEntries)]);
179
+
180
+ let navigationEntries: NavigationEntry[] = [
181
+ ...(collections ?? []).reduce((acc, collection) => {
182
+ if (collection.hideFromNavigation) return acc;
183
+
184
+ const pathKey = collection.id ?? collection.path;
185
+ let groupName = getGroup(collection); // Initial group
186
+
187
+ if (finalNavigationGroupMappings) {
188
+ for (const pluginGroupDef of finalNavigationGroupMappings) {
189
+ if (pluginGroupDef.entries.includes(pathKey)) {
190
+ groupName = pluginGroupDef.name;
191
+ break;
192
+ }
193
+ }
194
+ }
195
+
196
+ acc.push({
197
+ id: `collection:${pathKey}`,
198
+ url: buildUrlCollectionPath(pathKey),
96
199
  type: "collection",
97
200
  name: collection.name.trim(),
98
- path: collection.id ?? collection.path,
201
+ path: pathKey,
99
202
  collection,
100
203
  description: collection.description?.trim(),
101
- group: getGroup(collection)
102
- } satisfies TopNavigationEntry)
103
- : undefined))
104
- .filter(Boolean) as TopNavigationEntry[],
105
- ...(views ?? []).map(view =>
106
- !view.hideFromNavigation
107
- ? ({
108
- url: buildCMSUrlPath(Array.isArray(view.path) ? view.path[0] : view.path),
109
- name: view.name.trim(),
110
- type: "view",
111
- path: view.path,
112
- view,
113
- description: view.description?.trim(),
114
- group: getGroup(view)
115
- } satisfies TopNavigationEntry)
116
- : undefined)
117
- .filter(Boolean) as TopNavigationEntry[],
118
- ...(adminViews ?? []).map(view =>
119
- !view.hideFromNavigation
120
- ? ({
121
- url: buildCMSUrlPath(Array.isArray(view.path) ? view.path[0] : view.path),
122
- name: view.name.trim(),
123
- type: "admin",
124
- path: view.path,
125
- view,
126
- description: view.description?.trim(),
127
- group: "Admin"
128
- } satisfies TopNavigationEntry)
129
- : undefined)
130
- .filter(Boolean) as TopNavigationEntry[]
204
+ group: groupName ?? NAVIGATION_DEFAULT_GROUP_NAME
205
+ });
206
+ return acc;
207
+ }, [] as NavigationEntry[]),
208
+
209
+ ...(views ?? []).reduce((acc, view) => {
210
+ if (view.hideFromNavigation) return acc;
211
+
212
+ const pathKey = Array.isArray(view.path) ? view.path[0] : view.path;
213
+ let groupName = getGroup(view); // Initial group
214
+
215
+ if (finalNavigationGroupMappings) {
216
+ for (const pluginGroupDef of finalNavigationGroupMappings) {
217
+ if (pluginGroupDef.entries.includes(pathKey)) {
218
+ groupName = pluginGroupDef.name;
219
+ break;
220
+ }
221
+ }
222
+ }
223
+
224
+ acc.push({
225
+ id: `view:${pathKey}`,
226
+ url: buildCMSUrlPath(pathKey),
227
+ name: view.name.trim(),
228
+ type: "view",
229
+ path: view.path,
230
+ view,
231
+ description: view.description?.trim(),
232
+ group: groupName ?? NAVIGATION_DEFAULT_GROUP_NAME
233
+ });
234
+ return acc;
235
+ }, [] as NavigationEntry[]),
236
+
237
+ ...(adminViews ?? []).reduce((acc, view) => {
238
+ if (view.hideFromNavigation) return acc;
239
+
240
+ const pathKey = Array.isArray(view.path) ? view.path[0] : view.path;
241
+ const groupName = NAVIGATION_ADMIN_GROUP_NAME;
242
+
243
+ acc.push({
244
+ id: `admin:${pathKey}`,
245
+ url: buildCMSUrlPath(pathKey),
246
+ name: view.name.trim(),
247
+ type: "admin",
248
+ path: view.path,
249
+ view,
250
+ description: view.description?.trim(),
251
+ group: groupName
252
+ });
253
+ return acc;
254
+ }, [] as NavigationEntry[])
131
255
  ];
132
256
 
133
- // Sort by group, entries with group "Admin" will go last, and second to last will be the group "Views"
134
- navigationEntries = navigationEntries.sort((a, b) => {
135
- if (a.group !== "Views" && a.group !== "Admin" && (b.group === "Views" || b.group === "Admin")) {
136
- return -1;
137
- }
138
- if (b.group !== "Views" && b.group !== "Admin" && (a.group === "Views" || a.group === "Admin")) {
139
- return 1;
140
- }
141
- if (a.group === "Admin" && b.group !== "Admin") {
142
- return 1;
143
- }
144
- if (a.group !== "Admin" && b.group === "Admin") {
145
- return -1;
146
- }
147
- if (a.group === "Views" && b.group !== "Views") {
148
- return -1;
149
- }
150
- if (a.group !== "Views" && b.group === "Views") {
151
- return 1;
152
- }
153
- return 0;
257
+ const groupOrderValue = (groupName?: string): number => {
258
+ if (groupName === NAVIGATION_ADMIN_GROUP_NAME) return 1;
259
+ return 0; // Other groups
260
+ };
154
261
 
262
+ navigationEntries = navigationEntries.sort((a, b) => {
263
+ return groupOrderValue(a.group) - groupOrderValue(b.group);
155
264
  });
156
265
 
157
- if (viewsOrder) {
266
+ const usedViewsOrder = viewsOrder ?? navigationEntriesOrder;
267
+ if (usedViewsOrder) {
158
268
  navigationEntries = navigationEntries.sort((a, b) => {
159
- const aIndex = viewsOrder.indexOf(a.path);
160
- const bIndex = viewsOrder.indexOf(b.path);
161
- if (aIndex === -1 && bIndex === -1) {
162
- return 0;
163
- }
164
- if (aIndex === -1) {
165
- return 1;
166
- }
167
- if (bIndex === -1) {
168
- return -1;
169
- }
269
+ const getSortPath = (navEntry: NavigationEntry) => typeof navEntry.path === "string" ? navEntry.path : navEntry.path[0];
270
+ const aIndex = usedViewsOrder.indexOf(getSortPath(a));
271
+ const bIndex = usedViewsOrder.indexOf(getSortPath(b));
272
+ if (aIndex === -1 && bIndex === -1) return 0;
273
+ if (aIndex === -1) return 1;
274
+ if (bIndex === -1) return -1;
170
275
  return aIndex - bIndex;
171
276
  });
172
277
  }
173
278
 
174
- const groups: string[] = Object.values(navigationEntries)
279
+ const collectedGroupsFromEntries = navigationEntries
175
280
  .map(e => e.group)
176
- .filter(Boolean)
177
- .filter((value, index, array) => array.indexOf(value) === index) as string[];
281
+ .filter(Boolean) as string[];
282
+
283
+ const allDefinedGroups = [
284
+ ...(pluginGroups ?? []),
285
+ ...collectedGroupsFromEntries
286
+ ];
287
+
288
+ const uniqueGroups = [...new Set(allDefinedGroups)]
289
+ .sort((a, b) => groupOrderValue(a) - groupOrderValue(b));
178
290
 
179
291
  return {
292
+ allowDragAndDrop: plugins?.some(plugin => plugin.homePage?.allowDragAndDrop) ?? false,
180
293
  navigationEntries,
181
- groups
294
+ groups: uniqueGroups,
295
+ onNavigationEntriesUpdate: onNavigationEntriesOrderUpdate,
182
296
  };
183
- }, [buildCMSUrlPath, buildUrlCollectionPath]);
297
+ }, [navigationGroupMappings, buildCMSUrlPath, buildUrlCollectionPath, pluginGroups, onNavigationEntriesOrderUpdate]);
184
298
 
185
299
  const refreshNavigation = useCallback(async () => {
186
300
 
187
- if (authController.initialLoading)
301
+ if (disabled || authController.initialLoading)
188
302
  return;
189
303
 
190
304
  console.debug("Refreshing navigation");
@@ -192,15 +306,18 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
192
306
  try {
193
307
 
194
308
  const [resolvedCollections = [], resolvedViews, resolvedAdminViews = []] = await Promise.all([
195
- resolveCollections(collectionsProp, collectionPermissions, authController, dataSourceDelegate, injectCollections),
309
+ resolveCollections(collectionsProp, collectionPermissions, authController, dataSourceDelegate, plugins),
196
310
  resolveCMSViews(viewsProp, authController, dataSourceDelegate),
197
311
  resolveCMSViews(adminViewsProp, authController, dataSourceDelegate)
198
312
  ]
199
313
  );
200
314
 
315
+ const computedTopLevelNav = computeTopNavigation(resolvedCollections, resolvedViews, resolvedAdminViews, viewsOrder);
316
+
201
317
  let shouldUpdateTopLevelNav = false;
202
318
  if (!areCollectionListsEqual(collectionsRef.current ?? [], resolvedCollections)) {
203
319
  collectionsRef.current = resolvedCollections;
320
+ console.debug("Collections have changed", resolvedCollections);
204
321
  shouldUpdateTopLevelNav = true;
205
322
  }
206
323
  if (collectionsRef.current === undefined) {
@@ -216,7 +333,12 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
216
333
  shouldUpdateTopLevelNav = true;
217
334
  }
218
335
 
219
- const computedTopLevelNav = computeTopNavigation(resolvedCollections, resolvedViews, resolvedAdminViews, viewsOrder);
336
+ const navigationEntriesOrder = computedTopLevelNav.navigationEntries.map(e => e.id);
337
+ if (!equal(navigationEntriesOrderRef.current, navigationEntriesOrder)) {
338
+ navigationEntriesOrderRef.current = navigationEntriesOrder;
339
+ shouldUpdateTopLevelNav = true;
340
+ }
341
+
220
342
  if (shouldUpdateTopLevelNav && !equal(topLevelNavigation, computedTopLevelNav)) {
221
343
  setTopLevelNavigation(computedTopLevelNav);
222
344
  }
@@ -225,18 +347,20 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
225
347
  setNavigationLoadingError(e as any);
226
348
  }
227
349
 
228
- setNavigationLoading(false);
229
- setInitialised(true);
350
+ if (navigationLoading)
351
+ setNavigationLoading(false);
352
+ if (!initialised)
353
+ setInitialised(true);
230
354
 
231
355
  }, [
232
356
  collectionsProp,
233
357
  collectionPermissions,
234
358
  authController.user,
235
359
  authController.initialLoading,
360
+ disabled,
236
361
  viewsProp,
237
362
  adminViewsProp,
238
363
  computeTopNavigation,
239
- injectCollections
240
364
  ]);
241
365
 
242
366
  useEffect(() => {
@@ -276,6 +400,16 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
276
400
 
277
401
  }, [userConfigPersistence]);
278
402
 
403
+ const getCollectionById = useCallback((id: string): EC | undefined => {
404
+ const collections = collectionsRef.current;
405
+ if (collections === undefined)
406
+ throw Error("getCollectionById: Collections have not been initialised yet");
407
+ const collection: EntityCollection | undefined = collections.find(c => c.id === id);
408
+ if (!collection)
409
+ return undefined;
410
+ return collection as EC;
411
+ }, []);
412
+
279
413
  const getCollectionFromPaths = useCallback(<EC extends EntityCollection>(pathSegments: string[]): EC | undefined => {
280
414
 
281
415
  const collections = collectionsRef.current;
@@ -328,16 +462,7 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
328
462
  throw Error("Expected path starting with " + fullCollectionPath);
329
463
  }, [fullCollectionPath]);
330
464
 
331
- const buildUrlEditCollectionPath = useCallback(({
332
- path
333
- }: {
334
- path: string
335
- }): string => {
336
- return `s/edit/${encodePath(path)}`;
337
- },
338
- []);
339
-
340
- const resolveAliasesFrom = useCallback((path: string): string => {
465
+ const resolveIdsFrom = useCallback((path: string): string => {
341
466
  const collections = collectionsRef.current ?? [];
342
467
  return resolveCollectionPathIds(path, collections);
343
468
  }, []);
@@ -392,29 +517,23 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
392
517
  baseCollectionPath,
393
518
  initialised,
394
519
  getCollection,
520
+ getCollectionById,
395
521
  getCollectionFromPaths,
396
522
  getCollectionFromIds,
397
523
  isUrlCollectionPath,
398
524
  urlPathToDataPath,
399
525
  buildUrlCollectionPath,
400
- buildUrlEditCollectionPath,
401
- buildCMSUrlPath,
402
- resolveAliasesFrom,
526
+ resolveIdsFrom,
403
527
  topLevelNavigation,
404
528
  refreshNavigation,
405
529
  getParentReferencesFromPath: getAllParentReferencesForPath,
406
530
  getParentCollectionIds,
407
- convertIdsToPaths
531
+ convertIdsToPaths,
532
+ navigate,
533
+ plugins
408
534
  };
409
535
  }
410
536
 
411
- export function getSidePanelKey(path: string, entityId?: string) {
412
- if (entityId)
413
- return `${removeInitialAndTrailingSlashes(path)}/${removeInitialAndTrailingSlashes(entityId)}`;
414
- else
415
- return removeInitialAndTrailingSlashes(path);
416
- }
417
-
418
537
  function encodePath(input: string) {
419
538
  return encodeURIComponent(removeInitialAndTrailingSlashes(input))
420
539
  .replaceAll("%2F", "/")
@@ -423,9 +542,10 @@ function encodePath(input: string) {
423
542
 
424
543
  function filterOutNotAllowedCollections(resolvedCollections: EntityCollection[], authController: AuthController<User>): EntityCollection[] {
425
544
  return resolvedCollections
545
+ .filter((c) => Boolean(c.path))
426
546
  .filter((c) => {
427
547
  if (!c.permissions) return true;
428
- const resolvedPermissions = resolvePermissions(c, authController, c.path, null)
548
+ const resolvedPermissions = resolvePermissions(c, authController, c.path, null);
429
549
  return resolvedPermissions?.read !== false;
430
550
  })
431
551
  .map((c) => {
@@ -437,11 +557,24 @@ function filterOutNotAllowedCollections(resolvedCollections: EntityCollection[],
437
557
  });
438
558
  }
439
559
 
560
+ function applyPluginModifyCollection(resolvedCollections: EntityCollection[], modifyCollection: (collection: EntityCollection) => EntityCollection) {
561
+ return resolvedCollections.map((collection: EntityCollection): EntityCollection => {
562
+ const modifiedCollection = modifyCollection(collection);
563
+ if (modifiedCollection.subcollections) {
564
+ return {
565
+ ...modifiedCollection,
566
+ subcollections: applyPluginModifyCollection(modifiedCollection.subcollections, modifyCollection)
567
+ } satisfies EntityCollection;
568
+ }
569
+ return modifiedCollection;
570
+ });
571
+ }
572
+
440
573
  async function resolveCollections(collections: undefined | EntityCollection[] | EntityCollectionsBuilder<any>,
441
574
  collectionPermissions: PermissionsBuilder | undefined,
442
575
  authController: AuthController,
443
576
  dataSource: DataSourceDelegate,
444
- injectCollections?: (collections: EntityCollection[]) => EntityCollection[]): Promise<EntityCollection[]> {
577
+ plugins: FireCMSPlugin[] | undefined): Promise<EntityCollection[]> {
445
578
  let resolvedCollections: EntityCollection[] = [];
446
579
  if (typeof collections === "function") {
447
580
  resolvedCollections = await collections({
@@ -453,14 +586,20 @@ async function resolveCollections(collections: undefined | EntityCollection[] |
453
586
  resolvedCollections = collections;
454
587
  }
455
588
 
456
- if (injectCollections) {
457
- resolvedCollections = injectCollections(resolvedCollections ?? []);
589
+ if (plugins) {
590
+ for (const plugin of plugins) {
591
+ if (plugin.collection?.modifyCollection) {
592
+ resolvedCollections = applyPluginModifyCollection(resolvedCollections, plugin.collection.modifyCollection);
593
+ }
594
+
595
+ if (plugin.collection?.injectCollections) {
596
+ resolvedCollections = plugin.collection.injectCollections(resolvedCollections ?? []);
597
+ }
598
+ }
458
599
  }
459
600
 
460
601
  resolvedCollections = applyPermissionsFunctionIfEmpty(resolvedCollections, collectionPermissions);
461
-
462
602
  resolvedCollections = filterOutNotAllowedCollections(resolvedCollections, authController);
463
-
464
603
  return resolvedCollections;
465
604
  }
466
605
 
@@ -481,9 +620,9 @@ async function resolveCMSViews(baseViews: CMSView[] | CMSViewsBuilder | undefine
481
620
  function getGroup(collectionOrView: EntityCollection<any, any> | CMSView) {
482
621
  const trimmed = collectionOrView.group?.trim();
483
622
  if (!trimmed || trimmed === "") {
484
- return "Views";
623
+ return NAVIGATION_DEFAULT_GROUP_NAME;
485
624
  }
486
- return trimmed ?? "Views";
625
+ return trimmed ?? NAVIGATION_DEFAULT_GROUP_NAME;
487
626
  }
488
627
 
489
628
  function areCollectionListsEqual(a: EntityCollection[], b: EntityCollection[]) {
@@ -509,5 +648,128 @@ function areCollectionsEqual(a: EntityCollection, b: EntityCollection) {
509
648
  if (!areCollectionListsEqual(subcollectionsA ?? [], subcollectionsB ?? [])) {
510
649
  return false;
511
650
  }
512
- return equal(restA, restB);
651
+ return equal(removeFunctions(restA), removeFunctions(restB));
652
+ }
653
+
654
+ function useCustomBlocker(): NavigationBlocker {
655
+ const [blockListeners, setBlockListeners] = useState<Record<string, {
656
+ block: boolean,
657
+ basePath?: string
658
+ }>>({});
659
+
660
+ const shouldBlock = Object.values(blockListeners).some(b => b.block);
661
+
662
+ let blocker: any;
663
+ try {
664
+ blocker = useBlocker(({
665
+ nextLocation
666
+ }) => {
667
+ const allBasePaths = Object.values(blockListeners).map(b => b.basePath).filter(Boolean) as string[];
668
+ if (allBasePaths && allBasePaths.some(path => nextLocation.pathname.startsWith(path)))
669
+ return false;
670
+ return shouldBlock;
671
+ });
672
+ } catch (e) {
673
+ // console.warn("Blocker not available, navigation will not be blocked");
674
+ }
675
+
676
+ const updateBlockListener = (path: string, block: boolean, basePath?: string) => {
677
+ setBlockListeners(prev => ({
678
+ ...prev,
679
+ [path]: {
680
+ block,
681
+ basePath
682
+ }
683
+ }));
684
+ return () => setBlockListeners(prev => {
685
+ const {
686
+ [path]: removed,
687
+ ...rest
688
+ } = prev;
689
+ return rest;
690
+ })
691
+ };
692
+
693
+ const isBlocked = (path: string) => {
694
+ return (blockListeners[path]?.block ?? false) && blocker?.state === "blocked";
695
+ }
696
+
697
+ return {
698
+ updateBlockListener,
699
+ isBlocked,
700
+ proceed: blocker?.proceed,
701
+ reset: blocker?.reset
702
+ }
703
+ }
704
+
705
+ function computeNavigationGroups({
706
+ navigationGroupMappings,
707
+ collections,
708
+ views,
709
+ plugins
710
+ }: {
711
+ navigationGroupMappings?: NavigationGroupMapping[],
712
+ collections?: EntityCollection[],
713
+ views?: CMSView[],
714
+ plugins?: FireCMSPlugin[]
715
+ }): NavigationGroupMapping[] {
716
+
717
+ let result = navigationGroupMappings;
718
+
719
+ result = plugins ? plugins?.reduce((acc, plugin) => {
720
+ if (plugin.homePage?.navigationEntries) {
721
+ plugin.homePage.navigationEntries.forEach((entry) => {
722
+ const {
723
+ name,
724
+ entries
725
+ } = entry;
726
+ const existingGroup = acc.find(entry => entry.name === name);
727
+ if (existingGroup) {
728
+ existingGroup.entries.push(...entries);
729
+ } else {
730
+ acc.push({
731
+ name,
732
+ entries: [...entries]
733
+ });
734
+ }
735
+ });
736
+
737
+ }
738
+ return acc;
739
+ }, [...(result ?? [])] as NavigationGroupMapping[]) : result;
740
+
741
+ if (!result) {
742
+ // Convert views and collections to navigation group mappings, grouped by their group name
743
+ result = [];
744
+ const groupMap: Record<string, string[]> = {};
745
+
746
+ // Add collections
747
+ (collections ?? []).forEach(collection => {
748
+ const name = getGroup(collection);
749
+ const entry = collection.id ?? collection.path;
750
+ if (!groupMap[name]) groupMap[name] = [];
751
+ groupMap[name].push(entry);
752
+ });
753
+
754
+ // Add views
755
+ (views ?? []).forEach(view => {
756
+ const name = getGroup(view);
757
+ const entry = Array.isArray(view.path) ? view.path[0] : view.path;
758
+ if (!groupMap[name]) groupMap[name] = [];
759
+ groupMap[name].push(entry);
760
+ });
761
+
762
+ // Convert groupMap to initialGroupMappings array
763
+ result = Object.entries(groupMap).map(([name, entries]) => ({
764
+ name,
765
+ entries
766
+ }));
767
+ }
768
+
769
+ // Remove duplicates in entries
770
+ result.forEach(group => {
771
+ group.entries = [...new Set(group.entries)];
772
+ });
773
+
774
+ return result;
513
775
  }