@firecms/core 3.0.0-canary.29 → 3.0.0-canary.292

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 (433) 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 +77 -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/PropertyCollectionView.d.ts +23 -0
  34. package/dist/components/PropertyConfigBadge.d.ts +2 -1
  35. package/dist/components/PropertyIdCopyTooltip.d.ts +8 -0
  36. package/dist/components/ReferenceWidget.d.ts +3 -1
  37. package/dist/components/SelectableTable/SelectableTable.d.ts +14 -4
  38. package/dist/components/SelectableTable/filters/ReferenceFilterField.d.ts +2 -1
  39. package/dist/components/UnsavedChangesDialog.d.ts +8 -0
  40. package/dist/components/UserDisplay.d.ts +7 -0
  41. package/dist/components/VirtualTable/VirtualTableProps.d.ts +24 -12
  42. package/dist/components/VirtualTable/fields/VirtualTableUserSelect.d.ts +12 -0
  43. package/dist/components/VirtualTable/types.d.ts +3 -3
  44. package/dist/components/{EntityCollectionTable/internal → common}/default_entity_actions.d.ts +1 -3
  45. package/dist/components/common/index.d.ts +2 -1
  46. package/dist/components/common/table_height.d.ts +5 -0
  47. package/dist/components/common/types.d.ts +4 -6
  48. package/dist/components/common/useColumnsIds.d.ts +3 -1
  49. package/dist/components/common/{useDataSourceEntityCollectionTableController.d.ts → useDataSourceTableController.d.ts} +13 -2
  50. package/dist/components/common/useDebouncedCallback.d.ts +1 -0
  51. package/dist/components/common/useScrollRestoration.d.ts +14 -0
  52. package/dist/components/index.d.ts +5 -2
  53. package/dist/contexts/BreacrumbsContext.d.ts +8 -0
  54. package/dist/contexts/InternalUserManagementContext.d.ts +3 -0
  55. package/dist/core/DefaultAppBar.d.ts +29 -0
  56. package/dist/core/DefaultDrawer.d.ts +19 -0
  57. package/dist/core/DrawerNavigationItem.d.ts +10 -0
  58. package/dist/core/EntityEditView.d.ts +49 -11
  59. package/dist/core/EntityEditViewFormActions.d.ts +2 -0
  60. package/dist/core/FireCMS.d.ts +2 -3
  61. package/dist/core/FireCMSRouter.d.ts +4 -0
  62. package/dist/core/NavigationRoutes.d.ts +2 -3
  63. package/dist/core/SideDialogs.d.ts +4 -2
  64. package/dist/core/field_configs.d.ts +1 -1
  65. package/dist/core/index.d.ts +4 -4
  66. package/dist/form/EntityForm.d.ts +40 -64
  67. package/dist/form/EntityFormActions.d.ts +21 -0
  68. package/dist/form/PropertyFieldBinding.d.ts +1 -1
  69. package/dist/form/components/ErrorFocus.d.ts +1 -1
  70. package/dist/form/components/FieldHelperText.d.ts +3 -3
  71. package/dist/form/components/FormEntry.d.ts +6 -0
  72. package/dist/form/components/FormLayout.d.ts +5 -0
  73. package/dist/form/components/LabelWithIcon.d.ts +1 -1
  74. package/dist/form/components/LabelWithIconAndTooltip.d.ts +15 -0
  75. package/dist/form/components/LocalChangesMenu.d.ts +11 -0
  76. package/dist/form/components/StorageItemPreview.d.ts +4 -4
  77. package/dist/form/components/index.d.ts +3 -1
  78. package/dist/form/field_bindings/ArrayCustomShapedFieldBinding.d.ts +1 -1
  79. package/dist/form/field_bindings/ArrayOfReferencesFieldBinding.d.ts +1 -1
  80. package/dist/form/field_bindings/BlockFieldBinding.d.ts +1 -1
  81. package/dist/form/field_bindings/KeyValueFieldBinding.d.ts +1 -1
  82. package/dist/form/field_bindings/MapFieldBinding.d.ts +1 -1
  83. package/dist/form/field_bindings/MarkdownEditorFieldBinding.d.ts +11 -0
  84. package/dist/form/field_bindings/{MultiSelectBinding.d.ts → MultiSelectFieldBinding.d.ts} +1 -1
  85. package/dist/form/field_bindings/ReadOnlyFieldBinding.d.ts +1 -1
  86. package/dist/form/field_bindings/ReferenceAsStringFieldBinding.d.ts +9 -0
  87. package/dist/form/field_bindings/ReferenceFieldBinding.d.ts +2 -2
  88. package/dist/form/field_bindings/RepeatFieldBinding.d.ts +1 -1
  89. package/dist/form/field_bindings/SelectFieldBinding.d.ts +1 -1
  90. package/dist/form/field_bindings/StorageUploadFieldBinding.d.ts +5 -13
  91. package/dist/form/field_bindings/SwitchFieldBinding.d.ts +1 -2
  92. package/dist/form/field_bindings/TextFieldBinding.d.ts +1 -1
  93. package/dist/form/field_bindings/UserSelectFieldBinding.d.ts +12 -0
  94. package/dist/form/index.d.ts +18 -18
  95. package/dist/form/useClearRestoreValue.d.ts +2 -2
  96. package/dist/hooks/data/delete.d.ts +4 -4
  97. package/dist/hooks/data/save.d.ts +4 -5
  98. package/dist/hooks/data/useCollectionFetch.d.ts +1 -1
  99. package/dist/hooks/data/useEntityFetch.d.ts +4 -3
  100. package/dist/hooks/index.d.ts +3 -0
  101. package/dist/hooks/useAuthController.d.ts +1 -1
  102. package/dist/hooks/useBreadcrumbsController.d.ts +26 -0
  103. package/dist/hooks/useBuildNavigationController.d.ts +57 -13
  104. package/dist/hooks/useCollapsedGroups.d.ts +9 -0
  105. package/dist/hooks/useFireCMSContext.d.ts +1 -1
  106. package/dist/hooks/useInternalUserManagementController.d.ts +12 -0
  107. package/dist/hooks/useModeController.d.ts +1 -2
  108. package/dist/hooks/useProjectLog.d.ts +8 -2
  109. package/dist/hooks/useResolvedNavigationFrom.d.ts +3 -3
  110. package/dist/hooks/useValidateAuthenticator.d.ts +4 -8
  111. package/dist/index.d.ts +1 -0
  112. package/dist/index.es.js +24546 -13965
  113. package/dist/index.es.js.map +1 -1
  114. package/dist/index.umd.js +27256 -588
  115. package/dist/index.umd.js.map +1 -1
  116. package/dist/internal/useBuildDataSource.d.ts +3 -17
  117. package/dist/internal/useBuildSideEntityController.d.ts +3 -3
  118. package/dist/internal/useUnsavedChangesDialog.d.ts +7 -9
  119. package/dist/preview/PropertyPreviewProps.d.ts +6 -1
  120. package/dist/preview/components/EnumValuesChip.d.ts +1 -1
  121. package/dist/preview/components/ReferencePreview.d.ts +4 -3
  122. package/dist/preview/components/StorageThumbnail.d.ts +2 -1
  123. package/dist/preview/components/UrlComponentPreview.d.ts +2 -1
  124. package/dist/preview/components/UserPreview.d.ts +8 -0
  125. package/dist/preview/index.d.ts +1 -0
  126. package/dist/preview/util.d.ts +3 -3
  127. package/dist/routes/CustomCMSRoute.d.ts +4 -0
  128. package/dist/routes/FireCMSRoute.d.ts +1 -0
  129. package/dist/routes/HomePageRoute.d.ts +3 -0
  130. package/dist/types/analytics.d.ts +1 -1
  131. package/dist/types/auth.d.ts +8 -10
  132. package/dist/types/collections.d.ts +123 -25
  133. package/dist/types/customization_controller.d.ts +8 -0
  134. package/dist/types/datasource.d.ts +52 -36
  135. package/dist/types/dialogs_controller.d.ts +7 -3
  136. package/dist/types/entities.d.ts +12 -3
  137. package/dist/types/entity_actions.d.ts +72 -8
  138. package/dist/types/entity_callbacks.d.ts +16 -16
  139. package/dist/types/entity_overrides.d.ts +2 -2
  140. package/dist/types/export_import.d.ts +4 -4
  141. package/dist/types/fields.d.ts +79 -39
  142. package/dist/types/firecms.d.ts +31 -3
  143. package/dist/types/firecms_context.d.ts +17 -1
  144. package/dist/types/index.d.ts +1 -1
  145. package/dist/types/internal_user_management.d.ts +20 -0
  146. package/dist/types/navigation.d.ts +62 -19
  147. package/dist/types/permissions.d.ts +4 -4
  148. package/dist/types/plugins.d.ts +58 -13
  149. package/dist/types/properties.d.ts +122 -31
  150. package/dist/types/property_config.d.ts +1 -3
  151. package/dist/types/roles.d.ts +3 -0
  152. package/dist/types/side_dialogs_controller.d.ts +10 -0
  153. package/dist/types/side_entity_controller.d.ts +14 -1
  154. package/dist/types/storage.d.ts +75 -0
  155. package/dist/types/user.d.ts +2 -1
  156. package/dist/util/builders.d.ts +3 -3
  157. package/dist/util/callbacks.d.ts +2 -0
  158. package/dist/util/collections.d.ts +1 -0
  159. package/dist/util/createFormexStub.d.ts +2 -0
  160. package/dist/util/entities.d.ts +3 -3
  161. package/dist/util/entity_actions.d.ts +2 -0
  162. package/dist/util/entity_cache.d.ts +28 -0
  163. package/dist/util/icon_list.d.ts +5 -1
  164. package/dist/util/icon_synonyms.d.ts +1 -98
  165. package/dist/util/icons.d.ts +7 -4
  166. package/dist/util/index.d.ts +3 -0
  167. package/dist/util/make_properties_editable.d.ts +1 -2
  168. package/dist/util/navigation_from_path.d.ts +10 -1
  169. package/dist/util/navigation_utils.d.ts +15 -3
  170. package/dist/util/objects.d.ts +3 -1
  171. package/dist/util/permissions.d.ts +4 -4
  172. package/dist/util/plurals.d.ts +0 -2
  173. package/dist/util/property_utils.d.ts +4 -4
  174. package/dist/util/references.d.ts +2 -2
  175. package/dist/util/resolutions.d.ts +42 -17
  176. package/dist/util/storage.d.ts +23 -2
  177. package/dist/util/useStorageUploadController.d.ts +4 -3
  178. package/package.json +70 -53
  179. package/src/app/AppBar.tsx +18 -0
  180. package/src/app/Drawer.tsx +24 -0
  181. package/src/app/Scaffold.tsx +253 -0
  182. package/src/app/index.ts +4 -0
  183. package/src/app/useApp.tsx +32 -0
  184. package/src/components/ArrayContainer.tsx +447 -229
  185. package/src/components/CircularProgressCenter.tsx +2 -2
  186. package/src/components/ClearFilterSortButton.tsx +41 -0
  187. package/src/components/{DeleteConfirmationDialog.tsx → ConfirmationDialog.tsx} +12 -11
  188. package/src/components/DeleteEntityDialog.tsx +13 -20
  189. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +87 -62
  190. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +38 -31
  191. package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +30 -9
  192. package/src/components/EntityCollectionTable/PropertyTableCell.tsx +84 -42
  193. package/src/components/EntityCollectionTable/column_utils.tsx +3 -3
  194. package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +30 -16
  195. package/src/components/EntityCollectionTable/fields/TableStorageUpload.tsx +19 -17
  196. package/src/components/EntityCollectionTable/index.tsx +1 -1
  197. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +34 -39
  198. package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +49 -36
  199. package/src/components/EntityCollectionTable/internal/EntityTableCellActions.tsx +20 -8
  200. package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +135 -105
  201. package/src/components/EntityCollectionTable/internal/popup_field/useDraggable.tsx +9 -9
  202. package/src/components/EntityCollectionView/EntityCollectionView.tsx +241 -119
  203. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +7 -4
  204. package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +68 -0
  205. package/src/components/EntityCollectionView/useSelectionController.tsx +20 -7
  206. package/src/components/EntityCollectionView/utils.ts +19 -0
  207. package/src/components/EntityJsonPreview.tsx +66 -0
  208. package/src/components/EntityPreview.tsx +83 -62
  209. package/src/components/EntityView.tsx +34 -42
  210. package/src/components/ErrorView.tsx +4 -4
  211. package/src/components/FireCMSLogo.tsx +7 -51
  212. package/src/components/HomePage/DefaultHomePage.tsx +516 -158
  213. package/src/components/HomePage/FavouritesView.tsx +9 -14
  214. package/src/components/HomePage/HomePageDnD.tsx +702 -0
  215. package/src/components/HomePage/NavigationCard.tsx +48 -39
  216. package/src/components/HomePage/NavigationCardBinding.tsx +17 -16
  217. package/src/components/HomePage/NavigationGroup.tsx +144 -30
  218. package/src/components/HomePage/RenameGroupDialog.tsx +123 -0
  219. package/src/components/HomePage/SmallNavigationCard.tsx +5 -6
  220. package/src/components/NotFoundPage.tsx +2 -2
  221. package/src/components/PropertyCollectionView.tsx +329 -0
  222. package/src/components/PropertyConfigBadge.tsx +10 -4
  223. package/src/components/PropertyIdCopyTooltip.tsx +47 -0
  224. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +23 -13
  225. package/src/components/ReferenceWidget.tsx +21 -11
  226. package/src/components/SearchIconsView.tsx +10 -7
  227. package/src/components/SelectableTable/SelectableTable.tsx +157 -157
  228. package/src/components/SelectableTable/filters/BooleanFilterField.tsx +2 -3
  229. package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +27 -9
  230. package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +36 -12
  231. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +92 -24
  232. package/src/components/UnsavedChangesDialog.tsx +46 -0
  233. package/src/components/UserDisplay.tsx +55 -0
  234. package/src/components/VirtualTable/VirtualTable.tsx +105 -51
  235. package/src/components/VirtualTable/VirtualTableCell.tsx +1 -9
  236. package/src/components/VirtualTable/VirtualTableHeader.tsx +10 -10
  237. package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +2 -2
  238. package/src/components/VirtualTable/VirtualTableProps.tsx +28 -14
  239. package/src/components/VirtualTable/VirtualTableRow.tsx +5 -6
  240. package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +5 -5
  241. package/src/components/VirtualTable/fields/VirtualTableInput.tsx +2 -2
  242. package/src/components/VirtualTable/fields/VirtualTableNumberInput.tsx +2 -1
  243. package/src/components/VirtualTable/fields/VirtualTableSelect.tsx +16 -28
  244. package/src/components/VirtualTable/fields/VirtualTableUserSelect.tsx +99 -0
  245. package/src/components/VirtualTable/types.tsx +2 -3
  246. package/src/components/{EntityCollectionTable/internal → common}/default_entity_actions.tsx +64 -44
  247. package/src/components/common/index.ts +2 -1
  248. package/src/components/{VirtualTable/common.tsx → common/table_height.tsx} +5 -2
  249. package/src/components/common/types.tsx +4 -6
  250. package/src/components/common/useColumnsIds.tsx +16 -2
  251. package/src/components/common/useDataSourceTableController.tsx +420 -0
  252. package/src/components/common/useDebouncedCallback.tsx +20 -0
  253. package/src/components/common/useScrollRestoration.tsx +68 -0
  254. package/src/components/common/useTableSearchHelper.ts +53 -12
  255. package/src/components/index.tsx +6 -2
  256. package/src/contexts/BreacrumbsContext.tsx +38 -0
  257. package/src/contexts/DialogsProvider.tsx +5 -4
  258. package/src/contexts/InternalUserManagementContext.tsx +4 -0
  259. package/src/contexts/ModeController.tsx +1 -3
  260. package/src/contexts/SnackbarProvider.tsx +2 -0
  261. package/src/core/DefaultAppBar.tsx +219 -0
  262. package/src/core/DefaultDrawer.tsx +185 -0
  263. package/src/core/DrawerNavigationItem.tsx +66 -0
  264. package/src/core/EntityEditView.tsx +447 -469
  265. package/src/core/EntityEditViewFormActions.tsx +344 -0
  266. package/src/core/EntitySidePanel.tsx +96 -23
  267. package/src/core/FireCMS.tsx +85 -60
  268. package/src/core/FireCMSRouter.tsx +17 -0
  269. package/src/core/NavigationRoutes.tsx +28 -38
  270. package/src/core/SideDialogs.tsx +22 -12
  271. package/src/core/field_configs.tsx +41 -14
  272. package/src/core/index.tsx +6 -5
  273. package/src/form/EntityForm.tsx +740 -523
  274. package/src/form/EntityFormActions.tsx +226 -0
  275. package/src/form/PropertyFieldBinding.tsx +88 -41
  276. package/src/form/components/CustomIdField.tsx +9 -3
  277. package/src/form/components/ErrorFocus.tsx +22 -29
  278. package/src/form/components/FieldHelperText.tsx +4 -4
  279. package/src/form/components/FormEntry.tsx +22 -0
  280. package/src/form/components/FormLayout.tsx +16 -0
  281. package/src/form/components/LabelWithIcon.tsx +30 -19
  282. package/src/form/components/LabelWithIconAndTooltip.tsx +28 -0
  283. package/src/form/components/LocalChangesMenu.tsx +144 -0
  284. package/src/form/components/StorageItemPreview.tsx +23 -13
  285. package/src/form/components/StorageUploadProgress.tsx +5 -6
  286. package/src/form/components/index.tsx +3 -1
  287. package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +34 -19
  288. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +50 -36
  289. package/src/form/field_bindings/BlockFieldBinding.tsx +56 -33
  290. package/src/form/field_bindings/DateTimeFieldBinding.tsx +18 -14
  291. package/src/form/field_bindings/KeyValueFieldBinding.tsx +61 -52
  292. package/src/form/field_bindings/MapFieldBinding.tsx +73 -55
  293. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +159 -0
  294. package/src/form/field_bindings/{MultiSelectBinding.tsx → MultiSelectFieldBinding.tsx} +26 -21
  295. package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +11 -16
  296. package/src/form/field_bindings/ReferenceAsStringFieldBinding.tsx +135 -0
  297. package/src/form/field_bindings/ReferenceFieldBinding.tsx +42 -31
  298. package/src/form/field_bindings/RepeatFieldBinding.tsx +62 -35
  299. package/src/form/field_bindings/SelectFieldBinding.tsx +24 -15
  300. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +257 -199
  301. package/src/form/field_bindings/SwitchFieldBinding.tsx +29 -24
  302. package/src/form/field_bindings/TextFieldBinding.tsx +28 -24
  303. package/src/form/field_bindings/UserSelectFieldBinding.tsx +94 -0
  304. package/src/form/index.tsx +21 -37
  305. package/src/form/useClearRestoreValue.tsx +2 -2
  306. package/src/form/validation.ts +13 -23
  307. package/src/hooks/data/delete.ts +6 -5
  308. package/src/hooks/data/save.ts +26 -33
  309. package/src/hooks/data/useCollectionFetch.tsx +3 -3
  310. package/src/hooks/data/useDataSource.tsx +11 -3
  311. package/src/hooks/data/useEntityFetch.tsx +10 -6
  312. package/src/hooks/index.tsx +4 -0
  313. package/src/hooks/useAuthController.tsx +1 -1
  314. package/src/hooks/useBreadcrumbsController.tsx +31 -0
  315. package/src/hooks/useBrowserTitleAndIcon.tsx +1 -1
  316. package/src/hooks/useBuildLocalConfigurationPersistence.tsx +8 -10
  317. package/src/hooks/useBuildModeController.tsx +22 -29
  318. package/src/hooks/useBuildNavigationController.tsx +515 -121
  319. package/src/hooks/useCollapsedGroups.ts +64 -0
  320. package/src/hooks/useFireCMSContext.tsx +9 -35
  321. package/src/hooks/useInternalUserManagementController.tsx +16 -0
  322. package/src/hooks/useLargeLayout.tsx +0 -35
  323. package/src/hooks/useModeController.tsx +1 -2
  324. package/src/hooks/useProjectLog.tsx +32 -10
  325. package/src/hooks/useResolvedNavigationFrom.tsx +10 -12
  326. package/src/hooks/useValidateAuthenticator.tsx +17 -37
  327. package/src/index.ts +1 -0
  328. package/src/internal/useBuildDataSource.ts +79 -85
  329. package/src/internal/useBuildSideDialogsController.tsx +4 -2
  330. package/src/internal/useBuildSideEntityController.tsx +204 -77
  331. package/src/internal/useUnsavedChangesDialog.tsx +127 -91
  332. package/src/preview/PropertyPreview.tsx +42 -25
  333. package/src/preview/PropertyPreviewProps.tsx +7 -1
  334. package/src/preview/components/BooleanPreview.tsx +2 -2
  335. package/src/preview/components/EmptyValue.tsx +1 -1
  336. package/src/preview/components/EnumValuesChip.tsx +2 -2
  337. package/src/preview/components/ImagePreview.tsx +26 -37
  338. package/src/preview/components/ReferencePreview.tsx +30 -38
  339. package/src/preview/components/StorageThumbnail.tsx +5 -1
  340. package/src/preview/components/UrlComponentPreview.tsx +60 -28
  341. package/src/preview/components/UserPreview.tsx +27 -0
  342. package/src/preview/index.ts +1 -0
  343. package/src/preview/property_previews/ArrayOfMapsPreview.tsx +6 -6
  344. package/src/preview/property_previews/ArrayOfReferencesPreview.tsx +7 -5
  345. package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +5 -4
  346. package/src/preview/property_previews/ArrayOfStringsPreview.tsx +4 -4
  347. package/src/preview/property_previews/ArrayOneOfPreview.tsx +7 -6
  348. package/src/preview/property_previews/ArrayPropertyPreview.tsx +8 -7
  349. package/src/preview/property_previews/MapPropertyPreview.tsx +14 -13
  350. package/src/preview/property_previews/NumberPropertyPreview.tsx +2 -2
  351. package/src/preview/property_previews/SkeletonPropertyComponent.tsx +13 -13
  352. package/src/preview/property_previews/StringPropertyPreview.tsx +3 -3
  353. package/src/preview/util.ts +10 -10
  354. package/src/routes/CustomCMSRoute.tsx +21 -0
  355. package/src/routes/FireCMSRoute.tsx +246 -0
  356. package/src/routes/HomePageRoute.tsx +17 -0
  357. package/src/types/analytics.ts +3 -0
  358. package/src/types/auth.tsx +9 -13
  359. package/src/types/collections.ts +146 -30
  360. package/src/types/customization_controller.tsx +9 -1
  361. package/src/types/datasource.ts +61 -43
  362. package/src/types/dialogs_controller.tsx +7 -3
  363. package/src/types/entities.ts +19 -3
  364. package/src/types/entity_actions.tsx +86 -10
  365. package/src/types/entity_callbacks.ts +18 -18
  366. package/src/types/entity_overrides.tsx +2 -2
  367. package/src/types/export_import.ts +4 -4
  368. package/src/types/fields.tsx +91 -42
  369. package/src/types/firecms.tsx +34 -4
  370. package/src/types/firecms_context.tsx +18 -1
  371. package/src/types/index.ts +1 -1
  372. package/src/types/internal_user_management.ts +24 -0
  373. package/src/types/navigation.ts +77 -24
  374. package/src/types/permissions.ts +5 -5
  375. package/src/types/plugins.tsx +69 -15
  376. package/src/types/properties.ts +141 -33
  377. package/src/types/property_config.tsx +2 -2
  378. package/src/types/roles.ts +3 -0
  379. package/src/types/side_dialogs_controller.tsx +15 -0
  380. package/src/types/side_entity_controller.tsx +16 -1
  381. package/src/types/storage.ts +83 -1
  382. package/src/types/user.ts +3 -1
  383. package/src/util/builders.ts +10 -8
  384. package/src/util/callbacks.ts +119 -0
  385. package/src/util/collections.ts +8 -0
  386. package/src/util/createFormexStub.tsx +66 -0
  387. package/src/util/entities.ts +11 -8
  388. package/src/util/entity_actions.ts +28 -0
  389. package/src/util/entity_cache.ts +223 -0
  390. package/src/util/enums.ts +1 -1
  391. package/src/util/icon_list.ts +16 -10
  392. package/src/util/icon_synonyms.ts +3 -100
  393. package/src/util/icons.tsx +36 -11
  394. package/src/util/index.ts +3 -0
  395. package/src/util/join_collections.ts +11 -4
  396. package/src/util/make_properties_editable.ts +5 -19
  397. package/src/util/navigation_from_path.ts +33 -12
  398. package/src/util/navigation_utils.ts +141 -25
  399. package/src/util/objects.ts +128 -33
  400. package/src/util/parent_references_from_path.ts +3 -3
  401. package/src/util/permissions.ts +9 -8
  402. package/src/util/plurals.ts +0 -2
  403. package/src/util/property_utils.tsx +17 -6
  404. package/src/util/references.ts +19 -8
  405. package/src/util/resolutions.ts +122 -48
  406. package/src/util/storage.ts +79 -21
  407. package/src/util/strings.ts +2 -2
  408. package/src/util/useStorageUploadController.tsx +162 -62
  409. package/dist/components/EntityCollectionTable/internal/popup_field/ElementResizeListener.d.ts +0 -5
  410. package/dist/components/FireCMSAppBar.d.ts +0 -26
  411. package/dist/components/PropertyIdCopyTooltipContent.d.ts +0 -3
  412. package/dist/components/VirtualTable/common.d.ts +0 -2
  413. package/dist/core/Drawer.d.ts +0 -23
  414. package/dist/core/Scaffold.d.ts +0 -55
  415. package/dist/core/SideEntityView.d.ts +0 -7
  416. package/dist/form/components/FormikArrayContainer.d.ts +0 -18
  417. package/dist/form/field_bindings/MarkdownFieldBinding.d.ts +0 -9
  418. package/dist/internal/useBuildCustomizationController.d.ts +0 -2
  419. package/dist/internal/useLocaleConfig.d.ts +0 -1
  420. package/dist/types/appcheck.d.ts +0 -26
  421. package/src/components/EntityCollectionTable/internal/popup_field/ElementResizeListener.tsx +0 -59
  422. package/src/components/FireCMSAppBar.tsx +0 -165
  423. package/src/components/PropertyIdCopyTooltipContent.tsx +0 -28
  424. package/src/components/common/useDataSourceEntityCollectionTableController.tsx +0 -225
  425. package/src/core/Drawer.tsx +0 -191
  426. package/src/core/Scaffold.tsx +0 -281
  427. package/src/core/SideEntityView.tsx +0 -38
  428. package/src/form/components/FormikArrayContainer.tsx +0 -44
  429. package/src/form/field_bindings/MarkdownFieldBinding.tsx +0 -695
  430. package/src/internal/useBuildCustomizationController.tsx +0 -5
  431. package/src/internal/useLocaleConfig.tsx +0 -18
  432. package/src/types/appcheck.ts +0 -29
  433. /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
