@firecms/core 3.0.0-canary.98 → 3.0.0-rc.1

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 (349) hide show
  1. package/README.md +2 -2
  2. package/dist/app/Drawer.d.ts +0 -1
  3. package/dist/app/Scaffold.d.ts +4 -0
  4. package/dist/components/ArrayContainer.d.ts +31 -12
  5. package/dist/components/{DeleteConfirmationDialog.d.ts → ConfirmationDialog.d.ts} +1 -1
  6. package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +3 -1
  7. package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +2 -2
  8. package/dist/components/EntityCollectionTable/EntityCollectionTableProps.d.ts +17 -3
  9. package/dist/components/EntityCollectionTable/fields/TableReferenceField.d.ts +1 -1
  10. package/dist/components/EntityCollectionTable/index.d.ts +1 -1
  11. package/dist/components/EntityCollectionTable/internal/popup_field/PopupFormField.d.ts +6 -3
  12. package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +8 -0
  13. package/dist/components/EntityCollectionView/utils.d.ts +3 -0
  14. package/dist/components/EntityJsonPreview.d.ts +3 -0
  15. package/dist/components/EntityPreview.d.ts +8 -6
  16. package/dist/components/HomePage/DefaultHomePage.d.ts +2 -15
  17. package/dist/components/HomePage/HomePageDnD.d.ts +76 -0
  18. package/dist/components/HomePage/NavigationCard.d.ts +3 -1
  19. package/dist/components/HomePage/NavigationCardBinding.d.ts +3 -2
  20. package/dist/components/HomePage/NavigationGroup.d.ts +8 -1
  21. package/dist/components/HomePage/RenameGroupDialog.d.ts +9 -0
  22. package/dist/components/PropertyConfigBadge.d.ts +2 -1
  23. package/dist/components/PropertyIdCopyTooltip.d.ts +8 -0
  24. package/dist/components/SelectableTable/SelectableTable.d.ts +13 -3
  25. package/dist/components/SelectableTable/filters/ReferenceFilterField.d.ts +1 -1
  26. package/dist/components/UnsavedChangesDialog.d.ts +8 -0
  27. package/dist/components/VirtualTable/VirtualTableProps.d.ts +11 -2
  28. package/dist/components/common/default_entity_actions.d.ts +0 -2
  29. package/dist/components/common/index.d.ts +1 -1
  30. package/dist/components/common/useColumnsIds.d.ts +1 -0
  31. package/dist/components/common/{useDataSourceEntityCollectionTableController.d.ts → useDataSourceTableController.d.ts} +10 -2
  32. package/dist/components/common/useDebouncedCallback.d.ts +1 -0
  33. package/dist/components/common/useScrollRestoration.d.ts +14 -0
  34. package/dist/components/index.d.ts +3 -1
  35. package/dist/contexts/BreacrumbsContext.d.ts +8 -0
  36. package/dist/core/DefaultAppBar.d.ts +8 -2
  37. package/dist/core/DrawerNavigationItem.d.ts +2 -1
  38. package/dist/core/EntityEditView.d.ts +40 -22
  39. package/dist/core/EntityEditViewFormActions.d.ts +2 -0
  40. package/dist/core/FireCMS.d.ts +2 -2
  41. package/dist/core/FireCMSRouter.d.ts +4 -0
  42. package/dist/core/NavigationRoutes.d.ts +0 -1
  43. package/dist/core/SideDialogs.d.ts +4 -2
  44. package/dist/core/field_configs.d.ts +1 -1
  45. package/dist/core/index.d.ts +2 -1
  46. package/dist/form/EntityForm.d.ts +50 -0
  47. package/dist/form/EntityFormActions.d.ts +21 -0
  48. package/dist/form/PropertyFieldBinding.d.ts +1 -1
  49. package/dist/form/components/FormEntry.d.ts +6 -0
  50. package/dist/form/components/FormLayout.d.ts +5 -0
  51. package/dist/form/components/LabelWithIcon.d.ts +1 -1
  52. package/dist/form/components/LabelWithIconAndTooltip.d.ts +15 -0
  53. package/dist/form/components/index.d.ts +3 -1
  54. package/dist/form/field_bindings/ArrayCustomShapedFieldBinding.d.ts +1 -1
  55. package/dist/form/field_bindings/ArrayOfReferencesFieldBinding.d.ts +1 -1
  56. package/dist/form/field_bindings/BlockFieldBinding.d.ts +1 -1
  57. package/dist/form/field_bindings/KeyValueFieldBinding.d.ts +1 -1
  58. package/dist/form/field_bindings/MapFieldBinding.d.ts +1 -1
  59. package/dist/form/field_bindings/MarkdownEditorFieldBinding.d.ts +11 -0
  60. package/dist/form/field_bindings/{MultiSelectBinding.d.ts → MultiSelectFieldBinding.d.ts} +1 -1
  61. package/dist/form/field_bindings/ReadOnlyFieldBinding.d.ts +1 -1
  62. package/dist/form/field_bindings/ReferenceAsStringFieldBinding.d.ts +9 -0
  63. package/dist/form/field_bindings/ReferenceFieldBinding.d.ts +2 -2
  64. package/dist/form/field_bindings/RepeatFieldBinding.d.ts +1 -1
  65. package/dist/form/field_bindings/SelectFieldBinding.d.ts +1 -1
  66. package/dist/form/field_bindings/StorageUploadFieldBinding.d.ts +4 -10
  67. package/dist/form/field_bindings/SwitchFieldBinding.d.ts +1 -2
  68. package/dist/form/field_bindings/TextFieldBinding.d.ts +1 -1
  69. package/dist/form/index.d.ts +17 -16
  70. package/dist/form/useClearRestoreValue.d.ts +2 -2
  71. package/dist/hooks/data/delete.d.ts +4 -4
  72. package/dist/hooks/data/save.d.ts +3 -3
  73. package/dist/hooks/data/useCollectionFetch.d.ts +1 -1
  74. package/dist/hooks/data/useEntityFetch.d.ts +4 -3
  75. package/dist/hooks/useAuthController.d.ts +1 -1
  76. package/dist/hooks/useBreadcrumbsController.d.ts +26 -0
  77. package/dist/hooks/useBuildNavigationController.d.ts +57 -12
  78. package/dist/hooks/useFireCMSContext.d.ts +1 -1
  79. package/dist/hooks/useModeController.d.ts +1 -2
  80. package/dist/hooks/useProjectLog.d.ts +7 -1
  81. package/dist/hooks/useResolvedNavigationFrom.d.ts +3 -3
  82. package/dist/hooks/useValidateAuthenticator.d.ts +3 -3
  83. package/dist/index.es.js +20108 -14471
  84. package/dist/index.es.js.map +1 -1
  85. package/dist/index.umd.js +20039 -14407
  86. package/dist/index.umd.js.map +1 -1
  87. package/dist/internal/useBuildDataSource.d.ts +3 -2
  88. package/dist/internal/useBuildSideEntityController.d.ts +3 -3
  89. package/dist/internal/useUnsavedChangesDialog.d.ts +7 -9
  90. package/dist/preview/PropertyPreviewProps.d.ts +1 -1
  91. package/dist/preview/components/EnumValuesChip.d.ts +1 -1
  92. package/dist/preview/components/ReferencePreview.d.ts +2 -2
  93. package/dist/preview/util.d.ts +3 -3
  94. package/dist/routes/CustomCMSRoute.d.ts +4 -0
  95. package/dist/routes/FireCMSRoute.d.ts +1 -0
  96. package/dist/routes/HomePageRoute.d.ts +3 -0
  97. package/dist/types/analytics.d.ts +1 -1
  98. package/dist/types/auth.d.ts +7 -9
  99. package/dist/types/collections.d.ts +86 -25
  100. package/dist/types/customization_controller.d.ts +8 -0
  101. package/dist/types/datasource.d.ts +19 -17
  102. package/dist/types/dialogs_controller.d.ts +7 -3
  103. package/dist/types/entities.d.ts +2 -1
  104. package/dist/types/entity_actions.d.ts +58 -8
  105. package/dist/types/entity_callbacks.d.ts +16 -16
  106. package/dist/types/entity_overrides.d.ts +2 -2
  107. package/dist/types/export_import.d.ts +4 -4
  108. package/dist/types/fields.d.ts +43 -17
  109. package/dist/types/firecms.d.ts +16 -3
  110. package/dist/types/firecms_context.d.ts +1 -1
  111. package/dist/types/navigation.d.ts +60 -17
  112. package/dist/types/permissions.d.ts +4 -4
  113. package/dist/types/plugins.d.ts +42 -9
  114. package/dist/types/properties.d.ts +65 -22
  115. package/dist/types/property_config.d.ts +1 -3
  116. package/dist/types/roles.d.ts +3 -0
  117. package/dist/types/side_dialogs_controller.d.ts +10 -0
  118. package/dist/types/side_entity_controller.d.ts +14 -1
  119. package/dist/types/storage.d.ts +75 -0
  120. package/dist/types/user.d.ts +1 -0
  121. package/dist/util/builders.d.ts +3 -3
  122. package/dist/util/callbacks.d.ts +2 -0
  123. package/dist/util/createFormexStub.d.ts +2 -0
  124. package/dist/util/entities.d.ts +2 -2
  125. package/dist/util/entity_actions.d.ts +2 -0
  126. package/dist/util/entity_cache.d.ts +23 -0
  127. package/dist/util/icon_synonyms.d.ts +0 -1
  128. package/dist/util/icons.d.ts +5 -2
  129. package/dist/util/index.d.ts +3 -0
  130. package/dist/util/navigation_from_path.d.ts +10 -1
  131. package/dist/util/navigation_utils.d.ts +13 -1
  132. package/dist/util/objects.d.ts +2 -1
  133. package/dist/util/permissions.d.ts +4 -4
  134. package/dist/util/property_utils.d.ts +4 -4
  135. package/dist/util/references.d.ts +2 -2
  136. package/dist/util/resolutions.d.ts +30 -6
  137. package/dist/util/storage.d.ts +1 -1
  138. package/dist/util/useStorageUploadController.d.ts +2 -2
  139. package/package.json +133 -125
  140. package/src/app/Drawer.tsx +0 -1
  141. package/src/app/Scaffold.tsx +33 -29
  142. package/src/components/ArrayContainer.tsx +447 -229
  143. package/src/components/CircularProgressCenter.tsx +1 -1
  144. package/src/components/ClearFilterSortButton.tsx +1 -1
  145. package/src/components/{DeleteConfirmationDialog.tsx → ConfirmationDialog.tsx} +12 -11
  146. package/src/components/DeleteEntityDialog.tsx +13 -20
  147. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +59 -25
  148. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +23 -17
  149. package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +20 -3
  150. package/src/components/EntityCollectionTable/PropertyTableCell.tsx +35 -9
  151. package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +21 -16
  152. package/src/components/EntityCollectionTable/fields/TableStorageUpload.tsx +6 -12
  153. package/src/components/EntityCollectionTable/index.tsx +1 -1
  154. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +6 -6
  155. package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +35 -26
  156. package/src/components/EntityCollectionTable/internal/EntityTableCellActions.tsx +20 -8
  157. package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +132 -101
  158. package/src/components/EntityCollectionTable/internal/popup_field/useDraggable.tsx +9 -9
  159. package/src/components/EntityCollectionView/EntityCollectionView.tsx +178 -85
  160. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +7 -4
  161. package/src/components/EntityCollectionView/useSelectionController.tsx +5 -4
  162. package/src/components/EntityCollectionView/utils.ts +19 -0
  163. package/src/components/EntityJsonPreview.tsx +66 -0
  164. package/src/components/EntityPreview.tsx +75 -57
  165. package/src/components/EntityView.tsx +8 -5
  166. package/src/components/ErrorView.tsx +3 -3
  167. package/src/components/FireCMSLogo.tsx +7 -51
  168. package/src/components/HomePage/DefaultHomePage.tsx +522 -160
  169. package/src/components/HomePage/FavouritesView.tsx +9 -14
  170. package/src/components/HomePage/HomePageDnD.tsx +642 -0
  171. package/src/components/HomePage/NavigationCard.tsx +47 -38
  172. package/src/components/HomePage/NavigationCardBinding.tsx +16 -15
  173. package/src/components/HomePage/NavigationGroup.tsx +144 -30
  174. package/src/components/HomePage/RenameGroupDialog.tsx +117 -0
  175. package/src/components/HomePage/SmallNavigationCard.tsx +1 -2
  176. package/src/components/NotFoundPage.tsx +2 -2
  177. package/src/components/PropertyConfigBadge.tsx +9 -3
  178. package/src/components/PropertyIdCopyTooltip.tsx +47 -0
  179. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +22 -13
  180. package/src/components/SearchIconsView.tsx +2 -2
  181. package/src/components/SelectableTable/SelectableTable.tsx +154 -142
  182. package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +4 -2
  183. package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +10 -8
  184. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +59 -10
  185. package/src/components/UnsavedChangesDialog.tsx +46 -0
  186. package/src/components/VirtualTable/VirtualTable.tsx +65 -44
  187. package/src/components/VirtualTable/VirtualTableCell.tsx +0 -8
  188. package/src/components/VirtualTable/VirtualTableHeader.tsx +8 -8
  189. package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +1 -1
  190. package/src/components/VirtualTable/VirtualTableProps.tsx +12 -2
  191. package/src/components/VirtualTable/VirtualTableRow.tsx +1 -1
  192. package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +4 -4
  193. package/src/components/VirtualTable/fields/VirtualTableInput.tsx +2 -2
  194. package/src/components/VirtualTable/fields/VirtualTableNumberInput.tsx +2 -1
  195. package/src/components/VirtualTable/fields/VirtualTableSelect.tsx +16 -28
  196. package/src/components/common/default_entity_actions.tsx +62 -42
  197. package/src/components/common/index.ts +1 -1
  198. package/src/components/common/useColumnsIds.tsx +1 -1
  199. package/src/components/common/useDataSourceTableController.tsx +420 -0
  200. package/src/components/common/useDebouncedCallback.tsx +20 -0
  201. package/src/components/common/useScrollRestoration.tsx +68 -0
  202. package/src/components/common/useTableSearchHelper.ts +1 -0
  203. package/src/components/index.tsx +4 -1
  204. package/src/contexts/BreacrumbsContext.tsx +38 -0
  205. package/src/contexts/DialogsProvider.tsx +3 -2
  206. package/src/contexts/ModeController.tsx +1 -3
  207. package/src/contexts/SnackbarProvider.tsx +2 -0
  208. package/src/core/DefaultAppBar.tsx +124 -85
  209. package/src/core/DefaultDrawer.tsx +30 -22
  210. package/src/core/DrawerNavigationItem.tsx +32 -28
  211. package/src/core/EntityEditView.tsx +388 -995
  212. package/src/core/EntityEditViewFormActions.tsx +329 -0
  213. package/src/core/EntitySidePanel.tsx +88 -20
  214. package/src/core/FireCMS.tsx +46 -25
  215. package/src/core/FireCMSRouter.tsx +17 -0
  216. package/src/core/NavigationRoutes.tsx +23 -32
  217. package/src/core/SideDialogs.tsx +22 -12
  218. package/src/core/field_configs.tsx +24 -10
  219. package/src/core/index.tsx +4 -2
  220. package/src/form/EntityForm.tsx +814 -0
  221. package/src/form/EntityFormActions.tsx +211 -0
  222. package/src/form/PropertyFieldBinding.tsx +55 -41
  223. package/src/form/components/CustomIdField.tsx +9 -3
  224. package/src/form/components/FieldHelperText.tsx +1 -1
  225. package/src/form/components/FormEntry.tsx +22 -0
  226. package/src/form/components/FormLayout.tsx +16 -0
  227. package/src/form/components/LabelWithIcon.tsx +30 -19
  228. package/src/form/components/LabelWithIconAndTooltip.tsx +28 -0
  229. package/src/form/components/StorageItemPreview.tsx +5 -4
  230. package/src/form/components/StorageUploadProgress.tsx +2 -3
  231. package/src/form/components/index.tsx +3 -1
  232. package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +30 -18
  233. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +47 -36
  234. package/src/form/field_bindings/BlockFieldBinding.tsx +55 -33
  235. package/src/form/field_bindings/DateTimeFieldBinding.tsx +18 -14
  236. package/src/form/field_bindings/KeyValueFieldBinding.tsx +19 -15
  237. package/src/form/field_bindings/MapFieldBinding.tsx +72 -62
  238. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +159 -0
  239. package/src/form/field_bindings/{MultiSelectBinding.tsx → MultiSelectFieldBinding.tsx} +26 -21
  240. package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +10 -8
  241. package/src/form/field_bindings/ReferenceAsStringFieldBinding.tsx +135 -0
  242. package/src/form/field_bindings/ReferenceFieldBinding.tsx +28 -19
  243. package/src/form/field_bindings/RepeatFieldBinding.tsx +56 -32
  244. package/src/form/field_bindings/SelectFieldBinding.tsx +22 -13
  245. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +247 -168
  246. package/src/form/field_bindings/SwitchFieldBinding.tsx +29 -24
  247. package/src/form/field_bindings/TextFieldBinding.tsx +28 -24
  248. package/src/form/index.tsx +17 -37
  249. package/src/form/useClearRestoreValue.tsx +2 -2
  250. package/src/form/validation.ts +12 -6
  251. package/src/hooks/data/delete.ts +6 -5
  252. package/src/hooks/data/save.ts +26 -35
  253. package/src/hooks/data/useCollectionFetch.tsx +3 -3
  254. package/src/hooks/data/useDataSource.tsx +10 -2
  255. package/src/hooks/data/useEntityFetch.tsx +10 -6
  256. package/src/hooks/useAuthController.tsx +1 -1
  257. package/src/hooks/useBreadcrumbsController.tsx +31 -0
  258. package/src/hooks/useBrowserTitleAndIcon.tsx +1 -1
  259. package/src/hooks/useBuildModeController.tsx +15 -28
  260. package/src/hooks/useBuildNavigationController.tsx +386 -124
  261. package/src/hooks/useFireCMSContext.tsx +3 -33
  262. package/src/hooks/useLargeLayout.tsx +0 -35
  263. package/src/hooks/useModeController.tsx +1 -2
  264. package/src/hooks/useProjectLog.tsx +16 -5
  265. package/src/hooks/useResolvedNavigationFrom.tsx +9 -11
  266. package/src/hooks/useValidateAuthenticator.tsx +3 -3
  267. package/src/internal/useBuildDataSource.ts +67 -80
  268. package/src/internal/useBuildSideDialogsController.tsx +4 -2
  269. package/src/internal/useBuildSideEntityController.tsx +149 -86
  270. package/src/internal/useUnsavedChangesDialog.tsx +127 -91
  271. package/src/preview/PropertyPreview.tsx +28 -12
  272. package/src/preview/PropertyPreviewProps.tsx +1 -1
  273. package/src/preview/components/BooleanPreview.tsx +1 -1
  274. package/src/preview/components/EmptyValue.tsx +1 -1
  275. package/src/preview/components/EnumValuesChip.tsx +1 -1
  276. package/src/preview/components/ImagePreview.tsx +10 -9
  277. package/src/preview/components/ReferencePreview.tsx +6 -16
  278. package/src/preview/components/UrlComponentPreview.tsx +20 -21
  279. package/src/preview/property_previews/ArrayOfMapsPreview.tsx +6 -5
  280. package/src/preview/property_previews/ArrayOfReferencesPreview.tsx +5 -4
  281. package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +5 -3
  282. package/src/preview/property_previews/ArrayOfStringsPreview.tsx +4 -3
  283. package/src/preview/property_previews/ArrayOneOfPreview.tsx +6 -4
  284. package/src/preview/property_previews/ArrayPropertyPreview.tsx +5 -3
  285. package/src/preview/property_previews/MapPropertyPreview.tsx +7 -6
  286. package/src/preview/property_previews/SkeletonPropertyComponent.tsx +13 -13
  287. package/src/preview/property_previews/StringPropertyPreview.tsx +2 -2
  288. package/src/preview/util.ts +10 -10
  289. package/src/routes/CustomCMSRoute.tsx +21 -0
  290. package/src/routes/FireCMSRoute.tsx +246 -0
  291. package/src/routes/HomePageRoute.tsx +17 -0
  292. package/src/types/analytics.ts +3 -0
  293. package/src/types/auth.tsx +8 -12
  294. package/src/types/collections.ts +101 -28
  295. package/src/types/customization_controller.tsx +9 -0
  296. package/src/types/datasource.ts +21 -20
  297. package/src/types/dialogs_controller.tsx +7 -3
  298. package/src/types/entities.ts +3 -1
  299. package/src/types/entity_actions.tsx +71 -8
  300. package/src/types/entity_callbacks.ts +18 -18
  301. package/src/types/entity_overrides.tsx +2 -2
  302. package/src/types/export_import.ts +4 -4
  303. package/src/types/fields.tsx +52 -19
  304. package/src/types/firecms.tsx +18 -4
  305. package/src/types/firecms_context.tsx +1 -1
  306. package/src/types/navigation.ts +76 -22
  307. package/src/types/permissions.ts +5 -5
  308. package/src/types/plugins.tsx +50 -9
  309. package/src/types/properties.ts +74 -22
  310. package/src/types/property_config.tsx +1 -2
  311. package/src/types/roles.ts +3 -0
  312. package/src/types/side_dialogs_controller.tsx +15 -0
  313. package/src/types/side_entity_controller.tsx +16 -1
  314. package/src/types/storage.ts +82 -0
  315. package/src/types/user.ts +2 -0
  316. package/src/util/builders.ts +10 -8
  317. package/src/util/callbacks.ts +119 -0
  318. package/src/util/createFormexStub.tsx +62 -0
  319. package/src/util/entities.ts +5 -3
  320. package/src/util/entity_actions.ts +28 -0
  321. package/src/util/entity_cache.ts +204 -0
  322. package/src/util/icon_list.ts +1 -1
  323. package/src/util/icon_synonyms.ts +0 -1
  324. package/src/util/icons.tsx +36 -11
  325. package/src/util/index.ts +3 -0
  326. package/src/util/join_collections.ts +9 -2
  327. package/src/util/make_properties_editable.ts +13 -5
  328. package/src/util/navigation_from_path.ts +33 -12
  329. package/src/util/navigation_utils.ts +135 -19
  330. package/src/util/objects.ts +74 -14
  331. package/src/util/parent_references_from_path.ts +3 -3
  332. package/src/util/permissions.ts +8 -8
  333. package/src/util/property_utils.tsx +17 -6
  334. package/src/util/references.ts +19 -8
  335. package/src/util/resolutions.ts +93 -24
  336. package/src/util/storage.ts +6 -2
  337. package/src/util/useStorageUploadController.tsx +74 -29
  338. package/dist/components/EntityCollectionTable/internal/popup_field/ElementResizeListener.d.ts +0 -5
  339. package/dist/components/PropertyIdCopyTooltipContent.d.ts +0 -3
  340. package/dist/form/PropertiesForm.d.ts +0 -8
  341. package/dist/form/components/FormikArrayContainer.d.ts +0 -18
  342. package/dist/form/field_bindings/MarkdownFieldBinding.d.ts +0 -9
  343. package/src/components/EntityCollectionTable/internal/popup_field/ElementResizeListener.tsx +0 -59
  344. package/src/components/PropertyIdCopyTooltipContent.tsx +0 -27
  345. package/src/components/common/useDataSourceEntityCollectionTableController.tsx +0 -236
  346. package/src/form/PropertiesForm.tsx +0 -81
  347. package/src/form/components/FormikArrayContainer.tsx +0 -44
  348. package/src/form/field_bindings/MarkdownFieldBinding.tsx +0 -695
  349. /package/src/util/{common.tsx → common.ts} +0 -0
