@firecms/core 3.0.0-canary.27 → 3.0.0-canary.270

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 (408) hide show
  1. package/README.md +3 -3
  2. package/dist/app/AppBar.d.ts +12 -0
  3. package/dist/app/Drawer.d.ts +16 -0
  4. package/dist/app/Scaffold.d.ts +34 -0
  5. package/dist/app/index.d.ts +4 -0
  6. package/dist/app/useApp.d.ts +16 -0
  7. package/dist/components/ArrayContainer.d.ts +31 -12
  8. package/dist/components/CircularProgressCenter.d.ts +1 -1
  9. package/dist/components/ClearFilterSortButton.d.ts +5 -0
  10. package/dist/components/{DeleteConfirmationDialog.d.ts → ConfirmationDialog.d.ts} +1 -1
  11. package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +14 -13
  12. package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +2 -2
  13. package/dist/components/EntityCollectionTable/EntityCollectionTableProps.d.ts +22 -6
  14. package/dist/components/EntityCollectionTable/PropertyTableCell.d.ts +1 -0
  15. package/dist/components/EntityCollectionTable/column_utils.d.ts +1 -2
  16. package/dist/components/EntityCollectionTable/fields/TableReferenceField.d.ts +3 -1
  17. package/dist/components/EntityCollectionTable/index.d.ts +1 -1
  18. package/dist/components/EntityCollectionTable/internal/CollectionTableToolbar.d.ts +1 -4
  19. package/dist/components/EntityCollectionTable/internal/EntityTableCell.d.ts +2 -2
  20. package/dist/components/EntityCollectionTable/internal/popup_field/PopupFormField.d.ts +7 -4
  21. package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +20 -2
  22. package/dist/components/EntityCollectionView/EntityCollectionViewStartActions.d.ts +11 -0
  23. package/dist/components/EntityCollectionView/utils.d.ts +3 -0
  24. package/dist/components/EntityJsonPreview.d.ts +3 -0
  25. package/dist/components/EntityPreview.d.ts +10 -7
  26. package/dist/components/ErrorView.d.ts +1 -1
  27. package/dist/components/HomePage/DefaultHomePage.d.ts +2 -15
  28. package/dist/components/HomePage/HomePageDnD.d.ts +76 -0
  29. package/dist/components/HomePage/NavigationCard.d.ts +3 -1
  30. package/dist/components/HomePage/NavigationCardBinding.d.ts +4 -3
  31. package/dist/components/HomePage/NavigationGroup.d.ts +8 -1
  32. package/dist/components/HomePage/RenameGroupDialog.d.ts +9 -0
  33. package/dist/components/PropertyConfigBadge.d.ts +2 -1
  34. package/dist/components/PropertyIdCopyTooltip.d.ts +8 -0
  35. package/dist/components/ReferenceWidget.d.ts +3 -1
  36. package/dist/components/SelectableTable/SelectableTable.d.ts +14 -4
  37. package/dist/components/SelectableTable/filters/ReferenceFilterField.d.ts +2 -1
  38. package/dist/components/UnsavedChangesDialog.d.ts +8 -0
  39. package/dist/components/VirtualTable/VirtualTableProps.d.ts +24 -12
  40. package/dist/components/VirtualTable/types.d.ts +3 -3
  41. package/dist/components/{EntityCollectionTable/internal → common}/default_entity_actions.d.ts +1 -3
  42. package/dist/components/common/index.d.ts +2 -1
  43. package/dist/components/common/table_height.d.ts +5 -0
  44. package/dist/components/common/types.d.ts +4 -6
  45. package/dist/components/common/useColumnsIds.d.ts +3 -1
  46. package/dist/components/common/{useDataSourceEntityCollectionTableController.d.ts → useDataSourceTableController.d.ts} +13 -2
  47. package/dist/components/common/useDebouncedCallback.d.ts +1 -0
  48. package/dist/components/common/useScrollRestoration.d.ts +14 -0
  49. package/dist/components/index.d.ts +5 -2
  50. package/dist/contexts/BreacrumbsContext.d.ts +8 -0
  51. package/dist/core/DefaultAppBar.d.ts +29 -0
  52. package/dist/core/DefaultDrawer.d.ts +19 -0
  53. package/dist/core/DrawerNavigationItem.d.ts +10 -0
  54. package/dist/core/EntityEditView.d.ts +43 -11
  55. package/dist/core/EntityEditViewFormActions.d.ts +2 -0
  56. package/dist/core/FireCMS.d.ts +3 -3
  57. package/dist/core/FireCMSRouter.d.ts +4 -0
  58. package/dist/core/NavigationRoutes.d.ts +2 -3
  59. package/dist/core/SideDialogs.d.ts +4 -2
  60. package/dist/core/field_configs.d.ts +1 -1
  61. package/dist/core/index.d.ts +4 -4
  62. package/dist/form/EntityForm.d.ts +37 -64
  63. package/dist/form/EntityFormActions.d.ts +21 -0
  64. package/dist/form/PropertyFieldBinding.d.ts +1 -1
  65. package/dist/form/components/ErrorFocus.d.ts +1 -1
  66. package/dist/form/components/FieldHelperText.d.ts +3 -3
  67. package/dist/form/components/FormEntry.d.ts +6 -0
  68. package/dist/form/components/FormLayout.d.ts +5 -0
  69. package/dist/form/components/LabelWithIcon.d.ts +1 -1
  70. package/dist/form/components/LabelWithIconAndTooltip.d.ts +15 -0
  71. package/dist/form/components/StorageItemPreview.d.ts +4 -4
  72. package/dist/form/components/index.d.ts +3 -1
  73. package/dist/form/field_bindings/ArrayCustomShapedFieldBinding.d.ts +1 -1
  74. package/dist/form/field_bindings/ArrayOfReferencesFieldBinding.d.ts +1 -1
  75. package/dist/form/field_bindings/BlockFieldBinding.d.ts +1 -1
  76. package/dist/form/field_bindings/KeyValueFieldBinding.d.ts +1 -1
  77. package/dist/form/field_bindings/MapFieldBinding.d.ts +1 -1
  78. package/dist/form/field_bindings/MarkdownEditorFieldBinding.d.ts +11 -0
  79. package/dist/form/field_bindings/{MultiSelectBinding.d.ts → MultiSelectFieldBinding.d.ts} +1 -1
  80. package/dist/form/field_bindings/ReadOnlyFieldBinding.d.ts +1 -1
  81. package/dist/form/field_bindings/ReferenceAsStringFieldBinding.d.ts +9 -0
  82. package/dist/form/field_bindings/ReferenceFieldBinding.d.ts +2 -2
  83. package/dist/form/field_bindings/RepeatFieldBinding.d.ts +1 -1
  84. package/dist/form/field_bindings/SelectFieldBinding.d.ts +1 -1
  85. package/dist/form/field_bindings/StorageUploadFieldBinding.d.ts +5 -13
  86. package/dist/form/field_bindings/SwitchFieldBinding.d.ts +1 -2
  87. package/dist/form/field_bindings/TextFieldBinding.d.ts +1 -1
  88. package/dist/form/index.d.ts +17 -18
  89. package/dist/form/useClearRestoreValue.d.ts +2 -2
  90. package/dist/hooks/data/delete.d.ts +4 -4
  91. package/dist/hooks/data/save.d.ts +4 -5
  92. package/dist/hooks/data/useCollectionFetch.d.ts +1 -1
  93. package/dist/hooks/data/useEntityFetch.d.ts +4 -3
  94. package/dist/hooks/index.d.ts +2 -0
  95. package/dist/hooks/useAuthController.d.ts +1 -1
  96. package/dist/hooks/useBreadcrumbsController.d.ts +26 -0
  97. package/dist/hooks/useBuildNavigationController.d.ts +57 -13
  98. package/dist/hooks/useCollapsedGroups.d.ts +9 -0
  99. package/dist/hooks/useFireCMSContext.d.ts +1 -1
  100. package/dist/hooks/useModeController.d.ts +1 -2
  101. package/dist/hooks/useProjectLog.d.ts +8 -2
  102. package/dist/hooks/useResolvedNavigationFrom.d.ts +3 -3
  103. package/dist/hooks/useValidateAuthenticator.d.ts +4 -8
  104. package/dist/index.d.ts +1 -0
  105. package/dist/index.es.js +23154 -13912
  106. package/dist/index.es.js.map +1 -1
  107. package/dist/index.umd.js +25917 -588
  108. package/dist/index.umd.js.map +1 -1
  109. package/dist/internal/useBuildDataSource.d.ts +3 -17
  110. package/dist/internal/useBuildSideEntityController.d.ts +3 -3
  111. package/dist/internal/useUnsavedChangesDialog.d.ts +7 -9
  112. package/dist/preview/PropertyPreviewProps.d.ts +6 -1
  113. package/dist/preview/components/EnumValuesChip.d.ts +1 -1
  114. package/dist/preview/components/ReferencePreview.d.ts +4 -3
  115. package/dist/preview/components/StorageThumbnail.d.ts +2 -1
  116. package/dist/preview/components/UrlComponentPreview.d.ts +2 -1
  117. package/dist/preview/util.d.ts +3 -3
  118. package/dist/routes/CustomCMSRoute.d.ts +4 -0
  119. package/dist/routes/FireCMSRoute.d.ts +1 -0
  120. package/dist/routes/HomePageRoute.d.ts +3 -0
  121. package/dist/types/analytics.d.ts +1 -1
  122. package/dist/types/auth.d.ts +8 -10
  123. package/dist/types/collections.d.ts +110 -25
  124. package/dist/types/customization_controller.d.ts +8 -0
  125. package/dist/types/datasource.d.ts +52 -36
  126. package/dist/types/dialogs_controller.d.ts +7 -3
  127. package/dist/types/entities.d.ts +7 -2
  128. package/dist/types/entity_actions.d.ts +72 -8
  129. package/dist/types/entity_callbacks.d.ts +16 -16
  130. package/dist/types/entity_overrides.d.ts +2 -2
  131. package/dist/types/export_import.d.ts +4 -4
  132. package/dist/types/fields.d.ts +74 -42
  133. package/dist/types/firecms.d.ts +16 -3
  134. package/dist/types/firecms_context.d.ts +1 -1
  135. package/dist/types/index.d.ts +0 -1
  136. package/dist/types/navigation.d.ts +62 -19
  137. package/dist/types/permissions.d.ts +4 -4
  138. package/dist/types/plugins.d.ts +56 -13
  139. package/dist/types/properties.d.ts +81 -25
  140. package/dist/types/property_config.d.ts +1 -3
  141. package/dist/types/roles.d.ts +3 -0
  142. package/dist/types/side_dialogs_controller.d.ts +10 -0
  143. package/dist/types/side_entity_controller.d.ts +14 -1
  144. package/dist/types/storage.d.ts +75 -0
  145. package/dist/types/user.d.ts +1 -0
  146. package/dist/util/builders.d.ts +3 -3
  147. package/dist/util/callbacks.d.ts +2 -0
  148. package/dist/util/createFormexStub.d.ts +2 -0
  149. package/dist/util/entities.d.ts +3 -3
  150. package/dist/util/entity_actions.d.ts +2 -0
  151. package/dist/util/entity_cache.d.ts +23 -0
  152. package/dist/util/icon_list.d.ts +5 -1
  153. package/dist/util/icon_synonyms.d.ts +1 -98
  154. package/dist/util/icons.d.ts +7 -4
  155. package/dist/util/index.d.ts +3 -0
  156. package/dist/util/navigation_from_path.d.ts +10 -1
  157. package/dist/util/navigation_utils.d.ts +15 -3
  158. package/dist/util/objects.d.ts +2 -1
  159. package/dist/util/permissions.d.ts +4 -4
  160. package/dist/util/plurals.d.ts +0 -2
  161. package/dist/util/property_utils.d.ts +4 -4
  162. package/dist/util/references.d.ts +2 -2
  163. package/dist/util/resolutions.d.ts +42 -17
  164. package/dist/util/storage.d.ts +23 -2
  165. package/dist/util/useStorageUploadController.d.ts +3 -3
  166. package/package.json +69 -52
  167. package/src/app/AppBar.tsx +18 -0
  168. package/src/app/Drawer.tsx +24 -0
  169. package/src/app/Scaffold.tsx +253 -0
  170. package/src/app/index.ts +4 -0
  171. package/src/app/useApp.tsx +32 -0
  172. package/src/components/ArrayContainer.tsx +447 -229
  173. package/src/components/CircularProgressCenter.tsx +2 -2
  174. package/src/components/ClearFilterSortButton.tsx +41 -0
  175. package/src/components/{DeleteConfirmationDialog.tsx → ConfirmationDialog.tsx} +12 -11
  176. package/src/components/DeleteEntityDialog.tsx +13 -20
  177. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +65 -40
  178. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +38 -31
  179. package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +30 -9
  180. package/src/components/EntityCollectionTable/PropertyTableCell.tsx +72 -42
  181. package/src/components/EntityCollectionTable/column_utils.tsx +3 -3
  182. package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +30 -16
  183. package/src/components/EntityCollectionTable/fields/TableStorageUpload.tsx +19 -17
  184. package/src/components/EntityCollectionTable/index.tsx +1 -1
  185. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +34 -39
  186. package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +49 -36
  187. package/src/components/EntityCollectionTable/internal/EntityTableCellActions.tsx +20 -8
  188. package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +135 -105
  189. package/src/components/EntityCollectionTable/internal/popup_field/useDraggable.tsx +9 -9
  190. package/src/components/EntityCollectionView/EntityCollectionView.tsx +235 -118
  191. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +7 -4
  192. package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +68 -0
  193. package/src/components/EntityCollectionView/useSelectionController.tsx +20 -7
  194. package/src/components/EntityCollectionView/utils.ts +19 -0
  195. package/src/components/EntityJsonPreview.tsx +66 -0
  196. package/src/components/EntityPreview.tsx +83 -62
  197. package/src/components/EntityView.tsx +13 -10
  198. package/src/components/ErrorView.tsx +4 -4
  199. package/src/components/FireCMSLogo.tsx +7 -51
  200. package/src/components/HomePage/DefaultHomePage.tsx +512 -157
  201. package/src/components/HomePage/FavouritesView.tsx +9 -14
  202. package/src/components/HomePage/HomePageDnD.tsx +599 -0
  203. package/src/components/HomePage/NavigationCard.tsx +48 -39
  204. package/src/components/HomePage/NavigationCardBinding.tsx +17 -16
  205. package/src/components/HomePage/NavigationGroup.tsx +144 -30
  206. package/src/components/HomePage/RenameGroupDialog.tsx +117 -0
  207. package/src/components/HomePage/SmallNavigationCard.tsx +5 -6
  208. package/src/components/NotFoundPage.tsx +2 -2
  209. package/src/components/PropertyConfigBadge.tsx +9 -3
  210. package/src/components/PropertyIdCopyTooltip.tsx +47 -0
  211. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +23 -13
  212. package/src/components/ReferenceWidget.tsx +21 -11
  213. package/src/components/SearchIconsView.tsx +10 -7
  214. package/src/components/SelectableTable/SelectableTable.tsx +157 -145
  215. package/src/components/SelectableTable/filters/BooleanFilterField.tsx +2 -3
  216. package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +25 -8
  217. package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +36 -12
  218. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +92 -23
  219. package/src/components/UnsavedChangesDialog.tsx +46 -0
  220. package/src/components/VirtualTable/VirtualTable.tsx +105 -51
  221. package/src/components/VirtualTable/VirtualTableCell.tsx +1 -9
  222. package/src/components/VirtualTable/VirtualTableHeader.tsx +10 -10
  223. package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +2 -2
  224. package/src/components/VirtualTable/VirtualTableProps.tsx +28 -14
  225. package/src/components/VirtualTable/VirtualTableRow.tsx +5 -6
  226. package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +5 -5
  227. package/src/components/VirtualTable/fields/VirtualTableInput.tsx +2 -2
  228. package/src/components/VirtualTable/fields/VirtualTableNumberInput.tsx +2 -1
  229. package/src/components/VirtualTable/fields/VirtualTableSelect.tsx +16 -28
  230. package/src/components/VirtualTable/types.tsx +2 -3
  231. package/src/components/{EntityCollectionTable/internal → common}/default_entity_actions.tsx +64 -44
  232. package/src/components/common/index.ts +2 -1
  233. package/src/components/{VirtualTable/common.tsx → common/table_height.tsx} +5 -2
  234. package/src/components/common/types.tsx +4 -6
  235. package/src/components/common/useColumnsIds.tsx +24 -3
  236. package/src/components/common/useDataSourceTableController.tsx +420 -0
  237. package/src/components/common/useDebouncedCallback.tsx +20 -0
  238. package/src/components/common/useScrollRestoration.tsx +68 -0
  239. package/src/components/common/useTableSearchHelper.ts +53 -12
  240. package/src/components/index.tsx +6 -2
  241. package/src/contexts/BreacrumbsContext.tsx +38 -0
  242. package/src/contexts/DialogsProvider.tsx +5 -4
  243. package/src/contexts/ModeController.tsx +1 -3
  244. package/src/contexts/SnackbarProvider.tsx +2 -0
  245. package/src/core/DefaultAppBar.tsx +219 -0
  246. package/src/core/DefaultDrawer.tsx +185 -0
  247. package/src/core/DrawerNavigationItem.tsx +66 -0
  248. package/src/core/EntityEditView.tsx +435 -470
  249. package/src/core/EntityEditViewFormActions.tsx +329 -0
  250. package/src/core/EntitySidePanel.tsx +88 -21
  251. package/src/core/FireCMS.tsx +74 -58
  252. package/src/core/FireCMSRouter.tsx +17 -0
  253. package/src/core/NavigationRoutes.tsx +28 -38
  254. package/src/core/SideDialogs.tsx +22 -12
  255. package/src/core/field_configs.tsx +26 -13
  256. package/src/core/index.tsx +6 -5
  257. package/src/form/EntityForm.tsx +620 -534
  258. package/src/form/EntityFormActions.tsx +211 -0
  259. package/src/form/PropertyFieldBinding.tsx +88 -45
  260. package/src/form/components/CustomIdField.tsx +9 -3
  261. package/src/form/components/FieldHelperText.tsx +4 -4
  262. package/src/form/components/FormEntry.tsx +22 -0
  263. package/src/form/components/FormLayout.tsx +16 -0
  264. package/src/form/components/LabelWithIcon.tsx +30 -19
  265. package/src/form/components/LabelWithIconAndTooltip.tsx +28 -0
  266. package/src/form/components/StorageItemPreview.tsx +23 -13
  267. package/src/form/components/StorageUploadProgress.tsx +5 -6
  268. package/src/form/components/index.tsx +3 -1
  269. package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +34 -19
  270. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +50 -36
  271. package/src/form/field_bindings/BlockFieldBinding.tsx +56 -34
  272. package/src/form/field_bindings/DateTimeFieldBinding.tsx +18 -14
  273. package/src/form/field_bindings/KeyValueFieldBinding.tsx +61 -52
  274. package/src/form/field_bindings/MapFieldBinding.tsx +73 -55
  275. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +159 -0
  276. package/src/form/field_bindings/{MultiSelectBinding.tsx → MultiSelectFieldBinding.tsx} +26 -21
  277. package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +11 -16
  278. package/src/form/field_bindings/ReferenceAsStringFieldBinding.tsx +135 -0
  279. package/src/form/field_bindings/ReferenceFieldBinding.tsx +42 -31
  280. package/src/form/field_bindings/RepeatFieldBinding.tsx +62 -35
  281. package/src/form/field_bindings/SelectFieldBinding.tsx +24 -15
  282. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +257 -199
  283. package/src/form/field_bindings/SwitchFieldBinding.tsx +29 -24
  284. package/src/form/field_bindings/TextFieldBinding.tsx +28 -24
  285. package/src/form/index.tsx +17 -37
  286. package/src/form/useClearRestoreValue.tsx +2 -2
  287. package/src/form/validation.ts +13 -23
  288. package/src/hooks/data/delete.ts +6 -5
  289. package/src/hooks/data/save.ts +26 -33
  290. package/src/hooks/data/useCollectionFetch.tsx +3 -3
  291. package/src/hooks/data/useDataSource.tsx +11 -3
  292. package/src/hooks/data/useEntityFetch.tsx +10 -6
  293. package/src/hooks/index.tsx +2 -0
  294. package/src/hooks/useAuthController.tsx +1 -1
  295. package/src/hooks/useBreadcrumbsController.tsx +31 -0
  296. package/src/hooks/useBrowserTitleAndIcon.tsx +1 -1
  297. package/src/hooks/useBuildLocalConfigurationPersistence.tsx +8 -10
  298. package/src/hooks/useBuildModeController.tsx +22 -29
  299. package/src/hooks/useBuildNavigationController.tsx +440 -119
  300. package/src/hooks/useCollapsedGroups.ts +64 -0
  301. package/src/hooks/useFireCMSContext.tsx +3 -33
  302. package/src/hooks/useLargeLayout.tsx +0 -35
  303. package/src/hooks/useModeController.tsx +1 -2
  304. package/src/hooks/useProjectLog.tsx +32 -10
  305. package/src/hooks/useResolvedNavigationFrom.tsx +10 -12
  306. package/src/hooks/useValidateAuthenticator.tsx +17 -37
  307. package/src/index.ts +1 -0
  308. package/src/internal/useBuildDataSource.ts +79 -85
  309. package/src/internal/useBuildSideDialogsController.tsx +4 -2
  310. package/src/internal/useBuildSideEntityController.tsx +204 -77
  311. package/src/internal/useUnsavedChangesDialog.tsx +127 -91
  312. package/src/preview/PropertyPreview.tsx +34 -25
  313. package/src/preview/PropertyPreviewProps.tsx +7 -1
  314. package/src/preview/components/BooleanPreview.tsx +2 -2
  315. package/src/preview/components/EmptyValue.tsx +1 -1
  316. package/src/preview/components/EnumValuesChip.tsx +2 -2
  317. package/src/preview/components/ImagePreview.tsx +26 -37
  318. package/src/preview/components/ReferencePreview.tsx +26 -36
  319. package/src/preview/components/StorageThumbnail.tsx +5 -1
  320. package/src/preview/components/UrlComponentPreview.tsx +60 -28
  321. package/src/preview/property_previews/ArrayOfMapsPreview.tsx +6 -6
  322. package/src/preview/property_previews/ArrayOfReferencesPreview.tsx +7 -5
  323. package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +5 -4
  324. package/src/preview/property_previews/ArrayOfStringsPreview.tsx +4 -4
  325. package/src/preview/property_previews/ArrayOneOfPreview.tsx +7 -6
  326. package/src/preview/property_previews/ArrayPropertyPreview.tsx +7 -6
  327. package/src/preview/property_previews/MapPropertyPreview.tsx +12 -11
  328. package/src/preview/property_previews/SkeletonPropertyComponent.tsx +13 -13
  329. package/src/preview/property_previews/StringPropertyPreview.tsx +3 -3
  330. package/src/preview/util.ts +10 -10
  331. package/src/routes/CustomCMSRoute.tsx +21 -0
  332. package/src/routes/FireCMSRoute.tsx +246 -0
  333. package/src/routes/HomePageRoute.tsx +17 -0
  334. package/src/types/analytics.ts +3 -0
  335. package/src/types/auth.tsx +9 -13
  336. package/src/types/collections.ts +132 -30
  337. package/src/types/customization_controller.tsx +9 -1
  338. package/src/types/datasource.ts +61 -43
  339. package/src/types/dialogs_controller.tsx +7 -3
  340. package/src/types/entities.ts +12 -2
  341. package/src/types/entity_actions.tsx +86 -10
  342. package/src/types/entity_callbacks.ts +18 -18
  343. package/src/types/entity_overrides.tsx +2 -2
  344. package/src/types/export_import.ts +4 -4
  345. package/src/types/fields.tsx +85 -46
  346. package/src/types/firecms.tsx +18 -4
  347. package/src/types/firecms_context.tsx +1 -1
  348. package/src/types/index.ts +0 -1
  349. package/src/types/navigation.ts +77 -24
  350. package/src/types/permissions.ts +5 -5
  351. package/src/types/plugins.tsx +66 -15
  352. package/src/types/properties.ts +96 -27
  353. package/src/types/property_config.tsx +1 -2
  354. package/src/types/roles.ts +3 -0
  355. package/src/types/side_dialogs_controller.tsx +15 -0
  356. package/src/types/side_entity_controller.tsx +16 -1
  357. package/src/types/storage.ts +83 -1
  358. package/src/types/user.ts +2 -0
  359. package/src/util/builders.ts +10 -8
  360. package/src/util/callbacks.ts +119 -0
  361. package/src/util/createFormexStub.tsx +62 -0
  362. package/src/util/entities.ts +10 -7
  363. package/src/util/entity_actions.ts +28 -0
  364. package/src/util/entity_cache.ts +204 -0
  365. package/src/util/enums.ts +1 -1
  366. package/src/util/icon_list.ts +16 -10
  367. package/src/util/icon_synonyms.ts +3 -100
  368. package/src/util/icons.tsx +36 -11
  369. package/src/util/index.ts +3 -0
  370. package/src/util/join_collections.ts +9 -2
  371. package/src/util/make_properties_editable.ts +13 -5
  372. package/src/util/navigation_from_path.ts +33 -12
  373. package/src/util/navigation_utils.ts +141 -25
  374. package/src/util/objects.ts +90 -33
  375. package/src/util/parent_references_from_path.ts +3 -3
  376. package/src/util/permissions.ts +9 -8
  377. package/src/util/plurals.ts +0 -2
  378. package/src/util/property_utils.tsx +17 -6
  379. package/src/util/references.ts +19 -8
  380. package/src/util/resolutions.ts +122 -48
  381. package/src/util/storage.ts +79 -21
  382. package/src/util/strings.ts +2 -2
  383. package/src/util/useStorageUploadController.tsx +91 -28
  384. package/dist/components/EntityCollectionTable/internal/popup_field/ElementResizeListener.d.ts +0 -5
  385. package/dist/components/FireCMSAppBar.d.ts +0 -26
  386. package/dist/components/PropertyIdCopyTooltipContent.d.ts +0 -3
  387. package/dist/components/VirtualTable/common.d.ts +0 -2
  388. package/dist/core/Drawer.d.ts +0 -23
  389. package/dist/core/Scaffold.d.ts +0 -55
  390. package/dist/core/SideEntityView.d.ts +0 -7
  391. package/dist/form/components/FormikArrayContainer.d.ts +0 -18
  392. package/dist/form/field_bindings/MarkdownFieldBinding.d.ts +0 -9
  393. package/dist/internal/useBuildCustomizationController.d.ts +0 -2
  394. package/dist/internal/useLocaleConfig.d.ts +0 -1
  395. package/dist/types/appcheck.d.ts +0 -26
  396. package/src/components/EntityCollectionTable/internal/popup_field/ElementResizeListener.tsx +0 -59
  397. package/src/components/FireCMSAppBar.tsx +0 -165
  398. package/src/components/PropertyIdCopyTooltipContent.tsx +0 -28
  399. package/src/components/common/useDataSourceEntityCollectionTableController.tsx +0 -225
  400. package/src/core/Drawer.tsx +0 -191
  401. package/src/core/Scaffold.tsx +0 -281
  402. package/src/core/SideEntityView.tsx +0 -38
  403. package/src/form/components/FormikArrayContainer.tsx +0 -44
  404. package/src/form/field_bindings/MarkdownFieldBinding.tsx +0 -695
  405. package/src/internal/useBuildCustomizationController.tsx +0 -5
  406. package/src/internal/useLocaleConfig.tsx +0 -18
  407. package/src/types/appcheck.ts +0 -29
  408. /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