96
+ */
97
+ disabled?: boolean;
98
+
99
+ /**
100
+ * @deprecated
101
+ * Use `navigationGroupMappings` instead.
49
102
  */
50
- injectCollections?: (collections: EntityCollection[]) => EntityCollection[];
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,246 @@ 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 computeTopNavigation = useCallback((collections: EntityCollection[], views: CMSView[], adminViews: CMSView[], viewsOrder?: string[], navigationGroupMappingsOverride?: NavigationGroupMapping[], onNavigationEntriesUpdateCallback?: (entries: NavigationGroupMapping[]) => void): NavigationResult => {
153
+
154
+ const finalNavigationGroupMappings: NavigationGroupMapping[] = computeNavigationGroups({
155
+ navigationGroupMappings: navigationGroupMappingsOverride ?? navigationGroupMappings,
156
+ collections,
157
+ views,
158
+ plugins: plugins
159
+ });
160
+
161
+ const allPluginNavigationEntries = finalNavigationGroupMappings.map((g) => g.entries).flat() ?? [];
162
+ const navigationEntriesOrder = ([...new Set(allPluginNavigationEntries)]);
163
+
164
+ let navigationEntries: NavigationEntry[] = [
165
+ ...(collections ?? []).reduce((acc, collection) => {
166
+ if (collection.hideFromNavigation) return acc;
167
+
168
+ const pathKey = collection.id ?? collection.path;
169
+ let groupName = getGroup(collection); // Initial group
170
+
171
+ if (finalNavigationGroupMappings) {
172
+ for (const pluginGroupDef of finalNavigationGroupMappings) {
173
+ if (pluginGroupDef.entries.includes(pathKey)) {
174
+ groupName = pluginGroupDef.name;
175
+ break;
176
+ }
177
+ }
178
+ }
179
+
180
+ acc.push({
181
+ id: `collection:${pathKey}`,
182
+ url: buildUrlCollectionPath(pathKey),
96
183
  type: "collection",
97
184
  name: collection.name.trim(),
98
- path: collection.id ?? collection.path,
185
+ path: pathKey,
99
186
  collection,
100
187
  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[]
188
+ group: groupName ?? NAVIGATION_DEFAULT_GROUP_NAME
189
+ });
190
+ return acc;
191
+ }, [] as NavigationEntry[]),
192
+
193
+ ...(views ?? []).reduce((acc, view) => {
194
+ if (view.hideFromNavigation) return acc;
195
+
196
+ const pathKey = view.path;
197
+ let groupName = getGroup(view); // Initial group
198
+
199
+ if (finalNavigationGroupMappings) {
200
+ for (const pluginGroupDef of finalNavigationGroupMappings) {
201
+ if (pluginGroupDef.entries.includes(pathKey)) {
202
+ groupName = pluginGroupDef.name;
203
+ break;
204
+ }
205
+ }
206
+ }
207
+
208
+ acc.push({
209
+ id: `view:${pathKey}`,
210
+ url: buildCMSUrlPath(pathKey),
211
+ name: view.name.trim(),
212
+ type: "view",
213
+ path: view.path,
214
+ view,
215
+ description: view.description?.trim(),
216
+ group: groupName ?? NAVIGATION_DEFAULT_GROUP_NAME
217
+ });
218
+ return acc;
219
+ }, [] as NavigationEntry[]),
220
+
221
+ ...(adminViews ?? []).reduce((acc, view) => {
222
+ if (view.hideFromNavigation) return acc;
223
+
224
+ const pathKey = view.path;
225
+ const groupName = NAVIGATION_ADMIN_GROUP_NAME;
226
+
227
+ acc.push({
228
+ id: `admin:${pathKey}`,
229
+ url: buildCMSUrlPath(pathKey),
230
+ name: view.name.trim(),
231
+ type: "admin",
232
+ path: view.path,
233
+ view,
234
+ description: view.description?.trim(),
235
+ group: groupName
236
+ });
237
+ return acc;
238
+ }, [] as NavigationEntry[])
131
239
  ];
132
240
 
133
- if (viewsOrder) {
241
+ const groupOrderValue = (groupName?: string): number => {
242
+ if (groupName === NAVIGATION_ADMIN_GROUP_NAME) return 1;
243
+ return 0; // Other groups
244
+ };
245
+
246
+ navigationEntries = navigationEntries.sort((a, b) => {
247
+ return groupOrderValue(a.group) - groupOrderValue(b.group);
248
+ });
249
+
250
+ const usedViewsOrder = viewsOrder ?? navigationEntriesOrder;
251
+ if (usedViewsOrder) {
134
252
  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
- }
253
+ const getSortPath = (navEntry: NavigationEntry) => typeof navEntry.path === "string" ? navEntry.path : navEntry.path[0];
254
+ const aIndex = usedViewsOrder.indexOf(getSortPath(a));
255
+ const bIndex = usedViewsOrder.indexOf(getSortPath(b));
256
+ if (aIndex === -1 && bIndex === -1) return 0;
257
+ if (aIndex === -1) return 1;
258
+ if (bIndex === -1) return -1;
146
259
  return aIndex - bIndex;
147
260
  });
148
261
  }