@@ -1,1133 +1,526 @@
1
- import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
1
+ import React, { useEffect, useMemo, useState } from "react";
2
2
  import {
3
- CMSAnalyticsEvent,
4
3
  Entity,
5
- EntityAction,
6
4
  EntityCollection,
7
- EntityCustomView,
8
5
  EntityStatus,
9
- EntityValues,
10
6
  FireCMSPlugin,
11
7
  FormContext,
12
8
  PluginFormActionProps,
13
- PropertyFieldBindingProps,
14
- ResolvedEntityCollection,
15
9
  User
16
10
  } from "../types";
17
- import equal from "react-fast-compare"
18
11
 
12
+ import { CircularProgressCenter, EntityCollectionView, EntityView, ErrorBoundary } from "../components";
19
13
  import {
20
- CircularProgressCenter,
21
- copyEntityAction,
22
- deleteEntityAction,
23
- EntityCollectionView,
24
- EntityView,
25
- ErrorBoundary,
26
- getFormFieldKeys,
27
- } from "../components";
28
- import {
29
- canCreateEntity,
30
- canDeleteEntity,
31
14
  canEditEntity,
32
- getDefaultValuesFor,
33
- getEntityTitlePropertyKey,
34
- getValueInPath,
35
- isHidden,
36
- isReadOnly,
37
15
  removeInitialAndTrailingSlashes,
38
16
  resolveCollection,
39
17
  resolveDefaultSelectedView,
40
- resolveEntityView,
41
- useDebouncedCallback
18
+ resolvedSelectedEntityView
42
19
  } from "../util";