- 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,118 +146,221 @@ 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
- if (viewsOrder) {
257
+ const groupOrderValue = (groupName?: string): number => {
258
+ if (groupName === NAVIGATION_ADMIN_GROUP_NAME) return 1;
259
+ return 0; // Other groups
260
+ };
261
+
262
+ navigationEntries = navigationEntries.sort((a, b) => {
263
+ return groupOrderValue(a.group) - groupOrderValue(b.group);
264
+ });
265
+
266
+ const usedViewsOrder = viewsOrder ?? navigationEntriesOrder;
267
+ if (usedViewsOrder) {
134
268
  navigationEntries = navigationEntries.sort((a, b) => {
135
- const aIndex = viewsOrder.indexOf(a.path);
136
- const bIndex = viewsOrder.indexOf(b.path);
137
- if (aIndex === -1 && bIndex === -1) {
138
- return 0;
139
- }
140
- if (aIndex === -1) {
141
- return 1;
142
- }
143
- if (bIndex === -1) {
144
- return -1;
145
- }
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;
146
275
  return aIndex - bIndex;
147
276
  });
148
277
  }
149
278
 
150
- const groups: string[] = Object.values(navigationEntries)
279
+ const collectedGroupsFromEntries = navigationEntries
151
280
  .map(e => e.group)
152
- .filter(Boolean)
153
- .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));
154
290
 