149
262
 
150
- const groups: string[] = Object.values(navigationEntries)
263
+ const collectedGroupsFromEntries = navigationEntries
151
264
  .map(e => e.group)
152
- .filter(Boolean)
153
- .filter((value, index, array) => array.indexOf(value) === index) as string[];
265
+ .filter(Boolean) as string[];
266
+
267
+ // Preserve order from finalNavigationGroupMappings (persisted order)
268
+ const groupsFromMappings = finalNavigationGroupMappings.map(g => g.name);
269
+
270
+ // Add any additional groups not in mappings
271
+ const additionalGroups = collectedGroupsFromEntries.filter(g => !groupsFromMappings.includes(g));
272
+
273
+ const allDefinedGroups = [
274
+ ...(pluginGroups ?? []),
275
+ ...groupsFromMappings,
276
+ ...additionalGroups
277
+ ];
278
+
279
+ // Remove duplicates while preserving order, then separate admin to the end
280
+ const uniqueGroupsArray = [...new Set(allDefinedGroups)];
281
+ const adminGroups = uniqueGroupsArray.filter(g => g === NAVIGATION_ADMIN_GROUP_NAME);
282
+ const nonAdminGroups = uniqueGroupsArray.filter(g => g !== NAVIGATION_ADMIN_GROUP_NAME);
283
+ const uniqueGroups = [...nonAdminGroups, ...adminGroups];
154
284
 