43
20
 
44
21
  import {
45
- saveEntityWithCallbacks,
46
22
  useAuthController,
47
23
  useCustomizationController,
48
- useDataSource,
49
24
  useEntityFetch,
50
25
  useFireCMSContext,
51
- useSideEntityController,
52
- useSnackbarController
26
+ useLargeLayout
53
27
  } from "../hooks";
54
- import {
55
- Alert,
56
- Button,
57
- CircularProgress,
58
- CloseIcon,
59
- cls,
60
- defaultBorderMixin,
61
- DialogActions,
62
- IconButton,
63
- NotesIcon,
64
- paperMixin,
65
- Tab,
66
- Tabs,
67
- Tooltip,
68
- Typography
69
- } from "@firecms/ui";
70
- import { useSideDialogContext } from "./index";
71
- import { Formex, FormexController, getIn, setIn, useCreateFormex } from "@firecms/formex";
72
- import { useAnalyticsController } from "../hooks/useAnalyticsController";
73
- import { CustomIdField } from "../form/components/CustomIdField";
74
- import { CustomFieldValidator, getYupEntitySchema } from "../form/validation";
75
- import { ErrorFocus } from "../form/components/ErrorFocus";
76
- import { PropertyIdCopyTooltipContent } from "../components/PropertyIdCopyTooltipContent";
77
- import { LabelWithIcon, PropertyFieldBinding } from "../form";
78
- import { ValidationError } from "yup";
79
-
80
- const MAIN_TAB_VALUE = "main_##Q$SC^#S6";
28
+ import { CircularProgress, cls, CodeIcon, defaultBorderMixin, Tab, Tabs, Typography } from "@firecms/ui";
29
+ import { getEntityFromCache } from "../util/entity_cache";
30
+ import { EntityForm, EntityFormProps } from "../form";
31
+ import { EntityEditViewFormActions } from "./EntityEditViewFormActions";
32
+ import { EntityJsonPreview } from "../components/EntityJsonPreview";
33
+ import { createFormexStub } from "../util/createFormexStub";
34
+
35
+ export const MAIN_TAB_VALUE = "__main_##Q$SC^#S6";
36
+ export const JSON_TAB_VALUE = "__json";
37
+
38
+ export type OnUpdateParams = {
39
+ entity: Entity<any>,
40
+ status: EntityStatus,
41
+ path: string,
42
+ entityId?: string;
43
+ selectedTab?: string;
44
+ collection: EntityCollection<any>
45
+ };
46
+
47
+ export type OnTabChangeParams<M extends Record<string, any>> = {
48
+ path: string;
49
+ entityId?: string;
50
+ selectedTab?: string;
51
+ collection: EntityCollection<M>;
52
+
53
+ };
81
54
 
82
55
  export interface EntityEditViewProps<M extends Record<string, any>> {
56
+ /**
57
+ * The database path of the entity, e.g. "users" or "products".
58
+ */
83
59
  path: string;
60
+ /**
61
+ * The navigation path to the entity.
62
+ */
63
+ fullIdPath?: string;
84
64
  collection: EntityCollection<M>;
85
65
  entityId?: string;
66
+ databaseId?: string;
86
67
  copy?: boolean;
87
- selectedSubPath?: string;
68
+ selectedTab?: string;
88
69
  parentCollectionIds: string[];
89
- onValuesAreModified: (modified: boolean) => void;
90
- onUpdate?: (params: { entity: Entity<any> }) => void;
91
- onClose?: () => void;
70
+ onValuesModified?: (modified: boolean) => void;
71
+ onSaved?: (params: OnUpdateParams) => void;
72
+ onTabChange?: (props: OnTabChangeParams<M>) => void;
73
+ layout?: "side_panel" | "full_screen";
74
+ barActions?: React.ReactNode;
75
+ formProps?: Partial<EntityFormProps<M>>,
92
76
  }
93
77
 
94
78
  /**
95
79
  * This is the default view that is used as the content of a side panel when
96
80
  * an entity is opened.
97
- * You probably don't want to use this view directly since it is bound to the
98
- * side panel. Instead, you might want to use {@link EntityForm} or {@link EntityCollectionView}
99
81
  */