155
291
  return {
292
+ allowDragAndDrop: plugins?.some(plugin => plugin.homePage?.allowDragAndDrop) ?? false,
156
293
  navigationEntries,
157
- groups
294
+ groups: uniqueGroups,
295
+ onNavigationEntriesUpdate: onNavigationEntriesOrderUpdate,
158
296
  };
159
- }, [buildCMSUrlPath, buildUrlCollectionPath]);
297
+ }, [navigationGroupMappings, buildCMSUrlPath, buildUrlCollectionPath, pluginGroups, onNavigationEntriesOrderUpdate]);
160
298
 
161
299
  const refreshNavigation = useCallback(async () => {
162
300
 
163
- if (authController.initialLoading)
301
+ if (disabled || authController.initialLoading)
164
302
  return;
165
303
 
304
+ console.debug("Refreshing navigation");
305
+
166
306
  try {
167
307
 
168
308
  const [resolvedCollections = [], resolvedViews, resolvedAdminViews = []] = await Promise.all([
169
- resolveCollections(collectionsProp, collectionPermissions, authController, dataSourceDelegate, injectCollections),
309
+ resolveCollections(collectionsProp, collectionPermissions, authController, dataSourceDelegate, plugins),
170
310
  resolveCMSViews(viewsProp, authController, dataSourceDelegate),
171
311
  resolveCMSViews(adminViewsProp, authController, dataSourceDelegate)
172
312
  ]
173
313
  );
174
314
 
175
- if (
176
- !equal(collectionsRef.current, resolvedCollections) ||
177
- !equal(viewsRef.current, resolvedViews) ||
178
- !equal(adminViewsRef.current, resolvedAdminViews) ||
179
- !equal(topLevelNavigation, computeTopNavigation(resolvedCollections, resolvedViews, resolvedAdminViews, viewsOrder))
180
- ) {
315
+ const computedTopLevelNav = computeTopNavigation(resolvedCollections, resolvedViews, resolvedAdminViews, viewsOrder);
316
+
317
+ let shouldUpdateTopLevelNav = false;
318
+ if (!areCollectionListsEqual(collectionsRef.current ?? [], resolvedCollections)) {
181
319
  collectionsRef.current = resolvedCollections;
320
+ console.debug("Collections have changed", resolvedCollections);
321
+ shouldUpdateTopLevelNav = true;
322
+ }
323
+ if (collectionsRef.current === undefined) {
324
+ collectionsRef.current = resolvedCollections;
325
+ shouldUpdateTopLevelNav = true;
326
+ }
327
+ if (!equal(viewsRef.current, resolvedViews)) {
182
328
  viewsRef.current = resolvedViews;
329
+ shouldUpdateTopLevelNav = true;
330
+ }
331
+ if (!equal(adminViewsRef.current, resolvedAdminViews)) {
183
332
  adminViewsRef.current = resolvedAdminViews;
184
- setTopLevelNavigation(computeTopNavigation(resolvedCollections ?? [], resolvedViews, resolvedAdminViews, viewsOrder));
333
+ shouldUpdateTopLevelNav = true;
334
+ }
335
+
336
+ const navigationEntriesOrder = computedTopLevelNav.navigationEntries.map(e => e.id);
337
+ if (!equal(navigationEntriesOrderRef.current, navigationEntriesOrder)) {
338
+ navigationEntriesOrderRef.current = navigationEntriesOrder;
339
+ shouldUpdateTopLevelNav = true;
340
+ }
341
+
342
+ if (shouldUpdateTopLevelNav && !equal(topLevelNavigation, computedTopLevelNav)) {
343
+ setTopLevelNavigation(computedTopLevelNav);
185
344
  }
186
345
  } catch (e) {
187
346
  console.error(e);
188
347
  setNavigationLoadingError(e as any);
189
348
  }
190
349
 
191
- setNavigationLoading(false);
192
- setInitialised(true);
350
+ if (navigationLoading)
351
+ setNavigationLoading(false);
352
+ if (!initialised)
353
+ setInitialised(true);
193
354
 
194
355
  }, [
195
356
  collectionsProp,
196
357
  collectionPermissions,
197
358
  authController.user,
198
359
  authController.initialLoading,
360
+ disabled,
199
361
  viewsProp,
200
362
  adminViewsProp,
201
363
  computeTopNavigation,
202
- injectCollections
203
364
  ]);
204
365
 
205
366
  useEffect(() => {
@@ -208,7 +369,6 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
208
369
 
209
370
  const getCollection = useCallback((
210
371
  idOrPath: string,
211
- entityId?: string,
212
372
  includeUserOverride = false
213
373
  ): EC | undefined => {
214
374
  const collections = collectionsRef.current;
@@ -218,8 +378,7 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
218
378
  const baseCollection = getCollectionByPathOrId(removeInitialAndTrailingSlashes(idOrPath), collections);
219
379
 
220
380
  const userOverride = includeUserOverride ? userConfigPersistence?.getCollectionConfig(idOrPath) : undefined;
221
-
222
- const overriddenCollection = baseCollection ? mergeDeep(baseCollection, userOverride) : undefined;
381
+ const overriddenCollection = baseCollection ? mergeDeep(baseCollection, userOverride ?? {}) : undefined;
223
382
 
224
383
  let result: Partial<EntityCollection> | undefined = overriddenCollection;
225
384
 
@@ -241,12 +400,22 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
241
400
 
242
401
  }, [userConfigPersistence]);
243
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
+
244
413
  const getCollectionFromPaths = useCallback(<EC extends EntityCollection>(pathSegments: string[]): EC | undefined => {
245
414
 
246
415
  const collections = collectionsRef.current;
416
+ if (collections === undefined)
417
+ throw Error("getCollectionFromPaths: Collections have not been initialised yet");
247
418
  let currentCollections: EntityCollection[] | undefined = [...(collections ?? [])];
248
- if (!currentCollections)
249
- throw Error("Collections have not been initialised yet");
250
419
 
251
420
  for (let i = 0; i < pathSegments.length; i++) {
252
421
  const pathSegment = pathSegments[i];
@@ -265,9 +434,9 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
265
434
  const getCollectionFromIds = useCallback(<EC extends EntityCollection>(ids: string[]): EC | undefined => {
266
435
 
267
436
  const collections = collectionsRef.current;
437
+ if (collections === undefined)
438
+ throw Error("getCollectionFromIds: Collections have not been initialised yet");
268
439
  let currentCollections: EntityCollection[] | undefined = [...(collections ?? [])];
269
- if (!currentCollections)
270
- throw Error("Collections have not been initialised yet");
271
440
 
272
441
  for (let i = 0; i < ids.length; i++) {
273
442
  const id = ids[i];
@@ -293,19 +462,8 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
293
462
  throw Error("Expected path starting with " + fullCollectionPath);
294
463
  }, [fullCollectionPath]);
295
464
 
296
- const buildUrlEditCollectionPath = useCallback(({
297
- path
298
- }: {
299
- path: string
300
- }): string => {
301
- return `s/edit/${encodePath(path)}`;
302
- },
303
- []);
304
-
305
- const resolveAliasesFrom = useCallback((path: string): string => {
306
- const collections = collectionsRef.current;
307
- if (!collections)
308
- throw Error("Collections have not been initialised yet");
465
+ const resolveIdsFrom = useCallback((path: string): string => {
466
+ const collections = collectionsRef.current ?? [];
309
467
  return resolveCollectionPathIds(path, collections);
310
468
  }, []);
311
469
 
@@ -359,29 +517,23 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
359
517
  baseCollectionPath,
360
518
  initialised,
361
519
  getCollection,
520
+ getCollectionById,
362
521
  getCollectionFromPaths,
363
522
  getCollectionFromIds,
364
523
  isUrlCollectionPath,
365
524
  urlPathToDataPath,
366
525
  buildUrlCollectionPath,
367
- buildUrlEditCollectionPath,
368
- buildCMSUrlPath,
369
- resolveAliasesFrom,
526
+ resolveIdsFrom,
370
527
  topLevelNavigation,
371
528
  refreshNavigation,
372
529
  getParentReferencesFromPath: getAllParentReferencesForPath,
373
530
  getParentCollectionIds,
374
- convertIdsToPaths
531
+ convertIdsToPaths,
532
+ navigate,
533
+ plugins
375
534
  };
376
535
  }
377
536
 
378
- export function getSidePanelKey(path: string, entityId?: string) {
379
- if (entityId)
380
- return `${removeInitialAndTrailingSlashes(path)}/${removeInitialAndTrailingSlashes(entityId)}`;
381
- else
382
- return removeInitialAndTrailingSlashes(path);
383
- }
384
-
385
537
  function encodePath(input: string) {
386
538
  return encodeURIComponent(removeInitialAndTrailingSlashes(input))
387
539
  .replaceAll("%2F", "/")
@@ -390,9 +542,10 @@ function encodePath(input: string) {
390
542
 
391
543
  function filterOutNotAllowedCollections(resolvedCollections: EntityCollection[], authController: AuthController<User>): EntityCollection[] {
392
544
  return resolvedCollections
545
+ .filter((c) => Boolean(c.path))
393
546
  .filter((c) => {
394
547
  if (!c.permissions) return true;
395
- const resolvedPermissions = resolvePermissions(c, authController, c.path, null)
548
+ const resolvedPermissions = resolvePermissions(c, authController, c.path, null);
396
549
  return resolvedPermissions?.read !== false;
397
550
  })
398
551
  .map((c) => {
@@ -404,11 +557,24 @@ function filterOutNotAllowedCollections(resolvedCollections: EntityCollection[],
404
557
  });
405
558
  }
406
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
+
407
573
  async function resolveCollections(collections: undefined | EntityCollection[] | EntityCollectionsBuilder<any>,
408
574
  collectionPermissions: PermissionsBuilder | undefined,
409
575
  authController: AuthController,
410
576
  dataSource: DataSourceDelegate,
411
- injectCollections?: (collections: EntityCollection[]) => EntityCollection[]) {
577
+ plugins: FireCMSPlugin[] | undefined): Promise<EntityCollection[]> {
412
578
  let resolvedCollections: EntityCollection[] = [];
413
579
  if (typeof collections === "function") {
414
580
  resolvedCollections = await collections({
@@ -420,14 +586,20 @@ async function resolveCollections(collections: undefined | EntityCollection[] |
420
586
  resolvedCollections = collections;
421
587
  }
422
588
 
423
- resolvedCollections = applyPermissionsFunctionIfEmpty(resolvedCollections, collectionPermissions);
424
-
425
- resolvedCollections = filterOutNotAllowedCollections(resolvedCollections, authController);
589
+ if (plugins) {
590
+ for (const plugin of plugins) {
591
+ if (plugin.collection?.modifyCollection) {
592
+ resolvedCollections = applyPluginModifyCollection(resolvedCollections, plugin.collection.modifyCollection);
593
+ }
426
594
 
427
- if (injectCollections) {
428
- resolvedCollections = injectCollections(resolvedCollections ?? []);
595
+ if (plugin.collection?.injectCollections) {
596
+ resolvedCollections = plugin.collection.injectCollections(resolvedCollections ?? []);
597
+ }
598
+ }
429
599
  }
430
600
 
601
+ resolvedCollections = applyPermissionsFunctionIfEmpty(resolvedCollections, collectionPermissions);
602
+ resolvedCollections = filterOutNotAllowedCollections(resolvedCollections, authController);
431
603
  return resolvedCollections;
432
604
  }
433
605
 
@@ -448,7 +620,156 @@ async function resolveCMSViews(baseViews: CMSView[] | CMSViewsBuilder | undefine
448
620
  function getGroup(collectionOrView: EntityCollection<any, any> | CMSView) {
449
621
  const trimmed = collectionOrView.group?.trim();
450
622
  if (!trimmed || trimmed === "") {
451
- return "Views";
623
+ return NAVIGATION_DEFAULT_GROUP_NAME;
624
+ }
625
+ return trimmed ?? NAVIGATION_DEFAULT_GROUP_NAME;
626
+ }
627
+
628
+ function areCollectionListsEqual(a: EntityCollection[], b: EntityCollection[]) {
629
+ if (a.length !== b.length) {
630
+ return false;
452
631
  }
453
- return trimmed ?? "Views";
632
+ const aCopy = [...a];
633
+ const bCopy = [...b];
634
+ const aSorted = aCopy.sort((x, y) => x.id.localeCompare(y.id));
635
+ const bSorted = bCopy.sort((x, y) => x.id.localeCompare(y.id));
636
+ return aSorted.every((value, index) => areCollectionsEqual(value, bSorted[index]));
637
+ }
638
+
639
+ function areCollectionsEqual(a: EntityCollection, b: EntityCollection) {
640
+ const {
641
+ subcollections: subcollectionsA,
642
+ ...restA
643
+ } = a;
644
+ const {
645
+ subcollections: subcollectionsB,
646
+ ...restB
647
+ } = b;
648
+ if (!areCollectionListsEqual(subcollectionsA ?? [], subcollectionsB ?? [])) {
649
+ return false;
650
+ }
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;
454
775
  }