155
285
  return {
286
+ allowDragAndDrop: plugins?.some(plugin => plugin.homePage?.allowDragAndDrop) ?? false,
156
287
  navigationEntries,
157
- groups
288
+ groups: uniqueGroups,
289
+ onNavigationEntriesUpdate: onNavigationEntriesUpdateCallback!,
158
290
  };
159
- }, [buildCMSUrlPath, buildUrlCollectionPath]);
291
+ }, [navigationGroupMappings, buildCMSUrlPath, buildUrlCollectionPath, pluginGroups]);
292
+
293
+ const onNavigationEntriesOrderUpdate = useCallback((entries: NavigationGroupMapping[]) => {
294
+ if (!plugins) {
295
+ return;
296
+ }
297
+ // remove all groups that have no entries
298
+ const filteredEntries = entries.filter(entry => entry.entries.length > 0);
299
+
300
+ // Immediately update the local topLevelNavigation with new mappings
301
+ if (collectionsRef.current && viewsRef.current) {
302
+ const updatedNav = computeTopNavigation(
303
+ collectionsRef.current,
304
+ viewsRef.current,
305
+ adminViewsRef.current ?? [],
306
+ viewsOrder,
307
+ filteredEntries,
308
+ onNavigationEntriesOrderUpdate
309
+ );
310
+ setTopLevelNavigation(updatedNav);
311
+ }
312
+
313
+ // Then persist to backend
314
+ if (plugins.some(plugin => plugin.homePage?.onNavigationEntriesUpdate)) {
315
+ plugins.forEach(plugin => {
316
+ if (plugin.homePage?.onNavigationEntriesUpdate) {
317
+ plugin.homePage.onNavigationEntriesUpdate(filteredEntries);
318
+ }
319
+ });
320
+ }
321
+
322
+ }, [plugins, computeTopNavigation, viewsOrder]);
160
323
 