100
- export function EntityEditView<M extends Record<string, any>, UserType extends User>({
101
- entityId,
102
- ...props
103
- }: EntityEditViewProps<M>) {
82
+ export function EntityEditView<M extends Record<string, any>, USER extends User>({
83
+ entityId,
84
+ ...props
85
+ }: EntityEditViewProps<M>) {
86
+
104
87
  const {
105
88
  entity,
106
89
  dataLoading,
107
90
  // eslint-disable-next-line no-unused-vars
108
91
  dataLoadingError
109
- } = useEntityFetch<M, UserType>({
92
+ } = useEntityFetch<M, USER>({
110
93
  path: props.path,
111
94
  entityId: entityId,
112
95
  collection: props.collection,
96
+ databaseId: props.databaseId,
113
97
  useCache: false
114
98
  });
115
99
 
116
- if (dataLoading) {
117
- return <CircularProgressCenter/>
100
+ const cachedValues = entityId
101
+ ? getEntityFromCache(props.path + "/" + entityId)
102
+ : getEntityFromCache(props.path + "#new");
103
+
104
+ const authController = useAuthController();
105
+
106
+ const initialStatus = props.copy ? "copy" : (entityId ? "existing" : "new");
107
+ const [status, setStatus] = useState<EntityStatus>(initialStatus);
108
+
109
+ const canEdit = useMemo(() => {
110
+ if (status === "new" || status === "copy") {
111
+ return true;
112
+ } else {
113
+ return entity ? canEditEntity(props.collection, authController, props.path, entity ?? null) : undefined;
114
+ }
115
+ }, [authController, entity, status]);
116
+
117
+ if ((dataLoading && !cachedValues) || (!entity || canEdit === undefined) && (status === "existing" || status === "copy")) {
118
+ return <CircularProgressCenter/>;
118
119
  }
119
120
 
120
- if (entityId && !entity) {
121
- console.error(`Entity with id ${entityId} not found in collection ${props.collection.path}`);
121
+ if (entityId && !entity && !cachedValues) {
122
+ console.error(`Entity with id ${entityId} not found in collection ${props.path}`);
122
123
  }
123
124
 
124
125
  return <EntityEditViewInner<M> {...props}
125
126
  entityId={entityId}
126
127
  entity={entity}
127
- dataLoading={dataLoading}/>;
128
+ cachedDirtyValues={cachedValues as Partial<M>}
129
+ dataLoading={dataLoading}
130
+ status={status}
131
+ setStatus={setStatus}
132
+ canEdit={canEdit}
133
+ />;
128
134
  }
129
135
 
130
136
  export function EntityEditViewInner<M extends Record<string, any>>({
131
137
  path,
132
- entityId: entityIdProp,
133
- selectedSubPath: selectedSubPathProp,
134
- copy,
138
+ fullIdPath,
139
+ entityId,
140
+ selectedTab: selectedTabProp,
135
141
  collection,
136
142
  parentCollectionIds,
137
- onValuesAreModified,
138
- onUpdate,
139
- onClose,
143
+ onValuesModified,
144
+ onSaved,
145
+ onTabChange,
140
146
  entity,
147
+ cachedDirtyValues,
141
148
  dataLoading,
149
+ layout = "side_panel",
150
+ barActions,
151
+ status,
152
+ setStatus,
153
+ formProps,
154
+ canEdit
142
155
  }: EntityEditViewProps<M> & {
143
156
  entity?: Entity<M>,
144
- dataLoading: boolean
157
+ cachedDirtyValues?: Partial<M>, // dirty cached entity in memory
158
+ dataLoading: boolean,
159
+ status: EntityStatus,
160
+ setStatus: (status: EntityStatus) => void,
161
+ canEdit?: boolean,
145
162
  }) {
146
163
 
147
- if (collection.customId && collection.formAutoSave) {
148
- console.warn(`The collection ${collection.path} has customId and formAutoSave enabled. This is not supported and formAutoSave will be ignored`);
149
- }
150
-
151
- const [saving, setSaving] = useState(false);
152
- /**
153
- * These are the values that are being saved. They are debounced.
154
- * We use this only when autoSave is enabled.
155
- */
156
- const [valuesToBeSaved, setValuesToBeSaved] = useState<EntityValues<M> | undefined>(undefined);
157
- useDebouncedCallback(valuesToBeSaved, () => {
158
- if (valuesToBeSaved)
159
- saveEntity({
160
- entityId: usedEntity?.id,
161
- collection,
162
- path,
163
- values: valuesToBeSaved,
164
- closeAfterSave: false
165
- });
166
- }, false, 2000);
167
-
168
- // const largeLayout = useLargeLayout();
169
- // const largeLayoutTabSelected = useRef(!largeLayout);
170
- // const resolvedFormWidth: string = typeof formWidth === "number" ? `${formWidth}px` : formWidth ?? FORM_CONTAINER_WIDTH;
171
-
172
- const inputCollection = collection;
173
-
174
- const authController = useAuthController();
175
- const dataSource = useDataSource(collection);
176
- const sideDialogContext = useSideDialogContext();
177
- const sideEntityController = useSideEntityController();
178
- const snackbarController = useSnackbarController();
179
- const customizationController = useCustomizationController();
180
164
  const context = useFireCMSContext();
181
165
 
182
- const closeAfterSaveRef = useRef(false);
183
-
184
- const analyticsController = useAnalyticsController();
185
-
186
- const initialResolvedCollection = useMemo(() => resolveCollection({
187
- collection: inputCollection,
188
- path,
189
- values: entity?.values,
190
- fields: customizationController.propertyConfigs
191
- }), [entity?.values, path, customizationController.propertyConfigs]);
192
-
193
- const initialStatus = copy ? "copy" : (entityIdProp ? "existing" : "new");
194
- const [status, setStatus] = useState<EntityStatus>(initialStatus);
195
- const mustSetCustomId: boolean = (status === "new" || status === "copy") &&
196
- (Boolean(initialResolvedCollection.customId) && initialResolvedCollection.customId !== "optional");
197
- const initialEntityId: string | undefined = useMemo((): string | undefined => {
198
- if (status === "new" || status === "copy") {
199
- if (mustSetCustomId) {
200
- return undefined;
201
- } else {
202
- return dataSource.generateEntityId(path);
203
- }
204
- } else {
205
- return entityIdProp;
206
- }
207
- }, [entityIdProp, status]);
208
-
209
- const [entityId, setEntityId] = React.useState<string | undefined>(initialEntityId);
210
-
211
- // const doOnValuesChanges = (values?: EntityValues<M>) => {
212
- // const initialValues = formex.initialValues;
213
- // setInternalValues(values);
214
- // if (onValuesChanged)
215
- // onValuesChanged(values);
216
- // if (autoSave && values && !equal(values, initialValues)) {
217
- // save(values);
218
- // }
219
- // };
220
-
221
- const [entityIdError, setEntityIdError] = React.useState<boolean>(false);
222
- const [savingError, setSavingError] = React.useState<Error | undefined>();
223
-
224
- const [customIdLoading, setCustomIdLoading] = React.useState<boolean>(false);
225
-
226
- const defaultSelectedView = selectedSubPathProp ?? resolveDefaultSelectedView(
227
- collection ? collection.defaultSelectedView : undefined,
228
- {
229
- status,
230
- entityId
231
- }
232
- );
233
-
234
- const selectedTabRef = useRef<string>(defaultSelectedView ?? MAIN_TAB_VALUE);
235
- const baseDataSourceValuesRef = useRef<Partial<EntityValues<M>>>(getDataSourceEntityValues(initialResolvedCollection, status, entity));
236
-
237
- const mainViewVisible = selectedTabRef.current === MAIN_TAB_VALUE;
238
-
239
- // const initialValuesRef = useRef<EntityValues<M>>(entity?.values ?? baseDataSourceValues as EntityValues<M>);
240
- // const [internalValues, setInternalValues] = useState<EntityValues<M> | undefined>(entity?.values ?? baseDataSourceValuesRef.current as EntityValues<M>);
241
-
242
- // const modifiedValuesRef = useRef<EntityValues<M> | undefined>(undefined);
243
- // const modifiedValues = modifiedValuesRef.current;
244
-
245
- const subcollections = (collection.subcollections ?? []).filter(c => !c.hideFromNavigation);
246
- const subcollectionsCount = subcollections?.length ?? 0;
247
- const customViews = collection.entityViews;
248
- const customViewsCount = customViews?.length ?? 0;
249
- const autoSave = collection.formAutoSave && !collection.customId;
250
-
251
- const hasAdditionalViews = customViewsCount > 0 || subcollectionsCount > 0;
252
-
253
166
  const [usedEntity, setUsedEntity] = useState<Entity<M> | undefined>(entity);
254
- const [readOnly, setReadOnly] = useState<boolean | undefined>(undefined);
255
167
 
256
168
  useEffect(() => {
257
169
  if (entity)
258
170
  setUsedEntity(entity);
259
171
  }, [entity]);
260
172
 
261
- useEffect(() => {
262
- if (status === "new") {
263
- setReadOnly(false);
264
- } else {
265
- const editEnabled = usedEntity ? canEditEntity(collection, authController, path, usedEntity ?? null) : false;
266
- if (usedEntity)
267
- setReadOnly(!editEnabled);
268
- }
269
- }, [authController, usedEntity, status]);
270
-
271
- const onPreSaveHookError = useCallback((e: Error) => {
272
- setSaving(false);
273
- snackbarController.open({
274
- type: "error",
275
- message: "Error before saving: " + e?.message
276
- });
277
- console.error(e);
278
- }, [snackbarController]);
279
-
280
- const onSaveSuccessHookError = useCallback((e: Error) => {
281
- setSaving(false);
282
- snackbarController.open({
283
- type: "error",
284
- message: "Error after saving (entity is saved): " + e?.message
285
- });
286
- console.error(e);
287
- }, [snackbarController]);
288
-
289
- const onSaveSuccess = (updatedEntity: Entity<M>, closeAfterSave: boolean) => {
290
-
291
- setSaving(false);
292
- if (!autoSave)
293
- snackbarController.open({
294
- type: "success",
295
- message: `${collection.singularName ?? collection.name}: Saved correctly`
296
- });
297
-
298
- setUsedEntity(updatedEntity);
299
- setStatus("existing");
300
-
301
- onValuesAreModified(false);
302
-
303
- if (onUpdate)
304
- onUpdate({ entity: updatedEntity });
305
-
306
- if (closeAfterSave) {
307
- console.log("Closing side dialog")
308
- sideDialogContext.setBlocked(false);
309
- sideDialogContext.close(true);
310
- onClose?.();
311
- } else if (status !== "existing") {
312
- sideEntityController.replace({
313
- path,
314
- entityId: updatedEntity.id,
315
- selectedSubPath: selectedTabRef.current,
316
- updateUrl: true,
317
- collection,
318
- });
319
- }
320
-
321
- };
322
-
323
- const onSaveFailure = useCallback((e: Error) => {
324
-
325
- setSaving(false);
326
- snackbarController.open({
327
- type: "error",
328
- message: "Error saving: " + e?.message
329
- });
330
-
331
- console.error("Error saving entity", path, entityId);
332
- console.error(e);
333
- }, [entityId, path, snackbarController]);
334
-
335
- const saveEntity = ({
336
- values,
337
- previousValues,
338
- closeAfterSave,
339
- entityId,
340
- collection,
341
- path
342
- }: {
343
- collection: EntityCollection<M>,
344
- path: string,
345
- entityId: string | undefined,
346
- values: M,
347
- previousValues?: M,
348
- closeAfterSave: boolean,
349
- }) => {
350
- setSaving(true);
351
- return saveEntityWithCallbacks({
352
- path,
353
- entityId,
354
- values,
355
- previousValues,
356
- collection,
357
- status,
358
- dataSource,
359
- context,
360
- onSaveSuccess: (updatedEntity: Entity<M>) => onSaveSuccess(updatedEntity, closeAfterSave),
361
- onSaveFailure,
362
- onPreSaveHookError,
363
- onSaveSuccessHookError
364
- }).then();
365
- };
366
-
367
- const onSaveEntityRequest = async ({
368
- collection,
369
- path,
370
- entityId,
371
- values,
372
- previousValues,
373
- closeAfterSave,
374
- autoSave
375
- }: EntityFormSaveParams<M>): Promise<void> => {
376
- if (!status)
377
- return;
378
-
379
- if (autoSave) {
380
- setValuesToBeSaved(values);
381
- } else {
382
- return saveEntity({
383
- collection,
384
- path,
385
- entityId,
386
- values,
387
- previousValues,
388
- closeAfterSave
389
- });
390
- }
391
- };
392
-
393
- const onSubmit = (values: EntityValues<M>, formexController: FormexController<EntityValues<M>>) => {
394
-
395
- if (mustSetCustomId && !entityId) {
396
- console.error("Missing custom Id");
397
- setEntityIdError(true);
398
- formexController.setSubmitting(false);
399
- return;
400
- }
401
-
402
- setSavingError(undefined);
403
- setEntityIdError(false);
404
-
405
- if (status === "existing") {
406
- if (!entity?.id) throw Error("Form misconfiguration when saving, no id for existing entity");
407
- } else if (status === "new" || status === "copy") {
408
- if (inputCollection.customId) {
409
- if (inputCollection.customId !== "optional" && !entityId) {
410
- throw Error("Form misconfiguration when saving, entityId should be set");
411
- }
412
- }
413
- } else {
414
- throw Error("New FormType added, check EntityForm");
415
- }
416
-
417
- return save(values)
418
- ?.then(_ => {
419
- formexController.resetForm({
420
- values,
421
- submitCount: 0,
422
- touched: {}
423
- });
424
- })
425
- .finally(() => {
426
- formexController.setSubmitting(false);
427
- });
428
-
429
- };
430
-
431
- const formex: FormexController<M> = useCreateFormex<M>({
432
- initialValues: baseDataSourceValuesRef.current as M,
433
- onSubmit,
434
- validation: (values) => {
435
- return validationSchema?.validate(values, { abortEarly: false })
436
- .then(() => {
437
- return {};
438
- })
439
- .catch((e: any) => {
440
- const errors: Record<string, string> = {};
441
- e.inner.forEach((error: any) => {
442
- errors[error.path] = error.message;
443
- });
444
- return yupToFormErrors(e);
445
- });
446
- }
447
- });
448
-
449
- const resolvedCollection = resolveCollection<M>({
450
- collection: inputCollection,
451
- path,
452
- entityId,
453
- values: formex.values,
454
- previousValues: formex.initialValues,
455
- fields: customizationController.propertyConfigs
456
- });
457
-
458
- const lastSavedValues = useRef<EntityValues<M> | undefined>(entity?.values);
459
-
460
- const save = (values: EntityValues<M>): Promise<void> => {
461
- lastSavedValues.current = values;
462
- return onSaveEntityRequest({
463
- collection: resolvedCollection,
464
- path,
465
- entityId,
466
- values,
467
- previousValues: entity?.values,
468
- closeAfterSave: closeAfterSaveRef.current,
469
- autoSave: autoSave ?? false
470
- }).then(_ => {
471
- const eventName: CMSAnalyticsEvent = status === "new"
472
- ? "new_entity_saved"
473
- : (status === "copy" ? "entity_copied" : (status === "existing" ? "entity_edited" : "unmapped_event"));
474
- analyticsController.onAnalyticsEvent?.(eventName, { path });
475
- }).catch(e => {
476
- console.error(e);
477
- setSavingError(e);
478
- }).finally(() => {
479
- closeAfterSaveRef.current = false;
480
- });
481
- };
482
-
483
- const formContext: FormContext<M> = {
484
- // @ts-ignore
485
- setFieldValue: useCallback(formex.setFieldValue, []),
486
- values: formex.values,
487
- collection: resolvedCollection,
488
- entityId,
489
- path,
490
- save,
491
- formex
492
- };
493
-
494
- const resolvedEntityViews = customViews ? customViews
495
- .map(e => resolveEntityView(e, customizationController.entityViews))
496
- .filter(Boolean) as EntityCustomView[]
497
- : [];
498
-
499
- const selectedEntityView = resolvedEntityViews.find(e => e.key === selectedTabRef.current);
500
- const shouldShowEntityActions = !autoSave && (selectedTabRef.current === MAIN_TAB_VALUE || selectedEntityView?.includeActions);
501
-
502
- const customViewsView: React.ReactNode[] | undefined = customViews && resolvedEntityViews
503
- .map(
504
- (customView, colIndex) => {
505
- if (!customView)
506
- return null;
507
- if (selectedTabRef.current !== customView.key)
508
- return null;
509
- const Builder = customView.Builder;
510
- if (!Builder) {
511
- console.error("customView.Builder is not defined");
512
- return null;
513
- }
514
- return <div
515
- className={cls(defaultBorderMixin,
516
- "relative flex-grow w-full h-full overflow-auto ")}
517
- key={`custom_view_${customView.key}`}
518
- role="tabpanel">
519
- <ErrorBoundary>
520
- {formContext && <Builder
521
- collection={collection}
522
- entity={usedEntity}
523
- modifiedValues={formex.values ?? usedEntity?.values}
524
- formContext={formContext}
525
- />}
526
- </ErrorBoundary>
527
- </div>;
528
- }
529
- ).filter(Boolean);
530
-
531
- const globalLoading = (dataLoading && !usedEntity) ||
532
- ((!usedEntity || readOnly === undefined) && (status === "existing" || status === "copy"));
533
-
534
- const loading = globalLoading || saving;
535
-
536
- const subCollectionsViews = subcollections && subcollections.map(
537
- (subcollection, colIndex) => {
538
- const subcollectionId = subcollection.id ?? subcollection.path;
539
- const fullPath = usedEntity ? `${path}/${usedEntity?.id}/${removeInitialAndTrailingSlashes(subcollectionId)}` : undefined;
540
- if (selectedTabRef.current !== subcollectionId)
541
- return null;
542
- return (
543
- <div
544
- className={"relative flex-grow h-full overflow-auto w-full"}
545
- key={`subcol_${subcollectionId}`}
546
- role="tabpanel">
547
-
548
- {loading && <CircularProgressCenter/>}
549
-
550
- {!globalLoading &&
551
- (usedEntity && fullPath
552
- ? <EntityCollectionView
553
- fullPath={fullPath}
554
- parentCollectionIds={[...parentCollectionIds, collection.id]}
555
- isSubCollection={true}
556
- {...subcollection}/>
557
- : <div
558
- className="flex items-center justify-center w-full h-full p-3">
559
- <Typography variant={"label"}>
560
- You need to save your entity before
561
- adding additional collections
562
- </Typography>
563
- </div>)
564
- }
565
-
566
- </div>
567
- );
568
- }
569
- ).filter(Boolean);
570
-
571
- const onDiscard = useCallback(() => {
572
- onValuesAreModified(false);
573
- }, []);
574
-
575
- const onSideTabClick = (value: string) => {
576
- selectedTabRef.current = value;
577
- sideEntityController.replace({
578
- path,
579
- entityId,
580
- selectedSubPath: value === MAIN_TAB_VALUE ? undefined : value,
581
- updateUrl: true,
582
- collection,
583
- });
584
- };
585
-
586
- const onIdUpdateError = useCallback((error: any) => {
587
- snackbarController.open({
588
- type: "error",
589
- message: "Error updating id, check the console"
590
- });
591
- }, []);
173
+ const [formContext, setFormContext] = useState<FormContext<M> | undefined>(undefined);
592
174
 
593
- const onIdChange = useCallback((id: string) => {
594
- setUsedEntity((prevEntity) => prevEntity
595
- ? {
596
- ...prevEntity,
597
- id
598
- }
599
- : undefined);
600
- }, []);
601
-
602
- // useEffect(() => {
603
- // baseDataSourceValuesRef.current = getDataSourceEntityValues(initialResolvedCollection, status, entity);
604
- // const initialValues = formex.initialValues;
605
- // if (!formex.isSubmitting && initialValues && status === "existing") {
606
- // setUnderlyingChanges(
607
- // Object.entries(resolvedCollection.properties)
608
- // .map(([key, property]) => {
609
- // if (isHidden(property)) {
610
- // return {};
611
- // }
612
- // const initialValue = initialValues[key];
613
- // const latestValue = baseDataSourceValuesRef.current[key];
614
- // if (!equal(initialValue, latestValue)) {
615
- // return { [key]: latestValue };
616
- // }
617
- // return {};
618
- // })
619
- // .reduce((a, b) => ({ ...a, ...b }), {}) as Partial<EntityValues<M>>
620
- // );
621
- // } else {
622
- // setUnderlyingChanges({});
623
- // }
624
- // }, [entity, initialResolvedCollection, status]);
625
-
626
- const pluginActions: React.ReactNode[] = [];
175
+ const largeLayout = useLargeLayout();
627
176
 
177
+ const customizationController = useCustomizationController();
628
178
  const plugins = customizationController.plugins;
179
+ const pluginActionsTop: React.ReactNode[] = [];
629
180
 
630
- if (plugins && inputCollection) {
181
+ if (plugins && collection) {
631
182
  const actionProps: PluginFormActionProps = {
632
183
  entityId,
184
+ parentCollectionIds,
633
185
  path,
634
186
  status,
635
- collection: inputCollection,
187
+ collection,
636
188
  context,
637
- currentEntityId: entityId,
638
- formContext
189
+ formContext,
190
+ openEntityMode: layout,
191
+ disabled: false
639
192
  };
640
- pluginActions.push(...plugins.map((plugin, i) => (
641
- plugin.form?.Actions
642
- ? <plugin.form.Actions
643
- key={`actions_${plugin.key}`} {...actionProps}/>
193
+ pluginActionsTop.push(...plugins.map((plugin) => (
194
+ plugin.form?.ActionsTop
195
+ ? <plugin.form.ActionsTop
196
+ key={`actions_${plugin.key}`} {...actionProps} />
644
197
  : null
645
198
  )).filter(Boolean));
646
199
  }
647
200
 
648
- const titlePropertyKey = getEntityTitlePropertyKey(resolvedCollection, customizationController.propertyConfigs);
649
- const title = formex.values && titlePropertyKey ? getValueInPath(formex.values, titlePropertyKey) : undefined;
650
-
651
- const onIdUpdate = inputCollection.callbacks?.onIdUpdate;
652
-
653
- const doOnIdUpdate = useCallback(async () => {
654
- if (onIdUpdate && formex.values && (status === "new" || status === "copy")) {
655
- setCustomIdLoading(true);
656
- try {
657
- const updatedId = await onIdUpdate({
658
- collection: resolvedCollection,
659
- path,
660
- entityId,
661
- values: formex.values,
662
- context
663
- });
664
- setEntityId(updatedId);
665
- } catch (e) {
666
- onIdUpdateError && onIdUpdateError(e);
667
- console.error(e);
668
- }
669
- setCustomIdLoading(false);
670
- }
671
- }, [entityId, formex.values, status]);
672
-
673
- useEffect(() => {
674
- doOnIdUpdate();
675
- }, [doOnIdUpdate]);
676
-
677
- const [underlyingChanges, setUnderlyingChanges] = useState<Partial<EntityValues<M>>>({});
678
-
679
- const uniqueFieldValidator: CustomFieldValidator = useCallback(({
680
- name,
681
- value,
682
- property
683
- }) => dataSource.checkUniqueField(path, name, value, entityId),
684
- [dataSource, path, entityId]);
685
-
686
- const validationSchema = useMemo(() => entityId
687
- ? getYupEntitySchema(
688
- entityId,
689
- resolvedCollection.properties,
690
- uniqueFieldValidator)
691
- : undefined,
692
- [entityId, resolvedCollection.properties, uniqueFieldValidator]);
693
-
694
- const getActionsForEntity = useCallback(({
695
- entity,
696
- customEntityActions
697
- }: {
698
- entity?: Entity<M>,
699
- customEntityActions?: EntityAction[]
700
- }): EntityAction[] => {
701
- const createEnabled = canCreateEntity(inputCollection, authController, path, null);
702
- const deleteEnabled = entity ? canDeleteEntity(inputCollection, authController, path, entity) : true;
703
- const actions: EntityAction[] = [];
704
- if (createEnabled)
705
- actions.push(copyEntityAction);
706
- if (deleteEnabled)
707
- actions.push(deleteEntityAction);
708
- if (customEntityActions)
709
- actions.push(...customEntityActions);
710
- return actions;
711
- }, [authController, inputCollection, path]);
712
-
713
- const modified = formex.dirty;
714
- useEffect(() => {
715
- if (!autoSave) {
716
- onValuesAreModified(modified);
717
- } else {
718
- if (formex.values && !equal(formex.values, lastSavedValues.current)) {
719
- save(formex.values);
720
- }
201
+ const defaultSelectedView = useMemo(() => resolveDefaultSelectedView(
202
+ collection ? collection.defaultSelectedView : undefined,
203
+ {
204
+ status,
205
+ entityId
721
206
  }
722
- }, [modified, formex.values]);
207
+ ), []);
723
208
 
209
+ const [selectedTab, setSelectedTab] = useState<string>(selectedTabProp ?? defaultSelectedView ?? MAIN_TAB_VALUE);
724
210
  useEffect(() => {
725
- if (!autoSave && !formex.isSubmitting && underlyingChanges && entity) {
726
- // we update the form fields from the Firestore data
727
- // if they were not touched
728
- Object.entries(underlyingChanges).forEach(([key, value]) => {
729
- const formValue = formex.values[key];
730
- if (!equal(value, formValue) && !formex.touched[key]) {
731
- console.debug("Updated value from the datasource:", key, value);
732
- formex.setFieldValue(key, value !== undefined ? value : null);
733
- }
734
- });
211
+ if ((selectedTabProp ?? defaultSelectedView ?? MAIN_TAB_VALUE) !== selectedTab) {
212
+ setSelectedTab(selectedTabProp ?? defaultSelectedView ?? MAIN_TAB_VALUE);
735
213
  }
736
- }, [formex.isSubmitting, autoSave, underlyingChanges, entity, formex.values, formex.touched, formex.setFieldValue]);
737
-
738
- const formFields = (
739
- <>
740
- {(getFormFieldKeys(resolvedCollection))
741
- .map((key) => {
742
-
743
- const property = resolvedCollection.properties[key];
744
- if (property) {
745
-
746
- const underlyingValueHasChanged: boolean =
747
- !!underlyingChanges &&
748
- Object.keys(underlyingChanges).includes(key) &&
749
- formex.touched[key];
750
-
751
- const disabled = (!autoSave && formex.isSubmitting) || isReadOnly(property) || Boolean(property.disabled);
752
- const hidden = isHidden(property);
753
- if (hidden) return null;
754
- const cmsFormFieldProps: PropertyFieldBindingProps<any, M> = {
755
- propertyKey: key,
756
- disabled,
757
- property,
758
- includeDescription: property.description || property.longDescription,
759
- underlyingValueHasChanged: underlyingValueHasChanged && !autoSave,
760
- context: formContext,
761
- tableMode: false,
762
- partOfArray: false,
763
- partOfBlock: false,
764
- autoFocus: false
765
- };
766
-
767
- return (
768
- <div id={`form_field_${key}`}
769
- key={`field_${resolvedCollection.name}_${key}`}>
770
- <ErrorBoundary>
771
- <Tooltip title={<PropertyIdCopyTooltipContent propertyId={key}/>}
772
- delayDuration={800}
773
- side={"left"}
774
- align={"start"}
775
- sideOffset={16}>
776
- <PropertyFieldBinding {...cmsFormFieldProps}/>
777
- </Tooltip>
778
- </ErrorBoundary>
779
- </div>
780
- );
781
- }
782
-
783
- const additionalField = resolvedCollection.additionalFields?.find(f => f.key === key);
784
- if (additionalField && entity) {
785
- const Builder = additionalField.Builder;
786
- if (!Builder && !additionalField.value) {
787
- throw new Error("When using additional fields you need to provide a Builder or a value");
788
- }
789
-
790
- const child = Builder
791
- ? <Builder entity={entity} context={context}/>
792
- : <>{additionalField.value?.({
793
- entity,
794
- context
795
- })}</>;
796
- return (
797
- <div>
798
- <LabelWithIcon icon={<NotesIcon size={"small"}/>}
799
- title={additionalField.name}
800
- className={"text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>
801
- <div
802
- className={cls(paperMixin, "min-h-14 p-4 md:p-6 overflow-x-scroll no-scrollbar")}>
803
-
804
- <ErrorBoundary>
805
- {child}
806
- </ErrorBoundary>
807
-
808
- </div>
809
- </div>
810
- );
811
- }
812
-
813
- console.warn(`Property ${key} not found in collection ${resolvedCollection.name} in properties or additional fields. Skipping.`);
814
- return null;
815
- })
816
- .filter(Boolean)}
817
-
818
- </>
819
- );
820
-
821
- const disabled = formex.isSubmitting || (!modified && status === "existing");
822
- const formRef = React.useRef<HTMLDivElement>(null);
823
-
824
- const entityActions = getActionsForEntity({
825
- entity,
826
- customEntityActions: inputCollection.entityActions
827
- });
828
- const formActions = entityActions.filter(a => a.includeInForm === undefined || a.includeInForm);
829
-
830
- const dialogActions = <DialogActions position={"absolute"}>
214
+ }, [selectedTabProp]);
831
215
 
832
- {savingError &&
833
- <div className="text-right">
834
- <Typography color={"error"}>
835
- {savingError.message}
836
- </Typography>
837
- </div>}
216
+ const subcollections = (collection.subcollections ?? []).filter(c => !c.hideFromNavigation);
217
+ const subcollectionsCount = subcollections?.length ?? 0;
218
+ const customViews = collection.entityViews ?? [];
219
+ const customViewsCount = customViews?.length ?? 0;
220
+ const includeJsonView = collection.includeJsonView === undefined ? true : collection.includeJsonView;
221
+ const hasAdditionalViews = customViewsCount > 0 || subcollectionsCount > 0 || includeJsonView;
838
222
 
839
- {entity && formActions.length > 0 && <div className="flex-grow flex overflow-auto no-scrollbar">
840
- {formActions.map(action => (
841
- <IconButton
842
- key={action.name}
843
- color="primary"
844
- onClick={(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
845
- event.stopPropagation();
846
- if (entity)
847
- action.onClick({
848
- entity,
849
- fullPath: resolvedCollection.path,
850
- collection: resolvedCollection,
851
- context,
852
- sideEntityController
853
- });
854
- }}>
855
- {action.icon}
856
- </IconButton>
857
- ))}
858
- </div>}
859
- {formex.isSubmitting && <CircularProgress size={"small"}/>}
860
- <Button
861
- variant="text"
862
- disabled={disabled || formex.isSubmitting}
863
- type="reset">
864
- {status === "existing" ? "Discard" : "Clear"}
865
- </Button>
866
-
867
- <Button
868
- variant="text"
869
- color="primary"
870
- type="submit"
871
- disabled={disabled || formex.isSubmitting}
872
- onClick={() => {
873
- closeAfterSaveRef.current = false;
874
- }}>
875
- {status === "existing" && "Save"}
876
- {status === "copy" && "Create copy"}
877
- {status === "new" && "Create"}
878
- </Button>
879
-
880
- <Button
881
- variant="filled"
882
- color="primary"
883
- type="submit"
884
- disabled={disabled || formex.isSubmitting}
885
- onClick={() => {
886
- closeAfterSaveRef.current = true;
887
- }}>
888
- {status === "existing" && "Save and close"}
889
- {status === "copy" && "Create copy and close"}
890
- {status === "new" && "Create and close"}
891
- </Button>
892
-
893
- </DialogActions>;
894
-
895
- function buildForm() {
896
-
897
- let form = <div className="h-full overflow-auto">
898
-
899
- {pluginActions.length > 0 && <div
900
- className={cls("w-full flex justify-end items-center sticky top-0 right-0 left-0 z-10 bg-opacity-60 bg-slate-200 dark:bg-opacity-60 dark:bg-slate-800 backdrop-blur-md")}>
901
- {pluginActions}
902
- </div>}
223
+ const {
224
+ resolvedEntityViews,
225
+ selectedEntityView,
226
+ selectedSecondaryForm
227
+ } = resolvedSelectedEntityView(customViews, customizationController, selectedTab, canEdit);
903
228
 
904
- <div className="pt-12 pb-16 pl-8 pr-8 md:pl-10 md:pr-10">
905
- <div
906
- className={`w-full py-2 flex flex-col items-start mt-${4 + (pluginActions ? 8 : 0)} lg:mt-${8 + (pluginActions ? 8 : 0)} mb-8`}>
907
-
908
- <Typography
909
- className={"mt-4 flex-grow line-clamp-1 " + inputCollection.hideIdFromForm ? "mb-2" : "mb-0"}
910
- variant={"h4"}>{title ?? inputCollection.singularName ?? inputCollection.name}
911
- </Typography>
912
- <Alert color={"base"} className={"w-full"} size={"small"}>
913
- <code className={"text-xs select-all"}>{path}/{entityId}</code>
914
- </Alert>
915
- </div>
916
-
917
- {!collection.hideIdFromForm &&
918
- <CustomIdField customId={inputCollection.customId}
919
- entityId={entityId}
920
- status={status}
921
- onChange={setEntityId}
922
- error={entityIdError}
923
- loading={customIdLoading}
924
- entity={entity}/>}
229
+ const actionsAtTheBottom = !largeLayout || layout === "side_panel" || selectedEntityView?.includeActions === "bottom";
925
230
 
926
- {entityId && formContext && <>
927
- <div className="mt-12 flex flex-col gap-8"
928
- ref={formRef}>
231
+ const mainViewVisible = selectedTab === MAIN_TAB_VALUE || Boolean(selectedSecondaryForm);
929
232
 
930
- {formFields}
233
+ const authController = useAuthController();
931
234
 
932
- <ErrorFocus containerRef={formRef}/>
235
+ const customViewsView: React.ReactNode[] | undefined = customViews && resolvedEntityViews
236
+ .filter(e => !e.includeActions)
237
+ .map((customView) => {
933
238
 
934
- </div>
239
+ if (!customView)
240
+ return null;
241
+ const Builder = customView.Builder;
242
+ if (!Builder) {
243
+ console.error("INTERNAL: customView.Builder is not defined");
244
+ return null;
245
+ }
935
246
 
936
- <div className="h-14"/>
247
+ if (!entityId) {
248
+ return null;
249
+ }
937
250
 
938
- </>}
251
+ const formexStub = createFormexStub<M>(usedEntity?.values ?? {} as M);
252
+ const usedFormContext: FormContext = formContext ?? {
253
+ entityId,
254
+ disabled: false,
255
+ openEntityMode: layout,
256
+ status: status,
257
+ values: usedEntity?.values ?? {},
258
+ setFieldValue: (key: string, value: any) => {
259
+ throw new Error("You can't update values in read only mode");
260
+ },
261
+ save: () => {
262
+ throw new Error("You can't save in read only mode");
263
+ },
264
+ collection: resolveCollection<M>({
265
+ collection,
266
+ path,
267
+ entityId,
268
+ values: usedEntity?.values ?? {},
269
+ previousValues: usedEntity?.values ?? {},
270
+ propertyConfigs: customizationController.propertyConfigs,
271
+ authController
272
+ }),
273
+ path,
274
+ entity: usedEntity,
275
+ savingError: undefined,
276
+ formex: formexStub
277
+ };
278
+
279
+ return <div
280
+ className={cls(defaultBorderMixin,
281
+ "relative flex-1 w-full h-full overflow-auto",
282
+ { "hidden": selectedTab !== customView.key }
283
+ )}
284
+ key={`custom_view_${customView.key}`}
285
+ role="tabpanel">
286
+ <ErrorBoundary>
287
+ {usedFormContext && <Builder
288
+ collection={collection}
289
+ parentCollectionIds={parentCollectionIds}
290
+ entity={usedEntity}
291
+ modifiedValues={usedFormContext?.formex?.values ?? usedEntity?.values}
292
+ formContext={usedFormContext}
293
+ />}
294
+ </ErrorBoundary>
295
+ </div>;
296
+ }).filter(Boolean);
297
+
298
+ const globalLoading = dataLoading && !usedEntity;
299
+
300
+ const jsonView = <div
301
+ className={cls("relative flex-1 h-full overflow-auto w-full",
302
+ { "hidden": selectedTab !== JSON_TAB_VALUE })}
303
+ key={"json_view"}
304
+ role="tabpanel">
305
+ <ErrorBoundary>
306
+ <EntityJsonPreview
307
+ values={formContext?.values ?? entity?.values ?? {}}/>
308
+ </ErrorBoundary>
309
+ </div>;
310
+
311
+ const subCollectionsViews = subcollections && subcollections.map((subcollection) => {
312
+ const subcollectionId = subcollection.id ?? subcollection.path;
313
+ const newFullPath = usedEntity ? `${path}/${usedEntity?.id}/${removeInitialAndTrailingSlashes(subcollection.path)}` : undefined;
314
+ const newFullIdPath = fullIdPath ? `${fullIdPath}/${usedEntity?.id}/${removeInitialAndTrailingSlashes(subcollectionId)}` : undefined;
315
+
316
+ if (selectedTab !== subcollectionId) return null;
317
+ return (
318
+ <div
319
+ className={"relative flex-1 h-full overflow-auto w-full"}
320
+ key={`subcol_${subcollectionId}`}
321
+ role="tabpanel">
322
+
323
+ {globalLoading && <CircularProgressCenter/>}
324
+
325
+ {!globalLoading &&
326
+ (usedEntity && newFullPath
327
+ ? <EntityCollectionView
328
+ fullPath={newFullPath}
329
+ fullIdPath={newFullIdPath}
330
+ parentCollectionIds={[...parentCollectionIds, collection.id]}
331
+ isSubCollection={true}
332
+ updateUrl={false}
333
+ {...subcollection}
334
+ openEntityMode={layout}/>
335
+ : <div className="flex items-center justify-center w-full h-full p-3">
336
+ <Typography variant={"label"}>
337
+ You need to save your entity before
338
+ adding additional collections
339
+ </Typography>
340
+ </div>)
341
+ }
939
342
 
940
343
  </div>
941
- </div>;
942
-
943
- if (plugins) {
944
- plugins.forEach((plugin: FireCMSPlugin) => {
945
- if (plugin.form?.provider) {
946
- form = (
947
- <plugin.form.provider.Component
948
- status={status}
949
- path={path}
950
- collection={collection}
951
- onDiscard={onDiscard}
952
- entity={usedEntity}
953
- context={context}
954
- formContext={formContext}
955
- {...plugin.form.provider.props}>
956
- {form}
957
- </plugin.form.provider.Component>
958
- );
959
- }
344
+ );
345
+ }).filter(Boolean);
346
+
347
+ const onSideTabClick = (value: string) => {
348
+ setSelectedTab(value);
349
+ if (status === "existing") {
350
+ onTabChange?.({
351
+ path: fullIdPath ?? path,
352
+ entityId,
353
+ selectedTab: value === MAIN_TAB_VALUE ? undefined : value,
354
+ collection
960
355
  });
961
356
  }
962
- return <ErrorBoundary>{form}</ErrorBoundary>;
963
- }
964
-
965
- const entityView = (readOnly === undefined)
966
- ? <></>
967
- : (!readOnly
968
- ? buildForm()
969
- : (
970
- <>
971
- <Typography
972
- className={"mt-16 mb-8 mx-8"}
973
- variant={"h4"}>{collection.singularName ?? collection.name}
974
- </Typography>
975
- <EntityView
976
- className={"px-12"}
977
- entity={usedEntity as Entity<M>}
978
- path={path}
979
- collection={collection}/>
357
+ };
980
358
 
981
- </>
982
- ));
359
+ const entityReadOnlyView = !canEdit && entity ? <div
360
+ className={cls("flex-1 flex flex-row w-full overflow-y-auto justify-center", (canEdit || !mainViewVisible || selectedSecondaryForm) ? "hidden" : "")}>
361
+ <div
362
+ className={cls("relative flex flex-col max-w-4xl lg:max-w-3xl xl:max-w-4xl 2xl:max-w-6xl w-full h-fit")}>
363
+ <Typography className={"mt-16 mb-8 mx-8"} variant={"h4"}>
364
+ {collection.singularName ?? collection.name}
365
+ </Typography>
366
+ <EntityView
367
+ className={"px-8 h-full overflow-auto"}
368
+ entity={entity}
369
+ path={path}
370
+ collection={collection}/>
371
+ <div className="h-16"/>
372
+ </div>
373
+ </div> : null;
374
+
375
+ const entityView = <EntityForm<M>
376
+ fullIdPath={fullIdPath}
377
+ collection={collection}
378
+ path={path}
379
+ entityId={entityId ?? usedEntity?.id}
380
+ onValuesModified={onValuesModified}
381
+ entity={entity}
382
+ initialDirtyValues={cachedDirtyValues}
383
+ openEntityMode={layout}
384
+ forceActionsAtTheBottom={actionsAtTheBottom}
385
+ initialStatus={status}
386
+ className={cls((!mainViewVisible || !canEdit) && !selectedSecondaryForm ? "hidden" : "", formProps?.className)}
387
+ EntityFormActionsComponent={EntityEditViewFormActions}
388
+ disabled={!canEdit}
389
+ {...formProps}
390
+ onEntityChange={(entity) => {
391
+ setUsedEntity(entity);
392
+ formProps?.onEntityChange?.(entity);
393
+ }}
394
+ onStatusChange={(status) => {
395
+ setStatus(status);
396
+ formProps?.onStatusChange?.(status);
397
+ }}
398
+ onFormContextReady={(formContext) => {
399
+ setFormContext(formContext);
400
+ formProps?.onFormContextReady?.(formContext);
401
+ }}
402
+ onSaved={(params) => {
403
+ const res = {
404
+ ...params,
405
+ selectedTab: MAIN_TAB_VALUE === selectedTab ? undefined : selectedTab
406
+ };
407
+ onSaved?.(res);
408
+ formProps?.onSaved?.(res);
409
+ }}
410
+ Builder={selectedSecondaryForm?.Builder}
411
+ />;
412
+
413
+ const subcollectionTabs = subcollections && subcollections.map((subcollection) =>
414
+ <Tab
415
+ className="text-sm min-w-[120px]"
416
+ value={subcollection.id}
417
+ key={`entity_detail_collection_tab_${subcollection.name}`}>
418
+ {subcollection.name}
419
+ </Tab>
420
+ );
983
421
 
984
- const subcollectionTabs = subcollections && subcollections.map(
985
- (subcollection) =>
422
+ const customViewTabsStart = resolvedEntityViews.filter(view => view.position === "start")
423
+ .map((view) =>
986
424
  <Tab
987
- className="text-sm min-w-[140px]"
988
- value={subcollection.id}
989
- key={`entity_detail_collection_tab_${subcollection.name}`}>
990
- {subcollection.name}
425
+ className={!view.tabComponent ? "text-sm min-w-[120px]" : undefined}
426
+ value={view.key}
427
+ key={`entity_detail_collection_tab_${view.name}`}>
428
+ {view.tabComponent ?? view.name}
991
429
  </Tab>
992
- );
993
-
994
- const customViewTabs = resolvedEntityViews.map(
995
- (view) =>
996
-
430
+ );
431
+ const customViewTabsEnd = resolvedEntityViews.filter(view => !view.position || view.position === "end")
432
+ .map((view) =>
997
433
  <Tab
998
- className="text-sm min-w-[140px]"
434
+ className={!view.tabComponent ? "text-sm min-w-[120px]" : undefined}
999
435
  value={view.key}
1000
436
  key={`entity_detail_collection_tab_${view.name}`}>
1001
- {view.name}
437
+ {view.tabComponent ?? view.name}
1002
438
  </Tab>
1003
- );
439
+ );
1004
440
 
1005
- useEffect(() => {
1006
- if (entityId && onIdChange)
1007
- onIdChange(entityId);
1008
- }, [entityId, onIdChange]);
1009
-
1010
- return (
1011
- <Formex value={formex}>
441
+ const shouldShowTopBar = Boolean(barActions) || hasAdditionalViews;
1012
442
 
1013
- <div className="flex flex-col h-full w-full transition-width duration-250 ease-in-out">
443
+ let result = <div className="relative flex flex-col h-full w-full bg-white dark:bg-surface-900">
1014
444
 
1015
- <div
1016
- className={cls(defaultBorderMixin, "no-scrollbar border-b pl-2 pr-2 pt-1 flex items-end overflow-scroll bg-gray-50 dark:bg-gray-950")}>
445
+ {shouldShowTopBar && <div
446
+ className={cls("h-14 items-center flex overflow-visible overflow-x-scroll w-full no-scrollbar h-14 border-b pl-2 pr-2 pt-1 flex bg-surface-50 dark:bg-surface-900", defaultBorderMixin)}>
1017
447
 
1018
- <div
1019
- className="pb-1 self-center">
1020
- <IconButton
1021
- onClick={() => {
1022
- onClose?.();
1023
- return sideDialogContext.close(false);
1024
- }}>
1025
- <CloseIcon size={"small"}/>
1026
- </IconButton>
1027
- </div>
448
+ {barActions}
1028
449
 
1029
- <div className={"flex-grow"}/>
450
+ <div className={"flex-grow"}/>
1030
451
 
1031
- {globalLoading && <div
1032
- className="self-center">
1033
- <CircularProgress size={"small"}/>
1034
- </div>}
452
+ {pluginActionsTop}
1035
453
 
1036
- <Tabs
1037
- value={selectedTabRef.current}
1038
- onValueChange={(value) => {
1039
- onSideTabClick(value);
1040
- }}
1041
- className="pl-4 pr-4 pt-0">
454
+ {globalLoading && <div className="self-center">
455
+ <CircularProgress size={"small"}/>
456
+ </div>}
1042
457
 
1043
- <Tab
1044
- disabled={!hasAdditionalViews}
1045
- value={MAIN_TAB_VALUE}
1046
- className={`${
1047
- !hasAdditionalViews ? "hidden" : ""
1048
- } text-sm min-w-[140px]`}
1049
- >{collection.singularName ?? collection.name}</Tab>
458
+ {hasAdditionalViews && <Tabs
459
+ className={"self-end"}
460
+ value={selectedTab}
461
+ onValueChange={(value) => {
462
+ onSideTabClick(value);
463
+ }}>
1050
464
 
1051
- {customViewTabs}
465
+ {includeJsonView && <Tab
466
+ disabled={!hasAdditionalViews}
467
+ value={JSON_TAB_VALUE}
468
+ className={"text-sm"}>
469
+ <CodeIcon size={"small"}/>
470
+ </Tab>}
1052
471
 
1053
- {subcollectionTabs}
1054
- </Tabs>
472
+ {customViewTabsStart}
1055
473
 
1056
- </div>
474
+ <Tab
475
+ disabled={!hasAdditionalViews}
476
+ value={MAIN_TAB_VALUE}
477
+ className={"text-sm min-w-[120px]"}>
478
+ {collection.singularName ?? collection.name}
479
+ </Tab>
1057
480
 
1058
- <form
1059
- onSubmit={formex.handleSubmit}
1060
- onReset={() => {
1061
- formex.resetForm();
1062
- return onDiscard && onDiscard();
1063
- }}
1064
- noValidate
1065
- className={"flex-grow h-full flex overflow-auto flex-col w-full"}>
1066
481
 
1067
- <div
1068
- role="tabpanel"
1069
- hidden={!mainViewVisible}
1070
- id={`form_${path}`}
1071
- className={" w-full"}>
482
+ {customViewTabsEnd}
1072
483
 
1073
- {globalLoading
1074
- ? <CircularProgressCenter/>
1075
- : entityView}
484
+ {subcollectionTabs}
485
+ </Tabs>}
486
+ </div>}
1076
487
 
1077
- </div>
488
+ {globalLoading
489
+ ? <div className="w-full pt-12 pb-16 px-4 sm:px-8 md:px-10">
490
+ <CircularProgressCenter/>
491
+ </div>
492
+ : <>
493
+ {entityReadOnlyView}
494
+ {entityView}
495
+ </>}
1078
496
 
1079
- {customViewsView}
497
+ {jsonView}
1080
498
 
1081
- {subCollectionsViews}
499
+ {customViewsView}
1082
500
 
1083
- {shouldShowEntityActions && dialogActions}
501
+ {subCollectionsViews}
1084
502
 
1085
- </form>
503
+ </div>;
1086
504
 
1087
- </div>
1088
- </Formex>
1089
- );
1090
- }
1091
-
1092
- function getDataSourceEntityValues<M extends object>(initialResolvedCollection: ResolvedEntityCollection,
1093
- status: "new" | "existing" | "copy",
1094
- entity: Entity<M> | undefined): Partial<EntityValues<M>> {
1095
-
1096
- const properties = initialResolvedCollection.properties;
1097
- if ((status === "existing" || status === "copy") && entity) {
1098
- return entity.values ?? getDefaultValuesFor(properties);
1099
- } else if (status === "new") {
1100
- return getDefaultValuesFor(properties);
1101
- } else {
1102
- console.error({
1103
- status,
1104
- entity
505
+ if (plugins) {
506
+ plugins.forEach((plugin: FireCMSPlugin) => {
507
+ if (plugin.form?.provider) {
508
+ result = (
509
+ <plugin.form.provider.Component
510
+ status={status}
511
+ path={path}
512
+ collection={collection}
513
+ entity={usedEntity}
514
+ context={context}
515
+ formContext={formContext}
516
+ {...plugin.form.provider.props}>
517
+ {result}
518
+ </plugin.form.provider.Component>
519
+ );
520
+ }
1105
521
  });
1106
- throw new Error("Form has not been initialised with the correct parameters");
1107
522
  }
1108
- }
1109
-
1110
- export type EntityFormSaveParams<M extends Record<string, any>> = {
1111
- collection: ResolvedEntityCollection<M>,
1112
- path: string,
1113
- entityId: string | undefined,
1114
- values: EntityValues<M>,
1115
- previousValues?: EntityValues<M>,
1116
- closeAfterSave: boolean,
1117
- autoSave: boolean
1118
- };
1119
523
 
1120
- export function yupToFormErrors(yupError: ValidationError): Record<string, any> {
1121
- let errors: Record<string, any> = {};
1122
- if (yupError.inner) {
1123
- if (yupError.inner.length === 0) {
1124
- return setIn(errors, yupError.path!, yupError.message);
1125
- }
1126
- for (const err of yupError.inner) {
1127
- if (!getIn(errors, err.path!)) {
1128
- errors = setIn(errors, err.path!, err.message);
1129
- }
1130
- }
1131
- }
1132
- return errors;
524
+ return result;
1133
525
  }
526
+