161
324
  const refreshNavigation = useCallback(async () => {
162
325
 
163
- if (authController.initialLoading)
326
+ if (disabled || authController.initialLoading)
164
327
  return;
165
328
 
329
+ console.debug("Refreshing navigation");
330
+
166
331
  try {
167
332
 
168
333
  const [resolvedCollections = [], resolvedViews, resolvedAdminViews = []] = await Promise.all([
169
- resolveCollections(collectionsProp, collectionPermissions, authController, dataSourceDelegate, injectCollections),
334
+ resolveCollections(collectionsProp, collectionPermissions, authController, dataSourceDelegate, plugins),
170
335
  resolveCMSViews(viewsProp, authController, dataSourceDelegate),
171
336
  resolveCMSViews(adminViewsProp, authController, dataSourceDelegate)
172
337
  ]
173
338
  );
174
339
 
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
- ) {
340
+ const computedTopLevelNav = computeTopNavigation(resolvedCollections, resolvedViews, resolvedAdminViews, viewsOrder, undefined, onNavigationEntriesOrderUpdate);
341
+
342
+ let shouldUpdateTopLevelNav = false;
343
+ if (!areCollectionListsEqual(collectionsRef.current ?? [], resolvedCollections)) {
344
+ collectionsRef.current = resolvedCollections;
345
+ console.debug("Collections have changed", resolvedCollections);
346
+ shouldUpdateTopLevelNav = true;
347
+ }
348
+ if (collectionsRef.current === undefined) {
181
349
  collectionsRef.current = resolvedCollections;
350
+ shouldUpdateTopLevelNav = true;
351
+ }
352
+ if (!equal(viewsRef.current, resolvedViews)) {
182
353
  viewsRef.current = resolvedViews;
354
+ shouldUpdateTopLevelNav = true;
355
+ }
356
+ if (!equal(adminViewsRef.current, resolvedAdminViews)) {
183
357
  adminViewsRef.current = resolvedAdminViews;
184
- setTopLevelNavigation(computeTopNavigation(resolvedCollections ?? [], resolvedViews, resolvedAdminViews, viewsOrder));
358
+ shouldUpdateTopLevelNav = true;
359
+ }
360
+
361
+ const navigationEntriesOrder = computedTopLevelNav.navigationEntries.map(e => e.id);
362
+ if (!equal(navigationEntriesOrderRef.current, navigationEntriesOrder)) {
363
+ navigationEntriesOrderRef.current = navigationEntriesOrder;
364
+ shouldUpdateTopLevelNav = true;
365
+ }
366
+
367
+ if (shouldUpdateTopLevelNav && !equal(topLevelNavigation, computedTopLevelNav)) {
368
+ setTopLevelNavigation(computedTopLevelNav);
185
369
  }
186
370
  } catch (e) {
187
371
  console.error(e);
188
372
  setNavigationLoadingError(e as any);
189
373
  }
190
374
 
191
- setNavigationLoading(false);
192
- setInitialised(true);
375
+ if (navigationLoading)
376
+ setNavigationLoading(false);
377
+ if (!initialised)
378
+ setInitialised(true);
193
379
 
194
380
  }, [
195
381
  collectionsProp,
196
382
  collectionPermissions,
197
383
  authController.user,
198
384
  authController.initialLoading,
385
+ disabled,
199
386
  viewsProp,
200
387
  adminViewsProp,
201
388
  computeTopNavigation,
202
- injectCollections
203
389
  ]);
204
390
 
205
391
  useEffect(() => {
@@ -208,7 +394,6 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
208
394
 
209
395
  const getCollection = useCallback((
210
396
  idOrPath: string,
211
- entityId?: string,
212
397
  includeUserOverride = false
213
398
  ): EC | undefined => {
214
399
  const collections = collectionsRef.current;
@@ -218,8 +403,7 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
218
403
  const baseCollection = getCollectionByPathOrId(removeInitialAndTrailingSlashes(idOrPath), collections);
219
404
 
220
405
  const userOverride = includeUserOverride ? userConfigPersistence?.getCollectionConfig(idOrPath) : undefined;
221
-
222
- const overriddenCollection = baseCollection ? mergeDeep(baseCollection, userOverride) : undefined;
406
+ const overriddenCollection = baseCollection ? mergeDeep(baseCollection, userOverride ?? {}) : undefined;
223
407
 
224
408
  let result: Partial<EntityCollection> | undefined = overriddenCollection;
225
409
 
@@ -241,12 +425,22 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
241
425
 
242
426
  }, [userConfigPersistence]);
243
427
 
428
+ const getCollectionById = useCallback((id: string): EC | undefined => {
429
+ const collections = collectionsRef.current;
430
+ if (collections === undefined)
431
+ throw Error("getCollectionById: Collections have not been initialised yet");
432
+ const collection: EntityCollection | undefined = collections.find(c => c.id === id);
433
+ if (!collection)
434
+ return undefined;
435
+ return collection as EC;
436
+ }, []);
437
+
244
438
  const getCollectionFromPaths = useCallback(<EC extends EntityCollection>(pathSegments: string[]): EC | undefined => {
245
439
 
246
440
  const collections = collectionsRef.current;
441
+ if (collections === undefined)
442
+ throw Error("getCollectionFromPaths: Collections have not been initialised yet");
247
443
  let currentCollections: EntityCollection[] | undefined = [...(collections ?? [])];
248
- if (!currentCollections)
249
- throw Error("Collections have not been initialised yet");
250
444
 
251
445
  for (let i = 0; i < pathSegments.length; i++) {
252
446
  const pathSegment = pathSegments[i];
@@ -265,9 +459,9 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
265
459
  const getCollectionFromIds = useCallback(<EC extends EntityCollection>(ids: string[]): EC | undefined => {
266
460
 
267
461
  const collections = collectionsRef.current;
462
+ if (collections === undefined)
463
+ throw Error("getCollectionFromIds: Collections have not been initialised yet");
268
464
  let currentCollections: EntityCollection[] | undefined = [...(collections ?? [])];
269
- if (!currentCollections)
270
- throw Error("Collections have not been initialised yet");
271
465
 
272
466
  for (let i = 0; i < ids.length; i++) {
273
467
  const id = ids[i];
@@ -288,24 +482,14 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
288
482
  [fullCollectionPath]);
289
483
 
290
484
  const urlPathToDataPath = useCallback((path: string): string => {
291
- if (path.startsWith(fullCollectionPath))
292
- return path.replace(fullCollectionPath, "");
485
+ const decodedPath = decodeURIComponent(path);
486
+ if (decodedPath.startsWith(fullCollectionPath))
487
+ return decodedPath.replace(fullCollectionPath, "");
293
488
  throw Error("Expected path starting with " + fullCollectionPath);
294
489
  }, [fullCollectionPath]);
295
490
 
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");
491
+ const resolveIdsFrom = useCallback((path: string): string => {
492
+ const collections = collectionsRef.current ?? [];
309
493
  return resolveCollectionPathIds(path, collections);
310
494
  }, []);
311
495
 
@@ -359,29 +543,23 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
359
543
  baseCollectionPath,
360
544
  initialised,
361
545
  getCollection,
546
+ getCollectionById,
362
547
  getCollectionFromPaths,
363
548
  getCollectionFromIds,
364
549
  isUrlCollectionPath,
365
550
  urlPathToDataPath,
366
551
  buildUrlCollectionPath,
367
- buildUrlEditCollectionPath,
368
- buildCMSUrlPath,
369
- resolveAliasesFrom,
552
+ resolveIdsFrom,
370
553
  topLevelNavigation,
371
554
  refreshNavigation,
372
555
  getParentReferencesFromPath: getAllParentReferencesForPath,
373
556
  getParentCollectionIds,
374
- convertIdsToPaths
557
+ convertIdsToPaths,
558
+ navigate,
559
+ plugins
375
560
  };
376
561
  }
377
562
 
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
563
  function encodePath(input: string) {
386
564
  return encodeURIComponent(removeInitialAndTrailingSlashes(input))
387
565
  .replaceAll("%2F", "/")
@@ -390,9 +568,10 @@ function encodePath(input: string) {
390
568
 
391
569
  function filterOutNotAllowedCollections(resolvedCollections: EntityCollection[], authController: AuthController<User>): EntityCollection[] {
392
570
  return resolvedCollections
571
+ .filter((c) => Boolean(c.path))
393
572
  .filter((c) => {
394
573
  if (!c.permissions) return true;
395
- const resolvedPermissions = resolvePermissions(c, authController, c.path, null)
574
+ const resolvedPermissions = resolvePermissions(c, authController, c.path, null);
396
575
  return resolvedPermissions?.read !== false;
397
576
  })
398
577
  .map((c) => {
@@ -404,11 +583,24 @@ function filterOutNotAllowedCollections(resolvedCollections: EntityCollection[],
404
583
  });
405
584
  }
406
585
 
586
+ function applyPluginModifyCollection(resolvedCollections: EntityCollection[], modifyCollection: (collection: EntityCollection) => EntityCollection) {
587
+ return resolvedCollections.map((collection: EntityCollection): EntityCollection => {
588
+ const modifiedCollection = modifyCollection(collection);
589
+ if (modifiedCollection.subcollections) {
590
+ return {
591
+ ...modifiedCollection,
592
+ subcollections: applyPluginModifyCollection(modifiedCollection.subcollections, modifyCollection)
593
+ } satisfies EntityCollection;
594
+ }
595
+ return modifiedCollection;
596
+ });
597
+ }
598
+
407
599
  async function resolveCollections(collections: undefined | EntityCollection[] | EntityCollectionsBuilder<any>,
408
600
  collectionPermissions: PermissionsBuilder | undefined,
409
601
  authController: AuthController,
410
602
  dataSource: DataSourceDelegate,
411
- injectCollections?: (collections: EntityCollection[]) => EntityCollection[]) {
603
+ plugins: FireCMSPlugin[] | undefined): Promise<EntityCollection[]> {
412
604
  let resolvedCollections: EntityCollection[] = [];
413
605
  if (typeof collections === "function") {
414
606
  resolvedCollections = await collections({
@@ -420,14 +612,20 @@ async function resolveCollections(collections: undefined | EntityCollection[] |
420
612
  resolvedCollections = collections;
421
613
  }
422
614
 
423
- resolvedCollections = applyPermissionsFunctionIfEmpty(resolvedCollections, collectionPermissions);
424
-
425
- resolvedCollections = filterOutNotAllowedCollections(resolvedCollections, authController);
615
+ if (plugins) {
616
+ for (const plugin of plugins) {
617
+ if (plugin.collection?.modifyCollection) {
618
+ resolvedCollections = applyPluginModifyCollection(resolvedCollections, plugin.collection.modifyCollection);
619
+ }
426
620
 
427
- if (injectCollections) {
428
- resolvedCollections = injectCollections(resolvedCollections ?? []);
621
+ if (plugin.collection?.injectCollections) {
622
+ resolvedCollections = plugin.collection.injectCollections(resolvedCollections ?? []);
623
+ }
624
+ }
429
625
  }
430
626
 
627
+ resolvedCollections = applyPermissionsFunctionIfEmpty(resolvedCollections, collectionPermissions);
628
+ resolvedCollections = filterOutNotAllowedCollections(resolvedCollections, authController);
431
629
  return resolvedCollections;
432
630
  }
433
631
 
@@ -448,7 +646,203 @@ async function resolveCMSViews(baseViews: CMSView[] | CMSViewsBuilder | undefine
448
646
  function getGroup(collectionOrView: EntityCollection<any, any> | CMSView) {
449
647
  const trimmed = collectionOrView.group?.trim();
450
648
  if (!trimmed || trimmed === "") {
451
- return "Views";
649
+ return NAVIGATION_DEFAULT_GROUP_NAME;
650
+ }
651
+ return trimmed ?? NAVIGATION_DEFAULT_GROUP_NAME;
652
+ }
653
+
654
+ function areCollectionListsEqual(a: EntityCollection[], b: EntityCollection[]) {
655
+ if (a.length !== b.length) {
656
+ return false;
657
+ }
658
+ const aCopy = [...a];
659
+ const bCopy = [...b];
660
+ const aSorted = aCopy.sort((x, y) => x.id.localeCompare(y.id));
661
+ const bSorted = bCopy.sort((x, y) => x.id.localeCompare(y.id));
662
+ return aSorted.every((value, index) => areCollectionsEqual(value, bSorted[index]));
663
+ }
664
+
665
+ function areCollectionsEqual(a: EntityCollection, b: EntityCollection) {
666
+ const {
667
+ subcollections: subcollectionsA,
668
+ ...restA
669
+ } = a;
670
+ const {
671
+ subcollections: subcollectionsB,
672
+ ...restB
673
+ } = b;
674
+ if (!areCollectionListsEqual(subcollectionsA ?? [], subcollectionsB ?? [])) {
675
+ return false;
452
676
  }
453
- return trimmed ?? "Views";
677
+ return equal(removeFunctions(restA), removeFunctions(restB));
678
+ }
679
+
680
+ function useCustomBlocker(): NavigationBlocker {
681
+ const [blockListeners, setBlockListeners] = useState<Record<string, {
682
+ block: boolean,
683
+ basePath?: string
684
+ }>>({});
685
+
686
+ const shouldBlock = Object.values(blockListeners).some(b => b.block);
687
+
688
+ let blocker: any;
689
+ try {
690
+ blocker = useBlocker(({
691
+ nextLocation
692
+ }) => {
693
+ const allBasePaths = Object.values(blockListeners).map(b => b.basePath).filter(Boolean) as string[];
694
+ if (allBasePaths && allBasePaths.some(path => nextLocation.pathname.startsWith(path)))
695
+ return false;
696
+ return shouldBlock;
697
+ });
698
+ } catch (e) {
699
+ // console.warn("Blocker not available, navigation will not be blocked");
700
+ }
701
+
702
+ const updateBlockListener = (path: string, block: boolean, basePath?: string) => {
703
+ setBlockListeners(prev => ({
704
+ ...prev,
705
+ [path]: {
706
+ block,
707
+ basePath
708
+ }
709
+ }));
710
+ return () => setBlockListeners(prev => {
711
+ const {
712
+ [path]: removed,
713
+ ...rest
714
+ } = prev;
715
+ return rest;
716
+ })
717
+ };
718
+
719
+ const isBlocked = (path: string) => {
720
+ return (blockListeners[path]?.block ?? false) && blocker?.state === "blocked";
721
+ }
722
+
723
+ return {
724
+ updateBlockListener,
725
+ isBlocked,
726
+ proceed: blocker?.proceed,
727
+ reset: blocker?.reset
728
+ }
729
+ }
730
+
731
+ function computeNavigationGroups({
732
+ navigationGroupMappings,
733
+ collections,
734
+ views,
735
+ plugins
736
+ }: {
737
+ navigationGroupMappings?: NavigationGroupMapping[],
738
+ collections?: EntityCollection[],
739
+ views?: CMSView[],
740
+ plugins?: FireCMSPlugin[]
741
+ }): NavigationGroupMapping[] {
742
+
743
+ let result = navigationGroupMappings;
744
+
745
+ // Merge plugin navigation entries
746
+ result = plugins ? plugins?.reduce((acc, plugin) => {
747
+ if (plugin.homePage?.navigationEntries) {
748
+ plugin.homePage.navigationEntries.forEach((entry) => {
749
+ const {
750
+ name,
751
+ entries
752
+ } = entry;
753
+ const existingGroup = acc.find(entry => entry.name === name);
754
+ if (existingGroup) {
755
+ existingGroup.entries.push(...entries);
756
+ } else {
757
+ acc.push({
758
+ name,
759
+ entries: [...entries]
760
+ });
761
+ }
762
+ });
763
+
764
+ }
765
+ return acc;
766
+ }, [...(result ?? [])] as NavigationGroupMapping[]) : result;
767
+
768
+ // Track all entries that are already assigned to groups
769
+ const assignedEntries = new Set<string>();
770
+ if (result) {
771
+ result.forEach(group => {
772
+ group.entries.forEach(entry => assignedEntries.add(entry));
773
+ });
774
+ }
775
+
776
+ // Find collections and views that are NOT in any persisted group
777
+ const unassignedGroupMap: Record<string, string[]> = {};
778
+
779
+ // Check collections
780
+ (collections ?? []).forEach(collection => {
781
+ const entry = collection.id ?? collection.path;
782
+ if (!assignedEntries.has(entry)) {
783
+ const groupName = getGroup(collection);
784
+ if (!unassignedGroupMap[groupName]) unassignedGroupMap[groupName] = [];
785
+ unassignedGroupMap[groupName].push(entry);
786
+ }
787
+ });
788
+
789
+ // Check views
790
+ (views ?? []).forEach(view => {
791
+ const entry = view.path;
792
+ if (!assignedEntries.has(entry)) {
793
+ const groupName = getGroup(view);
794
+ if (!unassignedGroupMap[groupName]) unassignedGroupMap[groupName] = [];
795
+ unassignedGroupMap[groupName].push(entry);
796
+ }
797
+ });
798
+
799
+ // Merge unassigned entries into existing groups or create new groups
800
+ Object.entries(unassignedGroupMap).forEach(([groupName, entries]) => {
801
+ if (result) {
802
+ const existingGroup = result.find(g => g.name === groupName);
803
+ if (existingGroup) {
804
+ existingGroup.entries.push(...entries);
805
+ } else {
806
+ result.push({
807
+ name: groupName,
808
+ entries
809
+ });
810
+ }
811
+ }
812
+ });
813
+
814
+ if (!result) {
815
+ // No persisted data at all - create from scratch
816
+ result = [];
817
+ const groupMap: Record<string, string[]> = {};
818
+
819
+ // Add collections
820
+ (collections ?? []).forEach(collection => {
821
+ const name = getGroup(collection);
822
+ const entry = collection.id ?? collection.path;
823
+ if (!groupMap[name]) groupMap[name] = [];
824
+ groupMap[name].push(entry);
825
+ });
826
+
827
+ // Add views
828
+ (views ?? []).forEach(view => {
829
+ const name = getGroup(view);
830
+ const entry = view.path;
831
+ if (!groupMap[name]) groupMap[name] = [];
832
+ groupMap[name].push(entry);
833
+ });
834
+
835
+ // Convert groupMap to result array
836
+ result = Object.entries(groupMap).map(([name, entries]) => ({
837
+ name,
838
+ entries
839
+ }));
840
+ }
841
+
842
+ // Remove duplicates in entries
843
+ result.forEach(group => {
844
+ group.entries = [...new Set(group.entries)];
845
+ });
846
+
847
+ return result;
454
848
  }