@firecms/core 3.0.0-canary.2 → 3.0.0-canary.200

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 (409) hide show
  1. package/README.md +4 -4
  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 +30 -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 +23 -6
  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 +13 -13
  12. package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +3 -3
  13. package/dist/components/EntityCollectionTable/EntityCollectionTableProps.d.ts +22 -6
  14. package/dist/components/EntityCollectionTable/PropertyTableCell.d.ts +3 -2
  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 +17 -4
  22. package/dist/components/EntityCollectionView/EntityCollectionViewStartActions.d.ts +11 -0
  23. package/dist/components/EntityCollectionView/useSelectionController.d.ts +2 -0
  24. package/dist/components/EntityCollectionView/utils.d.ts +3 -0
  25. package/dist/components/EntityPreview.d.ts +26 -7
  26. package/dist/components/EntityView.d.ts +11 -0
  27. package/dist/components/ErrorView.d.ts +1 -1
  28. package/dist/components/FieldCaption.d.ts +5 -0
  29. package/dist/components/HomePage/DefaultHomePage.d.ts +1 -1
  30. package/dist/components/HomePage/NavigationCard.d.ts +8 -0
  31. package/dist/components/HomePage/{NavigationCollectionCard.d.ts → NavigationCardBinding.d.ts} +3 -3
  32. package/dist/components/HomePage/SmallNavigationCard.d.ts +6 -0
  33. package/dist/components/HomePage/index.d.ts +3 -1
  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 +6 -4
  37. package/dist/components/SelectableTable/SelectableTable.d.ts +13 -3
  38. package/dist/components/SelectableTable/filters/ReferenceFilterField.d.ts +2 -1
  39. package/dist/components/UnsavedChangesDialog.d.ts +8 -0
  40. package/dist/components/VirtualTable/VirtualTableProps.d.ts +29 -13
  41. package/dist/components/VirtualTable/types.d.ts +3 -3
  42. package/dist/components/{EntityCollectionTable/internal → common}/default_entity_actions.d.ts +1 -3
  43. package/dist/components/common/index.d.ts +2 -1
  44. package/dist/components/common/table_height.d.ts +5 -0
  45. package/dist/components/common/types.d.ts +4 -6
  46. package/dist/components/common/useColumnsIds.d.ts +3 -1
  47. package/dist/components/common/{useDataSourceEntityCollectionTableController.d.ts → useDataSourceTableController.d.ts} +13 -2
  48. package/dist/components/common/useDebouncedCallback.d.ts +1 -0
  49. package/dist/components/common/useScrollRestoration.d.ts +14 -0
  50. package/dist/components/index.d.ts +8 -5
  51. package/dist/contexts/AuthControllerContext.d.ts +1 -1
  52. package/dist/contexts/BreacrumbsContext.d.ts +8 -0
  53. package/dist/{components/FireCMSAppBar.d.ts → core/DefaultAppBar.d.ts} +6 -9
  54. package/dist/core/DefaultDrawer.d.ts +19 -0
  55. package/dist/core/DrawerNavigationItem.d.ts +10 -0
  56. package/dist/core/EntityEditView.d.ts +44 -0
  57. package/dist/core/EntityEditViewFormActions.d.ts +2 -0
  58. package/dist/core/FireCMS.d.ts +2 -2
  59. package/dist/core/FireCMSRouter.d.ts +4 -0
  60. package/dist/core/NavigationRoutes.d.ts +2 -3
  61. package/dist/core/SideDialogs.d.ts +4 -2
  62. package/dist/core/index.d.ts +4 -6
  63. package/dist/form/EntityForm.d.ts +45 -69
  64. package/dist/form/EntityFormActions.d.ts +17 -0
  65. package/dist/form/PropertyFieldBinding.d.ts +1 -1
  66. package/dist/form/components/ErrorFocus.d.ts +1 -1
  67. package/dist/form/components/FieldHelperText.d.ts +3 -3
  68. package/dist/form/components/FormEntry.d.ts +6 -0
  69. package/dist/form/components/FormLayout.d.ts +5 -0
  70. package/dist/{components → form/components}/LabelWithIcon.d.ts +1 -1
  71. package/dist/form/components/LabelWithIconAndTooltip.d.ts +15 -0
  72. package/dist/form/components/StorageItemPreview.d.ts +4 -3
  73. package/dist/form/components/StorageUploadProgress.d.ts +1 -1
  74. package/dist/form/components/index.d.ts +4 -1
  75. package/dist/form/field_bindings/ArrayCustomShapedFieldBinding.d.ts +1 -1
  76. package/dist/form/field_bindings/ArrayOfReferencesFieldBinding.d.ts +1 -1
  77. package/dist/form/field_bindings/BlockFieldBinding.d.ts +1 -1
  78. package/dist/form/field_bindings/KeyValueFieldBinding.d.ts +1 -1
  79. package/dist/form/field_bindings/MapFieldBinding.d.ts +1 -1
  80. package/dist/form/field_bindings/MarkdownEditorFieldBinding.d.ts +11 -0
  81. package/dist/form/field_bindings/{MultiSelectBinding.d.ts → MultiSelectFieldBinding.d.ts} +1 -1
  82. package/dist/form/field_bindings/ReadOnlyFieldBinding.d.ts +1 -1
  83. package/dist/form/field_bindings/ReferenceFieldBinding.d.ts +2 -2
  84. package/dist/form/field_bindings/RepeatFieldBinding.d.ts +1 -1
  85. package/dist/form/field_bindings/SelectFieldBinding.d.ts +1 -1
  86. package/dist/form/field_bindings/StorageUploadFieldBinding.d.ts +3 -4
  87. package/dist/form/field_bindings/SwitchFieldBinding.d.ts +1 -2
  88. package/dist/form/field_bindings/TextFieldBinding.d.ts +2 -2
  89. package/dist/form/index.d.ts +17 -18
  90. package/dist/form/useClearRestoreValue.d.ts +2 -2
  91. package/dist/form/validation.d.ts +1 -1
  92. package/dist/hooks/data/delete.d.ts +4 -4
  93. package/dist/hooks/data/save.d.ts +4 -5
  94. package/dist/hooks/data/useCollectionFetch.d.ts +1 -1
  95. package/dist/hooks/data/useDataSource.d.ts +2 -2
  96. package/dist/hooks/data/useEntityFetch.d.ts +4 -3
  97. package/dist/hooks/index.d.ts +4 -1
  98. package/dist/hooks/useAuthController.d.ts +1 -1
  99. package/dist/hooks/useBreadcrumbsController.d.ts +26 -0
  100. package/dist/{core → hooks}/useBuildModeController.d.ts +1 -1
  101. package/dist/hooks/useBuildNavigationController.d.ts +11 -6
  102. package/dist/hooks/useFireCMSContext.d.ts +1 -1
  103. package/dist/hooks/useModeController.d.ts +1 -2
  104. package/dist/hooks/useProjectLog.d.ts +12 -2
  105. package/dist/hooks/useResolvedNavigationFrom.d.ts +3 -3
  106. package/dist/hooks/useStorageSource.d.ts +2 -2
  107. package/dist/hooks/useValidateAuthenticator.d.ts +21 -0
  108. package/dist/index.d.ts +1 -0
  109. package/dist/index.es.js +22131 -14039
  110. package/dist/index.es.js.map +1 -1
  111. package/dist/index.umd.js +24420 -588
  112. package/dist/index.umd.js.map +1 -1
  113. package/dist/internal/useBuildDataSource.d.ts +1 -12
  114. package/dist/internal/useUnsavedChangesDialog.d.ts +7 -9
  115. package/dist/preview/PropertyPreview.d.ts +1 -1
  116. package/dist/preview/PropertyPreviewProps.d.ts +7 -5
  117. package/dist/preview/components/BooleanPreview.d.ts +5 -1
  118. package/dist/preview/components/EnumValuesChip.d.ts +1 -1
  119. package/dist/preview/components/ReferencePreview.d.ts +4 -9
  120. package/dist/preview/components/StorageThumbnail.d.ts +2 -1
  121. package/dist/preview/components/UrlComponentPreview.d.ts +2 -1
  122. package/dist/preview/util.d.ts +3 -3
  123. package/dist/routes/CustomCMSRoute.d.ts +4 -0
  124. package/dist/routes/FireCMSRoute.d.ts +1 -0
  125. package/dist/routes/HomePageRoute.d.ts +3 -0
  126. package/dist/types/analytics.d.ts +1 -1
  127. package/dist/types/auth.d.ts +37 -3
  128. package/dist/types/collections.d.ts +86 -24
  129. package/dist/types/datasource.d.ts +53 -37
  130. package/dist/types/dialogs_controller.d.ts +7 -3
  131. package/dist/types/entities.d.ts +8 -2
  132. package/dist/types/entity_actions.d.ts +28 -4
  133. package/dist/types/entity_callbacks.d.ts +16 -16
  134. package/dist/types/entity_overrides.d.ts +6 -0
  135. package/dist/types/export_import.d.ts +4 -4
  136. package/dist/types/fields.d.ts +71 -42
  137. package/dist/types/firecms.d.ts +6 -2
  138. package/dist/types/firecms_context.d.ts +1 -1
  139. package/dist/types/index.d.ts +2 -1
  140. package/dist/types/navigation.d.ts +45 -26
  141. package/dist/types/permissions.d.ts +8 -4
  142. package/dist/types/plugins.d.ts +40 -31
  143. package/dist/types/properties.d.ts +54 -26
  144. package/dist/types/property_config.d.ts +2 -4
  145. package/dist/types/roles.d.ts +31 -0
  146. package/dist/types/side_dialogs_controller.d.ts +10 -0
  147. package/dist/types/side_entity_controller.d.ts +6 -1
  148. package/dist/types/storage.d.ts +86 -3
  149. package/dist/types/user.d.ts +6 -0
  150. package/dist/util/builders.d.ts +3 -3
  151. package/dist/util/collections.d.ts +9 -1
  152. package/dist/util/entities.d.ts +3 -3
  153. package/dist/util/entity_actions.d.ts +2 -0
  154. package/dist/util/entity_cache.d.ts +23 -0
  155. package/dist/util/icon_list.d.ts +5 -1
  156. package/dist/util/icon_synonyms.d.ts +1 -98
  157. package/dist/util/icons.d.ts +13 -4
  158. package/dist/util/index.d.ts +2 -0
  159. package/dist/util/navigation_from_path.d.ts +6 -1
  160. package/dist/util/navigation_utils.d.ts +14 -3
  161. package/dist/util/objects.d.ts +2 -1
  162. package/dist/util/permissions.d.ts +4 -4
  163. package/dist/util/plurals.d.ts +0 -2
  164. package/dist/util/property_utils.d.ts +3 -3
  165. package/dist/util/references.d.ts +4 -2
  166. package/dist/util/resolutions.d.ts +31 -17
  167. package/dist/util/storage.d.ts +23 -2
  168. package/dist/util/useStorageUploadController.d.ts +3 -3
  169. package/dist/util/useTraceUpdate.d.ts +1 -0
  170. package/package.json +64 -51
  171. package/src/app/AppBar.tsx +18 -0
  172. package/src/app/Drawer.tsx +24 -0
  173. package/src/app/Scaffold.tsx +243 -0
  174. package/src/app/index.ts +4 -0
  175. package/src/app/useApp.tsx +32 -0
  176. package/src/components/ArrayContainer.tsx +126 -40
  177. package/src/components/CircularProgressCenter.tsx +2 -2
  178. package/src/components/ClearFilterSortButton.tsx +41 -0
  179. package/src/components/{DeleteConfirmationDialog.tsx → ConfirmationDialog.tsx} +3 -3
  180. package/src/components/DeleteEntityDialog.tsx +14 -23
  181. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +56 -39
  182. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +283 -280
  183. package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +29 -8
  184. package/src/components/EntityCollectionTable/PropertyTableCell.tsx +58 -50
  185. package/src/components/EntityCollectionTable/column_utils.tsx +3 -3
  186. package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +40 -33
  187. package/src/components/EntityCollectionTable/fields/TableStorageUpload.tsx +22 -20
  188. package/src/components/EntityCollectionTable/index.tsx +1 -1
  189. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +31 -36
  190. package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +36 -34
  191. package/src/components/EntityCollectionTable/internal/EntityTableCellActions.tsx +20 -8
  192. package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +81 -80
  193. package/src/components/EntityCollectionView/EntityCollectionView.tsx +228 -155
  194. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +9 -8
  195. package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +68 -0
  196. package/src/components/EntityCollectionView/useSelectionController.tsx +43 -0
  197. package/src/components/EntityCollectionView/utils.ts +19 -0
  198. package/src/components/EntityPreview.tsx +218 -71
  199. package/src/components/EntityView.tsx +84 -0
  200. package/src/components/ErrorView.tsx +4 -4
  201. package/src/components/FieldCaption.tsx +14 -0
  202. package/src/components/HomePage/DefaultHomePage.tsx +39 -28
  203. package/src/components/HomePage/FavouritesView.tsx +6 -11
  204. package/src/components/HomePage/NavigationCard.tsx +69 -0
  205. package/src/components/HomePage/NavigationCardBinding.tsx +111 -0
  206. package/src/components/HomePage/NavigationGroup.tsx +2 -2
  207. package/src/components/HomePage/SmallNavigationCard.tsx +44 -0
  208. package/src/components/HomePage/index.tsx +3 -1
  209. package/src/components/NotFoundPage.tsx +2 -2
  210. package/src/components/PropertyConfigBadge.tsx +9 -3
  211. package/src/components/PropertyIdCopyTooltip.tsx +47 -0
  212. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +22 -17
  213. package/src/components/ReferenceWidget.tsx +26 -16
  214. package/src/components/SearchIconsView.tsx +10 -7
  215. package/src/components/SelectableTable/SelectableTable.tsx +27 -12
  216. package/src/components/SelectableTable/filters/BooleanFilterField.tsx +2 -3
  217. package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +25 -8
  218. package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +46 -30
  219. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +92 -23
  220. package/src/components/UnsavedChangesDialog.tsx +42 -0
  221. package/src/components/VirtualTable/VirtualTable.tsx +96 -48
  222. package/src/components/VirtualTable/VirtualTableCell.tsx +1 -9
  223. package/src/components/VirtualTable/VirtualTableHeader.tsx +10 -10
  224. package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +2 -2
  225. package/src/components/VirtualTable/VirtualTableProps.tsx +34 -15
  226. package/src/components/VirtualTable/VirtualTableRow.tsx +5 -6
  227. package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +5 -5
  228. package/src/components/VirtualTable/fields/VirtualTableInput.tsx +3 -2
  229. package/src/components/VirtualTable/fields/VirtualTableNumberInput.tsx +2 -1
  230. package/src/components/VirtualTable/fields/VirtualTableSelect.tsx +16 -28
  231. package/src/components/VirtualTable/types.tsx +2 -3
  232. package/src/components/{EntityCollectionTable/internal → common}/default_entity_actions.tsx +40 -43
  233. package/src/components/common/index.ts +2 -1
  234. package/src/components/{VirtualTable/common.tsx → common/table_height.tsx} +5 -2
  235. package/src/components/common/types.tsx +4 -6
  236. package/src/components/common/useColumnsIds.tsx +24 -3
  237. package/src/components/common/useDataSourceTableController.tsx +420 -0
  238. package/src/components/common/useDebouncedCallback.tsx +20 -0
  239. package/src/components/common/useScrollRestoration.tsx +68 -0
  240. package/src/components/common/useTableSearchHelper.ts +53 -12
  241. package/src/components/index.tsx +8 -5
  242. package/src/contexts/AuthControllerContext.tsx +1 -1
  243. package/src/contexts/BreacrumbsContext.tsx +38 -0
  244. package/src/contexts/DialogsProvider.tsx +5 -4
  245. package/src/contexts/ModeController.tsx +1 -3
  246. package/src/contexts/SnackbarProvider.tsx +2 -0
  247. package/src/core/DefaultAppBar.tsx +207 -0
  248. package/src/core/DefaultDrawer.tsx +185 -0
  249. package/src/core/DrawerNavigationItem.tsx +66 -0
  250. package/src/core/EntityEditView.tsx +455 -0
  251. package/src/core/EntityEditViewFormActions.tsx +199 -0
  252. package/src/core/EntitySidePanel.tsx +87 -18
  253. package/src/core/FireCMS.tsx +67 -44
  254. package/src/core/FireCMSRouter.tsx +17 -0
  255. package/src/core/NavigationRoutes.tsx +36 -38
  256. package/src/core/SideDialogs.tsx +22 -12
  257. package/src/core/field_configs.tsx +11 -12
  258. package/src/core/index.tsx +6 -7
  259. package/src/form/EntityForm.tsx +565 -519
  260. package/src/form/EntityFormActions.tsx +169 -0
  261. package/src/form/PropertyFieldBinding.tsx +85 -44
  262. package/src/form/components/CustomIdField.tsx +6 -2
  263. package/src/form/components/FieldHelperText.tsx +4 -4
  264. package/src/form/components/FormEntry.tsx +22 -0
  265. package/src/form/components/FormLayout.tsx +16 -0
  266. package/src/form/components/LabelWithIcon.tsx +44 -0
  267. package/src/form/components/LabelWithIconAndTooltip.tsx +28 -0
  268. package/src/form/components/StorageItemPreview.tsx +23 -11
  269. package/src/form/components/StorageUploadProgress.tsx +9 -9
  270. package/src/form/components/index.tsx +4 -1
  271. package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +32 -21
  272. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +54 -45
  273. package/src/form/field_bindings/BlockFieldBinding.tsx +57 -36
  274. package/src/form/field_bindings/DateTimeFieldBinding.tsx +21 -17
  275. package/src/form/field_bindings/KeyValueFieldBinding.tsx +76 -67
  276. package/src/form/field_bindings/MapFieldBinding.tsx +82 -65
  277. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +153 -0
  278. package/src/form/field_bindings/{MultiSelectBinding.tsx → MultiSelectFieldBinding.tsx} +26 -22
  279. package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +12 -17
  280. package/src/form/field_bindings/ReferenceFieldBinding.tsx +47 -34
  281. package/src/form/field_bindings/RepeatFieldBinding.tsx +57 -36
  282. package/src/form/field_bindings/SelectFieldBinding.tsx +24 -17
  283. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +47 -46
  284. package/src/form/field_bindings/SwitchFieldBinding.tsx +31 -27
  285. package/src/form/field_bindings/TextFieldBinding.tsx +35 -30
  286. package/src/form/index.tsx +17 -37
  287. package/src/form/useClearRestoreValue.tsx +2 -2
  288. package/src/form/validation.ts +15 -26
  289. package/src/hooks/data/delete.ts +7 -6
  290. package/src/hooks/data/save.ts +13 -9
  291. package/src/hooks/data/useCollectionFetch.tsx +4 -4
  292. package/src/hooks/data/useDataSource.tsx +15 -2
  293. package/src/hooks/data/useEntityFetch.tsx +11 -7
  294. package/src/hooks/index.tsx +6 -1
  295. package/src/hooks/useAuthController.tsx +1 -1
  296. package/src/hooks/useBreadcrumbsController.tsx +31 -0
  297. package/src/{core → hooks}/useBuildLocalConfigurationPersistence.tsx +8 -10
  298. package/src/{core → hooks}/useBuildModeController.tsx +23 -30
  299. package/src/hooks/useBuildNavigationController.tsx +278 -117
  300. package/src/hooks/useFireCMSContext.tsx +5 -5
  301. package/src/hooks/useLargeLayout.tsx +0 -35
  302. package/src/hooks/useModeController.tsx +1 -2
  303. package/src/hooks/useProjectLog.tsx +41 -9
  304. package/src/hooks/useReferenceDialog.tsx +2 -2
  305. package/src/hooks/useResolvedNavigationFrom.tsx +10 -12
  306. package/src/hooks/useStorageSource.tsx +7 -2
  307. package/src/hooks/useValidateAuthenticator.tsx +115 -0
  308. package/src/index.ts +1 -0
  309. package/src/internal/useBuildDataSource.ts +72 -77
  310. package/src/internal/useBuildSideDialogsController.tsx +4 -2
  311. package/src/internal/useBuildSideEntityController.tsx +177 -70
  312. package/src/internal/useUnsavedChangesDialog.tsx +127 -91
  313. package/src/preview/PropertyPreview.tsx +19 -26
  314. package/src/preview/PropertyPreviewProps.tsx +5 -9
  315. package/src/preview/components/BooleanPreview.tsx +19 -4
  316. package/src/preview/components/EmptyValue.tsx +1 -1
  317. package/src/preview/components/EnumValuesChip.tsx +2 -2
  318. package/src/preview/components/ImagePreview.tsx +25 -36
  319. package/src/preview/components/ReferencePreview.tsx +73 -176
  320. package/src/preview/components/StorageThumbnail.tsx +5 -1
  321. package/src/preview/components/UrlComponentPreview.tsx +60 -28
  322. package/src/preview/property_previews/ArrayOfMapsPreview.tsx +2 -3
  323. package/src/preview/property_previews/ArrayOfReferencesPreview.tsx +4 -3
  324. package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +2 -3
  325. package/src/preview/property_previews/ArrayOfStringsPreview.tsx +1 -2
  326. package/src/preview/property_previews/ArrayOneOfPreview.tsx +4 -5
  327. package/src/preview/property_previews/ArrayPropertyPreview.tsx +4 -5
  328. package/src/preview/property_previews/MapPropertyPreview.tsx +12 -11
  329. package/src/preview/property_previews/SkeletonPropertyComponent.tsx +12 -12
  330. package/src/preview/property_previews/StringPropertyPreview.tsx +8 -7
  331. package/src/preview/util.ts +10 -10
  332. package/src/routes/CustomCMSRoute.tsx +21 -0
  333. package/src/routes/FireCMSRoute.tsx +236 -0
  334. package/src/routes/HomePageRoute.tsx +17 -0
  335. package/src/types/analytics.ts +1 -0
  336. package/src/types/auth.tsx +48 -3
  337. package/src/types/collections.ts +102 -27
  338. package/src/types/customization_controller.tsx +0 -1
  339. package/src/types/datasource.ts +62 -44
  340. package/src/types/dialogs_controller.tsx +7 -3
  341. package/src/types/entities.ts +16 -2
  342. package/src/types/entity_actions.tsx +33 -4
  343. package/src/types/entity_callbacks.ts +18 -18
  344. package/src/types/entity_overrides.tsx +7 -0
  345. package/src/types/export_import.ts +4 -4
  346. package/src/types/fields.tsx +81 -46
  347. package/src/types/firecms.tsx +6 -2
  348. package/src/types/firecms_context.tsx +1 -1
  349. package/src/types/index.ts +2 -1
  350. package/src/types/navigation.ts +52 -31
  351. package/src/types/permissions.ts +10 -5
  352. package/src/types/plugins.tsx +47 -39
  353. package/src/types/properties.ts +63 -27
  354. package/src/types/property_config.tsx +2 -4
  355. package/src/types/roles.ts +41 -0
  356. package/src/types/side_dialogs_controller.tsx +15 -0
  357. package/src/types/side_entity_controller.tsx +7 -1
  358. package/src/types/storage.ts +94 -3
  359. package/src/types/user.ts +9 -0
  360. package/src/util/builders.ts +10 -8
  361. package/src/util/collections.ts +22 -0
  362. package/src/util/entities.ts +9 -6
  363. package/src/util/entity_actions.ts +28 -0
  364. package/src/util/entity_cache.ts +204 -0
  365. package/src/util/enums.ts +1 -1
  366. package/src/util/icon_list.ts +16 -10
  367. package/src/util/icon_synonyms.ts +3 -100
  368. package/src/util/icons.tsx +26 -8
  369. package/src/util/index.ts +2 -0
  370. package/src/util/join_collections.ts +3 -0
  371. package/src/util/make_properties_editable.ts +13 -5
  372. package/src/util/navigation_from_path.ts +13 -6
  373. package/src/util/navigation_utils.ts +59 -7
  374. package/src/util/objects.ts +45 -28
  375. package/src/util/parent_references_from_path.ts +3 -3
  376. package/src/util/permissions.ts +20 -16
  377. package/src/util/plurals.ts +0 -2
  378. package/src/util/property_utils.tsx +11 -4
  379. package/src/util/references.ts +45 -5
  380. package/src/util/resolutions.ts +79 -46
  381. package/src/util/storage.ts +79 -21
  382. package/src/util/strings.ts +2 -2
  383. package/src/util/useStorageUploadController.tsx +71 -27
  384. package/src/util/useTraceUpdate.tsx +2 -1
  385. package/dist/components/PropertyIdCopyTooltipContent.d.ts +0 -3
  386. package/dist/components/VirtualTable/common.d.ts +0 -2
  387. package/dist/core/Drawer.d.ts +0 -23
  388. package/dist/core/Scaffold.d.ts +0 -55
  389. package/dist/form/components/FormikArrayContainer.d.ts +0 -18
  390. package/dist/form/field_bindings/MarkdownFieldBinding.d.ts +0 -9
  391. package/dist/internal/EntityView.d.ts +0 -22
  392. package/dist/internal/useBuildCustomizationController.d.ts +0 -2
  393. package/dist/internal/useLocaleConfig.d.ts +0 -1
  394. package/dist/types/appcheck.d.ts +0 -26
  395. package/src/components/FireCMSAppBar.tsx +0 -157
  396. package/src/components/HomePage/NavigationCollectionCard.tsx +0 -146
  397. package/src/components/LabelWithIcon.tsx +0 -33
  398. package/src/components/PropertyIdCopyTooltipContent.tsx +0 -28
  399. package/src/components/common/useDataSourceEntityCollectionTableController.tsx +0 -225
  400. package/src/core/Drawer.tsx +0 -164
  401. package/src/core/Scaffold.tsx +0 -281
  402. package/src/form/components/FormikArrayContainer.tsx +0 -44
  403. package/src/form/field_bindings/MarkdownFieldBinding.tsx +0 -696
  404. package/src/internal/EntityView.tsx +0 -578
  405. package/src/internal/useBuildCustomizationController.tsx +0 -5
  406. package/src/internal/useLocaleConfig.tsx +0 -18
  407. package/src/types/appcheck.ts +0 -29
  408. /package/dist/{core → hooks}/useBuildLocalConfigurationPersistence.d.ts +0 -0
  409. /package/src/util/{common.tsx → common.ts} +0 -0
@@ -1,243 +1,161 @@
1
- import React, { MutableRefObject, useCallback, useEffect, useMemo, useRef, useState } from "react";
2
-
1
+ import React, { useCallback, useDeferredValue, useEffect, useMemo, useRef, useState } from "react";
3
2
  import {
4
3
  CMSAnalyticsEvent,
5
4
  Entity,
6
- EntityAction,
7
5
  EntityCollection,
8
6
  EntityStatus,
9
7
  EntityValues,
10
8
  FormContext,
11
- PluginFormActionProps,
9
+ PluginFormActionProps, PropertyConfig,
12
10
  PropertyFieldBindingProps,
13
11
  ResolvedEntityCollection
14
12
  } from "../types";
15
- import { Formex, FormexController, getIn, setIn, useCreateFormex } from "@firecms/formex";
16
- import { PropertyFieldBinding } from "./PropertyFieldBinding";
17
- import { CustomFieldValidator, getYupEntitySchema } from "./validation";
18
- import equal from "react-fast-compare"
13
+ import equal from "react-fast-compare";
14
+
15
+ import { ErrorBoundary, getFormFieldKeys } from "../components";
19
16
  import {
20
- canCreateEntity,
21
- canDeleteEntity,
22
- fullPathToCollectionSegments,
23
17
  getDefaultValuesFor,
18
+ getEntityTitlePropertyKey,
19
+ getValueInPath,
24
20
  isHidden,
25
21
  isReadOnly,
26
- resolveCollection
22
+ resolveCollection,
23
+ useDebouncedCallback
27
24
  } from "../util";
25
+
28
26
  import {
29
- useAuthController,
27
+ saveEntityWithCallbacks,
30
28
  useCustomizationController,
31
29
  useDataSource,
32
30
  useFireCMSContext,
33
- useSideEntityController
31
+ useSnackbarController
34
32
  } from "../hooks";
35
- import { ErrorFocus } from "./components/ErrorFocus";
36
- import { CustomIdField } from "./components/CustomIdField";
37
- import { Alert, Button, cn, DialogActions, IconButton, Tooltip, Typography } from "@firecms/ui";
38
- import { ErrorBoundary } from "../components";
39
- import {
40
- copyEntityAction,
41
- deleteEntityAction
42
- } from "../components/EntityCollectionTable/internal/default_entity_actions";
33
+ import { Alert, CheckIcon, Chip, cls, EditIcon, NotesIcon, paperMixin, Tooltip, Typography } from "@firecms/ui";
34
+ import { Formex, FormexController, getIn, setIn, useCreateFormex } from "@firecms/formex";
43
35
  import { useAnalyticsController } from "../hooks/useAnalyticsController";
36
+ import { FormEntry, FormLayout, LabelWithIconAndTooltip, PropertyFieldBinding } from "../form";
44
37
  import { ValidationError } from "yup";
45
- import { PropertyIdCopyTooltipContent } from "../components/PropertyIdCopyTooltipContent";
46
-
47
- /**
48
- * @group Components
49
- */
50
- export interface EntityFormProps<M extends Record<string, any>> {
51
-
52
- /**
53
- * New or existing status
54
- */
55
- status: EntityStatus;
38
+ import { removeEntityFromCache, saveEntityToCache } from "../util/entity_cache";
39
+ import { CustomIdField } from "../form/components/CustomIdField";
40
+ import { ErrorFocus } from "../form/components/ErrorFocus";
41
+ import { CustomFieldValidator, getYupEntitySchema } from "../form/validation";
42
+ import { EntityFormActions, EntityFormActionsProps } from "./EntityFormActions";
43
+
44
+ export type OnUpdateParams = {
45
+ entity: Entity<any>,
46
+ status: EntityStatus,
47
+ path: string,
48
+ entityId?: string;
49
+ selectedTab?: string;
50
+ collection: EntityCollection<any>
51
+ };
56
52
 
57
- /**
58
- * Path of the collection this entity is located
59
- */
53
+ export type EntityFormProps<M extends Record<string, any>> = {
60
54
  path: string;
61
-
62
- /**
63
- * The collection is used to build the fields of the form
64
- */
65
- collection: EntityCollection<M>
66
-
67
- /**
68
- * The updated entity is passed from the parent component when the underlying data
69
- * has changed in the datasource
70
- */
55
+ collection: EntityCollection<M>;
56
+ entityId?: string;
71
57
  entity?: Entity<M>;
72
-
73
- /**
74
- * The callback function called when Save is clicked and validation is correct
75
- */
76
- onEntitySaveRequested: (
77
- props: EntityFormSaveParams<M>
78
- ) => Promise<void>;
79
-
80
- /**
81
- * The callback function called when discard is clicked
82
- */
83
- onDiscard?: () => void;
84
-
85
- /**
86
- * The callback function when the form is dirty, so the values are different
87
- * from the original ones
88
- */
89
- onModified?: (dirty: boolean) => void;
90
-
58
+ databaseId?: string;
59
+ onIdChange?: (id: string) => void;
60
+ onValuesModified?: (modified: boolean) => void;
61
+ onSaved?: (params: OnUpdateParams) => void;
62
+ initialDirtyValues?: Partial<M>; // dirty cached entity in memory
63
+ onFormContextReady?: (formContext: FormContext) => void;
64
+ forceActionsAtTheBottom?: boolean;
65
+ className?: string;
66
+ initialStatus: EntityStatus;
67
+ onStatusChange?: (status: EntityStatus) => void;
68
+ onEntityChange?: (entity: Entity<M>) => void;
69
+ formex?: FormexController<M>;
70
+ openEntityMode?: "side_panel" | "full_screen";
91
71
  /**
92
- * The callback function when the form original values have been modified
72
+ * Include the copy and delete actions in the form
93
73
  */
94
- onValuesChanged?: (values?: EntityValues<M>) => void;
74
+ showDefaultActions?: boolean;
95
75
 
96
76
  /**
97
- *
98
- * @param id
77
+ * Display the entity path in the form
99
78
  */
100
- onIdChange?: (id: string) => void;
101
-
102
- currentEntityId?: string;
103
-
104
- onFormContextChange?: (formContext: FormContext<M>) => void;
105
-
106
- hideId?: boolean;
79
+ showEntityPath?: boolean;
107
80
 
108
- autoSave?: boolean;
109
-
110
- onIdUpdateError?: (error: any) => void;
111
-
112
- }
113
-
114
- export type EntityFormSaveParams<M extends Record<string, any>> = {
115
- collection: ResolvedEntityCollection<M>,
116
- path: string,
117
- entityId: string | undefined,
118
- values: EntityValues<M>,
119
- previousValues?: EntityValues<M>,
120
- closeAfterSave: boolean,
121
- autoSave: boolean
81
+ EntityFormActionsComponent?: React.FC<EntityFormActionsProps>;
122
82
  };
123
83
 
124
- /**
125
- * This is the form used internally by the CMS
126
- * @param status
127
- * @param path
128
- * @param collection
129
- * @param entity
130
- * @param onEntitySave
131
- * @param onDiscard
132
- * @param onModified
133
- * @param onValuesChanged
134
- * @constructor
135
- * @group Components
136
- */
137
- export const EntityForm = React.memo<EntityFormProps<any>>(EntityFormInternal,
138
- (a: EntityFormProps<any>, b: EntityFormProps<any>) => {
139
- return a.status === b.status &&
140
- a.path === b.path &&
141
- equal(a.entity?.values, b.entity?.values);
142
- }) as typeof EntityFormInternal;
143
-
144
- function getDataSourceEntityValues<M extends object>(initialResolvedCollection: ResolvedEntityCollection,
145
- status: "new" | "existing" | "copy",
146
- entity: Entity<M> | undefined): Partial<EntityValues<M>> {
147
- const properties = initialResolvedCollection.properties;
148
- if ((status === "existing" || status === "copy") && entity) {
149
- return entity.values ?? getDefaultValuesFor(properties);
150
- } else if (status === "new") {
151
- return getDefaultValuesFor(properties);
152
- } else {
153
- console.error({
154
- status,
155
- entity
156
- });
157
- throw new Error("Form has not been initialised with the correct parameters");
84
+ export function EntityForm<M extends Record<string, any>>({
85
+ path,
86
+ entityId: entityIdProp,
87
+ collection,
88
+ onValuesModified,
89
+ onIdChange,
90
+ onSaved,
91
+ entity,
92
+ initialDirtyValues,
93
+ onFormContextReady,
94
+ forceActionsAtTheBottom,
95
+ initialStatus,
96
+ className,
97
+ onStatusChange,
98
+ onEntityChange,
99
+ openEntityMode = "full_screen",
100
+ formex: formexProp,
101
+ EntityFormActionsComponent = EntityFormActions,
102
+ showDefaultActions = true,
103
+ showEntityPath = true
104
+ }: EntityFormProps<M>) {
105
+
106
+ if (collection.customId && collection.formAutoSave) {
107
+ console.warn(`The collection ${collection.path} has customId and formAutoSave enabled. This is not supported and formAutoSave will be ignored`);
158
108
  }
159
- }
160
109
 
161
- function EntityFormInternal<M extends Record<string, any>>({
162
- status,
163
- path,
164
- collection: inputCollection,
165
- entity,
166
- onEntitySaveRequested,
167
- onDiscard,
168
- onModified,
169
- onValuesChanged,
170
- onIdChange,
171
- onFormContextChange,
172
- hideId,
173
- autoSave,
174
- onIdUpdateError
175
- }: EntityFormProps<M>) {
110
+ const [status, setStatus] = useState<EntityStatus>(initialStatus);
111
+ const [saving, setSaving] = useState(false);
176
112
 
177
- const analyticsController = useAnalyticsController();
113
+ const updateStatus = (status: EntityStatus) => {
114
+ setStatus(status);
115
+ onStatusChange?.(status);
116
+ };
178
117
 
179
- const customizationController = useCustomizationController();
118
+ const [valuesToBeSaved, setValuesToBeSaved] = useState<EntityValues<M> | undefined>(undefined);
119
+ useDebouncedCallback(valuesToBeSaved, () => {
120
+ if (valuesToBeSaved)
121
+ saveEntity({
122
+ entityId: entityIdProp,
123
+ collection,
124
+ path,
125
+ values: valuesToBeSaved,
126
+ });
127
+ }, false, 2000);
180
128
 
129
+ const dataSource = useDataSource(collection);
130
+ const snackbarController = useSnackbarController();
131
+ const customizationController = useCustomizationController();
181
132
  const context = useFireCMSContext();
182
- const dataSource = useDataSource();
183
- const plugins = customizationController.plugins;
133
+ const analyticsController = useAnalyticsController();
184
134
 
185
- const initialResolvedCollection = useMemo(() => resolveCollection({
186
- collection: inputCollection,
187
- path,
188
- values: entity?.values,
189
- fields: customizationController.propertyConfigs
190
- }), [entity?.values, path]);
135
+ const [underlyingChanges, setUnderlyingChanges] = useState<Partial<EntityValues<M>>>({});
136
+
137
+ const [customIdLoading, setCustomIdLoading] = useState<boolean>(false);
191
138
 
192
139
  const mustSetCustomId: boolean = (status === "new" || status === "copy") &&
193
- (Boolean(initialResolvedCollection.customId) && initialResolvedCollection.customId !== "optional");
140
+ (Boolean(collection.customId) && collection.customId !== "optional");
194
141
 
195
- const initialEntityId = useMemo(() => {
142
+ const initialEntityId: string | undefined = useMemo((): string | undefined => {
196
143
  if (status === "new" || status === "copy") {
197
144
  if (mustSetCustomId) {
198
145
  return undefined;
199
146
  } else {
200
- return dataSource.generateEntityId(path);
147
+ return dataSource.generateEntityId(path, collection);
201
148
  }
202
149
  } else {
203
- return entity?.id;
150
+ return entityIdProp;
204
151
  }
205
- }, []);
206
-
207
- const closeAfterSaveRef = useRef(false);
208
-
209
- const baseDataSourceValuesRef = useRef<Partial<EntityValues<M>>>(getDataSourceEntityValues(initialResolvedCollection, status, entity));
152
+ }, [entityIdProp, status]);
210
153
 
211
- const [entityId, setEntityId] = React.useState<string | undefined>(initialEntityId);
212
- const [entityIdError, setEntityIdError] = React.useState<boolean>(false);
213
- const [savingError, setSavingError] = React.useState<Error | undefined>();
154
+ const [entityId, setEntityId] = useState<string | undefined>(initialEntityId);
155
+ const [entityIdError, setEntityIdError] = useState<boolean>(false);
156
+ const [savingError, setSavingError] = useState<Error | undefined>();
214
157
 
215
- const [customIdLoading, setCustomIdLoading] = React.useState<boolean>(false);
216
-
217
- // const initialValuesRef = useRef<EntityValues<M>>(entity?.values ?? baseDataSourceValues as EntityValues<M>);
218
- const [internalValues, setInternalValues] = useState<EntityValues<M> | undefined>(entity?.values ?? baseDataSourceValuesRef.current as EntityValues<M>);
219
-
220
- const save = (values: EntityValues<M>): Promise<void> => {
221
- return onEntitySaveRequested({
222
- collection: resolvedCollection,
223
- path,
224
- entityId,
225
- values,
226
- previousValues: entity?.values,
227
- closeAfterSave: closeAfterSaveRef.current,
228
- autoSave: autoSave ?? false
229
- }).then(_ => {
230
- const eventName: CMSAnalyticsEvent = status === "new"
231
- ? "new_entity_saved"
232
- : (status === "copy" ? "entity_copied" : (status === "existing" ? "entity_edited" : "unmapped_event"));
233
- analyticsController.onAnalyticsEvent?.(eventName, { path });
234
- }).catch(e => {
235
- console.error(e);
236
- setSavingError(e);
237
- }).finally(() => {
238
- closeAfterSaveRef.current = false;
239
- });
240
- };
158
+ const autoSave = collection.formAutoSave && !collection.customId;
241
159
 
242
160
  const onSubmit = (values: EntityValues<M>, formexController: FormexController<EntityValues<M>>) => {
243
161
 
@@ -254,8 +172,8 @@ function EntityFormInternal<M extends Record<string, any>>({
254
172
  if (status === "existing") {
255
173
  if (!entity?.id) throw Error("Form misconfiguration when saving, no id for existing entity");
256
174
  } else if (status === "new" || status === "copy") {
257
- if (inputCollection.customId) {
258
- if (inputCollection.customId !== "optional" && !entityId) {
175
+ if (collection.customId) {
176
+ if (collection.customId !== "optional" && !entityId) {
259
177
  throw Error("Form misconfiguration when saving, entityId should be set");
260
178
  }
261
179
  }
@@ -274,109 +192,272 @@ function EntityFormInternal<M extends Record<string, any>>({
274
192
  .finally(() => {
275
193
  formexController.setSubmitting(false);
276
194
  });
277
-
278
195
  };
279
196
 
280
- const formex: FormexController<M> = useCreateFormex<M>({
281
- initialValues: baseDataSourceValuesRef.current as M,
197
+ const formex: FormexController<M> = formexProp ?? useCreateFormex<M>({
198
+ initialValues: (initialDirtyValues ?? getInitialEntityValues(collection, path, status, entity, customizationController.propertyConfigs)) as M,
199
+ initialDirty: Boolean(initialDirtyValues),
282
200
  onSubmit,
201
+ onReset: () => {
202
+ clearDirtyCache();
203
+ onValuesModified?.(false);
204
+ },
283
205
  validation: (values) => {
284
206
  return validationSchema?.validate(values, { abortEarly: false })
285
207
  .then(() => {
286
208
  return {};
287
209
  })
288
- .catch((e) => {
289
-
290
- const errors: Record<string, string> = {};
291
- e.inner.forEach((error: any) => {
292
- errors[error.path] = error.message;
293
- });
210
+ .catch((e: any) => {
294
211
  return yupToFormErrors(e);
295
212
  });
296
213
  }
297
214
  });
298
215
 
299
- useEffect(() => {
300
- baseDataSourceValuesRef.current = getDataSourceEntityValues(initialResolvedCollection, status, entity);
301
- const initialValues = formex.initialValues;
302
- if (!formex.isSubmitting && initialValues && status === "existing") {
303
- setUnderlyingChanges(
304
- Object.entries(resolvedCollection.properties)
305
- .map(([key, property]) => {
306
- if (isHidden(property)) {
307
- return {};
308
- }
309
- const initialValue = initialValues[key];
310
- const latestValue = baseDataSourceValuesRef.current[key];
311
- if (!equal(initialValue, latestValue)) {
312
- return { [key]: latestValue };
313
- }
314
- return {};
315
- })
316
- .reduce((a, b) => ({ ...a, ...b }), {}) as Partial<EntityValues<M>>
317
- );
216
+ const resolvedCollection = useMemo(() => resolveCollection<M>({
217
+ collection,
218
+ path,
219
+ entityId,
220
+ values: formex.values,
221
+ previousValues: formex.initialValues,
222
+ propertyConfigs: customizationController.propertyConfigs
223
+ }), [collection, path, entityId, formex.values, formex.initialValues, customizationController.propertyConfigs]);
224
+
225
+ const onPreSaveHookError = useCallback((e: Error) => {
226
+ setSaving(false);
227
+ snackbarController.open({
228
+ type: "error",
229
+ message: "Error before saving: " + e?.message
230
+ });
231
+ console.error(e);
232
+ }, [snackbarController]);
233
+
234
+ const onSaveSuccessHookError = useCallback((e: Error) => {
235
+ setSaving(false);
236
+ snackbarController.open({
237
+ type: "error",
238
+ message: "Error after saving (entity is saved): " + e?.message
239
+ });
240
+ console.error(e);
241
+ }, [snackbarController]);
242
+
243
+ function clearDirtyCache() {
244
+ if (status === "new" || status === "copy") {
245
+ removeEntityFromCache(path + "#new");
318
246
  } else {
319
- setUnderlyingChanges({});
247
+ removeEntityFromCache(path + "/" + entityId);
320
248
  }
321
- }, [entity, initialResolvedCollection, status]);
322
-
323
- const doOnValuesChanges = (values?: EntityValues<M>) => {
324
- const initialValues = formex.initialValues;
325
- setInternalValues(values);
326
- if (onValuesChanged)
327
- onValuesChanged(values);
328
- if (autoSave && values && !equal(values, initialValues)) {
329
- save(values);
249
+ }
250
+
251
+ const onSaveSuccess = (updatedEntity: Entity<M>) => {
252
+
253
+ clearDirtyCache();
254
+ onValuesModified?.(false);
255
+ setSaving(false);
256
+ if (!autoSave)
257
+ snackbarController.open({
258
+ type: "success",
259
+ message: `${collection.singularName ?? collection.name}: Saved correctly`
260
+ });
261
+ onEntityChange?.(updatedEntity);
262
+ updateStatus("existing");
263
+ setEntityId(updatedEntity.id);
264
+
265
+ if (onSaved) {
266
+ onSaved({
267
+ entity: updatedEntity,
268
+ status,
269
+ path,
270
+ entityId: updatedEntity.id,
271
+ collection
272
+ });
330
273
  }
331
274
  };
332
275
 
333
- useEffect(() => {
334
- if (entityId && onIdChange)
335
- onIdChange(entityId);
336
- }, [entityId, onIdChange]);
276
+ const onSaveFailure = useCallback((e: Error) => {
277
+ setSaving(false);
278
+ snackbarController.open({
279
+ type: "error",
280
+ message: "Error saving: " + e?.message
281
+ });
282
+ console.error("Error saving entity", path, entityId);
283
+ console.error(e);
284
+ }, [entityId, path, snackbarController]);
285
+
286
+ const saveEntity = ({
287
+ values,
288
+ previousValues,
289
+ entityId,
290
+ collection,
291
+ path
292
+ }: {
293
+ collection: EntityCollection<M>,
294
+ path: string,
295
+ entityId: string | undefined,
296
+ values: M,
297
+ previousValues?: M,
298
+ }) => {
299
+ setSaving(true);
300
+ return saveEntityWithCallbacks({
301
+ path,
302
+ entityId,
303
+ values,
304
+ previousValues,
305
+ collection,
306
+ status,
307
+ dataSource,
308
+ context,
309
+ onSaveSuccess: (updatedEntity: Entity<M>) => onSaveSuccess(updatedEntity),
310
+ onSaveFailure,
311
+ onPreSaveHookError,
312
+ onSaveSuccessHookError
313
+ }).then();
314
+ };
315
+
316
+ type EntityFormSaveParams<M extends Record<string, any>> = {
317
+ collection: ResolvedEntityCollection<M>,
318
+ path: string,
319
+ entityId: string | undefined,
320
+ values: EntityValues<M>,
321
+ previousValues?: EntityValues<M>,
322
+ autoSave: boolean
323
+ };
324
+
325
+ const onSaveEntityRequest = async ({
326
+ collection,
327
+ path,
328
+ entityId,
329
+ values,
330
+ previousValues,
331
+ autoSave
332
+ }: EntityFormSaveParams<M>): Promise<void> => {
333
+ if (!status)
334
+ return;
335
+ if (autoSave) {
336
+ setValuesToBeSaved(values);
337
+ } else {
338
+ return saveEntity({
339
+ collection,
340
+ path,
341
+ entityId,
342
+ values,
343
+ previousValues,
344
+ });
345
+ }
346
+ };
347
+
348
+ const lastSavedValues = useRef<EntityValues<M> | undefined>(entity?.values);
349
+ const save = (values: EntityValues<M>): Promise<void> => {
350
+ lastSavedValues.current = values;
351
+ return onSaveEntityRequest({
352
+ collection: resolvedCollection,
353
+ path,
354
+ entityId,
355
+ values,
356
+ previousValues: entity?.values,
357
+ autoSave: autoSave ?? false
358
+ }).then((res) => {
359
+ const eventName: CMSAnalyticsEvent = status === "new"
360
+ ? "new_entity_saved"
361
+ : (status === "copy" ? "entity_copied" : (status === "existing" ? "entity_edited" : "unmapped_event"));
362
+ analyticsController.onAnalyticsEvent?.(eventName, { path });
363
+ }).catch(e => {
364
+ console.error(e);
365
+ setSavingError(e);
366
+ });
367
+ };
337
368
 
338
- const resolvedCollection = resolveCollection<M>({
339
- collection: inputCollection,
369
+ const formContext: FormContext<M> = {
370
+ // @ts-ignore
371
+ setFieldValue: useCallback(formex.setFieldValue, []),
372
+ values: formex.values,
373
+ collection: resolvedCollection,
374
+ entityId: entityId as string,
340
375
  path,
341
- entityId,
342
- values: internalValues,
343
- previousValues: formex.initialValues,
344
- fields: customizationController.propertyConfigs
345
- });
376
+ save,
377
+ formex,
378
+ entity,
379
+ savingError,
380
+ status,
381
+ openEntityMode
382
+ };
383
+
384
+ useEffect(() => {
385
+ onFormContextReady?.(formContext);
386
+ }, [formex.version, resolvedCollection, entityId, path]);
346
387
 
347
- const onIdUpdate = inputCollection.callbacks?.onIdUpdate;
388
+ const onIdUpdateError = useCallback((error: any) => {
389
+ snackbarController.open({
390
+ type: "error",
391
+ message: "Error updating id, check the console"
392
+ });
393
+ }, []);
348
394
 
395
+ const pluginActions: React.ReactNode[] = [];
396
+ const plugins = customizationController.plugins;
397
+
398
+ if (plugins && collection) {
399
+ const actionProps: PluginFormActionProps = {
400
+ entityId,
401
+ path,
402
+ status,
403
+ collection: collection,
404
+ context,
405
+ currentEntityId: entityId,
406
+ formContext,
407
+ openEntityMode
408
+ };
409
+ pluginActions.push(...plugins.map((plugin) => (
410
+ plugin.form?.Actions
411
+ ? <plugin.form.Actions
412
+ key={`actions_${plugin.key}`} {...actionProps} />
413
+ : null
414
+ )).filter(Boolean));
415
+ }
416
+
417
+ const titlePropertyKey = getEntityTitlePropertyKey(resolvedCollection, customizationController.propertyConfigs);
418
+ const title = (formex.values && titlePropertyKey ? getValueInPath(formex.values, titlePropertyKey) : undefined)
419
+ ?? collection.singularName
420
+ ?? collection.name;
421
+
422
+ const onIdUpdate = collection.callbacks?.onIdUpdate;
349
423
  const doOnIdUpdate = useCallback(async () => {
350
- if (onIdUpdate && internalValues && (status === "new" || status === "copy")) {
424
+ if (onIdUpdate && formex.values && (status === "new" || status === "copy")) {
351
425
  setCustomIdLoading(true);
352
426
  try {
353
427
  const updatedId = await onIdUpdate({
354
428
  collection: resolvedCollection,
355
429
  path,
356
430
  entityId,
357
- values: internalValues,
431
+ values: formex.values,
358
432
  context
359
433
  });
360
434
  setEntityId(updatedId);
361
435
  } catch (e) {
362
- onIdUpdateError && onIdUpdateError(e);
436
+ onIdUpdateError?.(e);
363
437
  console.error(e);
364
438
  }
365
439
  setCustomIdLoading(false);
366
440
  }
367
- }, [entityId, internalValues, status]);
441
+ }, [entityId, formex.values, status, onIdUpdate, resolvedCollection, path, context, onIdUpdateError]);
368
442
 
369
443
  useEffect(() => {
370
444
  doOnIdUpdate();
371
445
  }, [doOnIdUpdate]);
372
446
 
373
- const [underlyingChanges, setUnderlyingChanges] = useState<Partial<EntityValues<M>>>({});
447
+ useEffect(() => {
448
+ if (!autoSave) {
449
+ onValuesModified?.(modified);
450
+ }
451
+ }, [formex.dirty]);
452
+
453
+ const deferredValues = useDeferredValue(formex.values);
454
+ const modified = formex.dirty;
374
455
 
375
456
  const uniqueFieldValidator: CustomFieldValidator = useCallback(({
376
457
  name,
377
458
  value,
378
459
  property
379
- }) => dataSource.checkUniqueField(path, name, value, entityId),
460
+ }) => dataSource.checkUniqueField(path, name, value, entityId, collection),
380
461
  [dataSource, path, entityId]);
381
462
 
382
463
  const validationSchema = useMemo(() => entityId
@@ -387,197 +468,44 @@ function EntityFormInternal<M extends Record<string, any>>({
387
468
  : undefined,
388
469
  [entityId, resolvedCollection.properties, uniqueFieldValidator]);
389
470
 
390
- const authController = useAuthController();
391
-
392
- const getActionsForEntity = useCallback(({ entity, customEntityActions }: {
393
- entity?: Entity<M>,
394
- customEntityActions?: EntityAction[]
395
- }): EntityAction[] => {
396
- const createEnabled = canCreateEntity(inputCollection, authController, fullPathToCollectionSegments(path), null);
397
- const deleteEnabled = entity ? canDeleteEntity(inputCollection, authController, fullPathToCollectionSegments(path), entity) : true;
398
- const actions: EntityAction[] = [];
399
- if (createEnabled)
400
- actions.push(copyEntityAction);
401
- if (deleteEnabled)
402
- actions.push(deleteEntityAction);
403
- if (customEntityActions)
404
- actions.push(...customEntityActions);
405
- return actions;
406
- }, [authController, inputCollection, path]);
407
-
408
- const pluginActions: React.ReactNode[] = [];
409
-
410
- const formContext: FormContext<M> = {
411
- setFieldValue: formex.setFieldValue,
412
- values: formex.values,
413
- collection: resolvedCollection,
414
- entityId,
415
- path,
416
- save
417
- };
418
-
419
- // eslint-disable-next-line react-hooks/rules-of-hooks
420
471
  useEffect(() => {
421
- if (onFormContextChange) {
422
- onFormContextChange(formContext);
472
+ const key = (status === "new" || status === "copy") ? path + "#new" : path + "/" + entityId;
473
+ if (modified) {
474
+ saveEntityToCache(key, deferredValues);
423
475
  }
424
- }, [onFormContextChange, formContext]);
425
-
426
- if (plugins && inputCollection) {
427
- const actionProps: PluginFormActionProps = {
428
- entityId,
429
- path,
430
- status,
431
- collection: inputCollection,
432
- context,
433
- currentEntityId: entityId,
434
- formContext
435
- };
436
- pluginActions.push(...plugins.map((plugin, i) => (
437
- plugin.form?.Actions
438
- ? <plugin.form.Actions
439
- key={`actions_${plugin.name}`} {...actionProps}/>
440
- : null
441
- )).filter(Boolean));
442
- }
443
-
444
- return <Formex value={formex}>
445
- <div className="h-full overflow-auto">
446
-
447
- {pluginActions.length > 0 && <div
448
- className={cn("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")}>
449
- {pluginActions}
450
- </div>}
451
-
452
- <div className="pt-12 pb-16 pl-8 pr-8 md:pl-10 md:pr-10">
453
- <div
454
- className={`w-full py-2 flex flex-col items-start mt-${4 + (pluginActions ? 8 : 0)} lg:mt-${8 + (pluginActions ? 8 : 0)} mb-8`}>
455
-
456
- <Typography
457
- className={"mt-4 flex-grow " + inputCollection.hideIdFromForm ? "mb-2" : "mb-0"}
458
- variant={"h4"}>{inputCollection.singularName ?? inputCollection.name}
459
- </Typography>
460
- <Alert color={"base"} className={"w-full"} size={"small"}>
461
- <code className={"text-xs select-all"}>{path}/{entityId}</code>
462
- </Alert>
463
- </div>
464
-
465
- {!hideId &&
466
- <CustomIdField customId={inputCollection.customId}
467
- entityId={entityId}
468
- status={status}
469
- onChange={setEntityId}
470
- error={entityIdError}
471
- loading={customIdLoading}
472
- entity={entity}/>}
473
-
474
- {entityId && <InnerForm
475
- {...formex}
476
- initialValues={formex.initialValues}
477
- onModified={onModified}
478
- onDiscard={onDiscard}
479
- onValuesChanged={doOnValuesChanges}
480
- underlyingChanges={underlyingChanges}
481
- entity={entity}
482
- resolvedCollection={resolvedCollection}
483
- formContext={formContext}
484
- status={status}
485
- savingError={savingError}
486
- closeAfterSaveRef={closeAfterSaveRef}
487
- autoSave={autoSave}
488
- entityActions={getActionsForEntity({
489
- entity,
490
- customEntityActions: inputCollection.entityActions
491
- })}/>}
492
-
493
- </div>
494
- </div>
495
- </Formex>
496
- }
497
-
498
- function InnerForm<M extends Record<string, any>>(props: FormexController<M> & {
499
- initialValues: EntityValues<M>,
500
- onModified: ((modified: boolean) => void) | undefined,
501
- onValuesChanged?: (changedValues?: EntityValues<M>) => void,
502
- underlyingChanges: Partial<M>,
503
- entity: Entity<M> | undefined,
504
- resolvedCollection: ResolvedEntityCollection<M>,
505
- formContext: FormContext<M>,
506
- onDiscard?: () => void,
507
- status: "new" | "existing" | "copy",
508
- savingError?: Error,
509
- closeAfterSaveRef: MutableRefObject<boolean>,
510
- autoSave?: boolean,
511
- entityActions: EntityAction[]
512
- }) {
476
+ }, [deferredValues, modified, path, entityId, status]);
513
477
 
514
- const {
515
- values,
516
- onDiscard,
517
- onModified,
518
- onValuesChanged,
519
- underlyingChanges,
520
- formContext,
521
- entity,
522
- touched,
523
- setFieldValue,
524
- resolvedCollection,
525
- isSubmitting,
526
- status,
527
- handleSubmit,
528
- resetForm,
529
- savingError,
530
- dirty,
531
- closeAfterSaveRef,
532
- autoSave,
533
- entityActions
534
- } = props;
535
-
536
- const context = useFireCMSContext();
537
- const formActions = entityActions.filter(a => a.includeInForm === undefined || a.includeInForm);
538
- const sideEntityController = useSideEntityController();
478
+ useOnAutoSave(autoSave, formex, lastSavedValues, save);
539
479
 
540
- const modified = dirty;
541
480
  useEffect(() => {
542
- if (onModified)
543
- onModified(modified);
544
- if (onValuesChanged)
545
- onValuesChanged(values);
546
- }, [modified, values]);
547
-
548
- useEffect(() => {
549
- if (!autoSave && !isSubmitting && underlyingChanges && entity) {
481
+ if (!autoSave && !formex.isSubmitting && underlyingChanges && entity) {
550
482
  // we update the form fields from the Firestore data
551
483
  // if they were not touched
552
484
  Object.entries(underlyingChanges).forEach(([key, value]) => {
553
- const formValue = values[key];
554
- if (!equal(value, formValue) && !touched[key]) {
485
+ const formValue = formex.values[key];
486
+ if (!equal(value, formValue) && !formex.touched[key]) {
555
487
  console.debug("Updated value from the datasource:", key, value);
556
- setFieldValue(key, value !== undefined ? value : null);
488
+ formex.setFieldValue(key, value !== undefined ? value : null);
557
489
  }
558
490
  });
559
491
  }
560
- }, [isSubmitting, autoSave, underlyingChanges, entity, values, touched, setFieldValue]);
492
+ }, [formex.isSubmitting, autoSave, underlyingChanges, entity, formex.values, formex.touched, formex.setFieldValue]);
561
493
 
562
- const formFields = (
563
- <div className={"flex flex-col gap-8"}>
564
- {(resolvedCollection.propertiesOrder ?? Object.keys(resolvedCollection.properties))
565
- .map((key) => {
566
-
567
- const property = resolvedCollection.properties[key];
568
- if (!property) {
569
- console.warn(`Property ${key} not found in collection ${resolvedCollection.name}`);
570
- return null;
571
- }
494
+ const formFieldKeys = getFormFieldKeys(resolvedCollection);
572
495
 
496
+ const formFields = () => (
497
+ <FormLayout>
498
+ {formFieldKeys.map((key) => {
499
+ const property = resolvedCollection.properties[key];
500
+ if (property) {
573
501
  const underlyingValueHasChanged: boolean =
574
502
  !!underlyingChanges &&
575
503
  Object.keys(underlyingChanges).includes(key) &&
576
- !!touched[key];
577
-
578
- const disabled = (!autoSave && isSubmitting) || isReadOnly(property) || Boolean(property.disabled);
504
+ formex.touched[key];
505
+ const disabled = (!autoSave && formex.isSubmitting) || isReadOnly(property) || Boolean(property.disabled);
579
506
  const hidden = isHidden(property);
580
507
  if (hidden) return null;
508
+ const widthPercentage = property.widthPercentage ?? 100;
581
509
  const cmsFormFieldProps: PropertyFieldBindingProps<any, M> = {
582
510
  propertyKey: key,
583
511
  disabled,
@@ -585,125 +513,143 @@ function InnerForm<M extends Record<string, any>>(props: FormexController<M> & {
585
513
  includeDescription: property.description || property.longDescription,
586
514
  underlyingValueHasChanged: underlyingValueHasChanged && !autoSave,
587
515
  context: formContext,
588
- tableMode: false,
589
516
  partOfArray: false,
590
- partOfBlock: false,
517
+ minimalistView: false,
591
518
  autoFocus: false
592
519
  };
593
520
 
594
521
  return (
595
- <div id={`form_field_${key}`}
596
- key={`field_${resolvedCollection.name}_${key}`}>
597
- <ErrorBoundary>
598
- <Tooltip title={<PropertyIdCopyTooltipContent propertyId={key}/>}
599
- delayDuration={800}
600
- side={"left"}
601
- sideOffset={16}>
602
- <PropertyFieldBinding {...cmsFormFieldProps}/>
603
- </Tooltip>
604
- </ErrorBoundary>
522
+ <FormEntry propertyKey={key}
523
+ widthPercentage={widthPercentage}
524
+ key={`field_${key}`}>
525
+ <PropertyFieldBinding {...cmsFormFieldProps} />
526
+ </FormEntry>
527
+ );
528
+ }
529
+
530
+ const additionalField = resolvedCollection.additionalFields?.find(f => f.key === key);
531
+ if (additionalField && entity) {
532
+ const Builder = additionalField.Builder;
533
+ if (!Builder && !additionalField.value) {
534
+ throw new Error("When using additional fields you need to provide a Builder or a value");
535
+ }
536
+ const child = Builder
537
+ ? <Builder entity={entity} context={context}/>
538
+ : <div className={"w-full"}>
539
+ {additionalField.value?.({
540
+ entity,
541
+ context
542
+ })?.toString()}
543
+ </div>;
544
+
545
+ return (
546
+ <div key={`additional_${key}`} className={"w-full"}>
547
+ <LabelWithIconAndTooltip
548
+ propertyKey={key}
549
+ icon={<NotesIcon size={"small"}/>}
550
+ title={additionalField.name}
551
+ className={"text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>
552
+ <div
553
+ className={cls(paperMixin, "w-full min-h-14 p-4 md:p-6 overflow-x-scroll no-scrollbar")}>
554
+ <ErrorBoundary>
555
+ {child}
556
+ </ErrorBoundary>
557
+ </div>
605
558
  </div>
606
559
  );
607
- })
608
- .filter(Boolean)}
560
+ }
609
561
 
610
- </div>
562
+ console.warn(`Property ${key} not found in collection ${resolvedCollection.name} in properties or additional fields. Skipping.`);
563
+ return null;
564
+ }).filter(Boolean)}
565
+ </FormLayout>
611
566
  );
612
567
 
613
- const disabled = isSubmitting || (!modified && status === "existing");
614
- const formRef = React.useRef<HTMLDivElement>(null);
615
-
616
- return (
568
+ const formRef = useRef<HTMLDivElement>(null);
569
+
570
+ const formView = <ErrorBoundary>
571
+ <>
572
+ <div className={"w-full py-2 flex flex-col items-start mt-4 lg:mt-8 mb-8"}>
573
+ <Typography
574
+ className={"py-4 flex-grow line-clamp-1 " + (collection.hideIdFromForm ? "mb-2" : "mb-0")}
575
+ variant={"h4"}>
576
+ {title ?? collection.singularName ?? collection.name}
577
+ </Typography>
578
+ {showEntityPath && <Alert color={"base"} className={"w-full"} size={"small"}>
579
+ <code
580
+ className={"text-xs select-all text-text-secondary dark:text-text-secondary-dark"}>
581
+ {entity?.path ?? path}/{entityId}
582
+ </code>
583
+ </Alert>}
584
+ </div>
617
585
 
618
- <form onSubmit={handleSubmit}
619
- onReset={() => {
620
- console.debug("Resetting form")
621
- resetForm();
622
- return onDiscard && onDiscard();
623
- }}
624
- noValidate>
625
- <div className="mt-12"
626
- ref={formRef}>
586
+ {!collection.hideIdFromForm &&
587
+ <CustomIdField customId={collection.customId}
588
+ entityId={entityId}
589
+ status={status}
590
+ onChange={setEntityId}
591
+ error={entityIdError}
592
+ loading={customIdLoading}
593
+ entity={entity}/>
594
+ }
627
595
 
628
- {formFields}
596
+ {entityId && formContext && <>
597
+ <div className="mt-12 flex flex-col gap-8" ref={formRef}>
598
+ {formFields()}
599
+ <ErrorFocus containerRef={formRef}/>
600
+ </div>
601
+ </>}
629
602
 
630
- <ErrorFocus containerRef={formRef}/>
603
+ {forceActionsAtTheBottom && <div className="h-16"/>}
604
+ </>
605
+ </ErrorBoundary>;
631
606
 
632
- </div>
607
+ useEffect(() => {
608
+ if (entityId && onIdChange)
609
+ onIdChange(entityId);
610
+ }, [entityId, onIdChange]);
633
611
 
634
- <div className="h-14"/>
635
-
636
- {!autoSave && <DialogActions position={"absolute"}>
637
-
638
- {savingError &&
639
- <div className="text-right">
640
- <Typography color={"error"}>
641
- {savingError.message}
642
- </Typography>
643
- </div>}
644
-
645
- {entity && formActions.length > 0 && <div className="flex-grow flex overflow-auto no-scrollbar">
646
- {formActions.map(action => (
647
- <IconButton
648
- key={action.name}
649
- color="primary"
650
- onClick={(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
651
- event.stopPropagation();
652
- if (entity)
653
- action.onClick({
654
- entity,
655
- fullPath: resolvedCollection.path,
656
- collection: resolvedCollection,
657
- context,
658
- sideEntityController
659
- });
660
- }}>
661
- {action.icon}
662
- </IconButton>
663
- ))}
664
- </div>}
665
-
666
- <Button
667
- variant="text"
668
- disabled={disabled}
669
- type="reset"
670
- >
671
- {status === "existing" ? "Discard" : "Clear"}
672
- </Button>
673
-
674
- <Button
675
- variant="text"
676
- color="primary"
677
- type="submit"
678
- disabled={disabled}
679
- onClick={() => {
680
- closeAfterSaveRef.current = false;
681
- }}
682
- >
683
- {status === "existing" && "Save"}
684
- {status === "copy" && "Create copy"}
685
- {status === "new" && "Create"}
686
- </Button>
687
-
688
- <Button
689
- variant="filled"
690
- color="primary"
691
- type="submit"
692
- disabled={disabled}
693
- onClick={() => {
694
- closeAfterSaveRef.current = true;
695
- }}
696
- >
697
- {status === "existing" && "Save and close"}
698
- {status === "copy" && "Create copy and close"}
699
- {status === "new" && "Create and close"}
700
- </Button>
701
-
702
- </DialogActions>}
703
- </form>
612
+ return (
613
+ <FormLayoutInner
614
+ className={className}
615
+ id={`form_${path}`}
616
+ pluginActions={pluginActions}
617
+ forceActionsAtTheBottom={forceActionsAtTheBottom}
618
+ EntityFormActionsComponent={EntityFormActionsComponent}
619
+ formContext={formContext}
620
+ showDefaultActions={showDefaultActions}>
621
+ {formView}
622
+ </FormLayoutInner>
704
623
  );
705
624
  }
706
625
 
626
+ function getInitialEntityValues<M extends object>(
627
+ collection: EntityCollection,
628
+ path: string,
629
+ status: "new" | "existing" | "copy",
630
+ entity: Entity<M> | undefined,
631
+ propertyConfigs?: Record<string, PropertyConfig>
632
+ ): Partial<EntityValues<M>> {
633
+ const resolvedCollection = resolveCollection({
634
+ collection,
635
+ path,
636
+ values: entity?.values,
637
+ propertyConfigs
638
+ });
639
+ const properties = resolvedCollection.properties;
640
+ if ((status === "existing" || status === "copy") && entity) {
641
+ return entity.values ?? getDefaultValuesFor(properties);
642
+ } else if (status === "new") {
643
+ return getDefaultValuesFor(properties);
644
+ } else {
645
+ console.error({
646
+ status,
647
+ entity
648
+ });
649
+ throw new Error("Form has not been initialised with the correct parameters");
650
+ }
651
+ }
652
+
707
653
  export function yupToFormErrors(yupError: ValidationError): Record<string, any> {
708
654
  let errors: Record<string, any> = {};
709
655
  if (yupError.inner) {
@@ -718,3 +664,103 @@ export function yupToFormErrors(yupError: ValidationError): Record<string, any>
718
664
  }
719
665
  return errors;
720
666
  }
667
+
668
+ export function FormLayoutInner({
669
+ id,
670
+ formContext,
671
+ children,
672
+ className,
673
+ forceActionsAtTheBottom,
674
+ pluginActions,
675
+ EntityFormActionsComponent,
676
+ showDefaultActions,
677
+ }: {
678
+ id?: string,
679
+ formContext: FormContext,
680
+ children: React.ReactNode,
681
+ className?: string,
682
+ forceActionsAtTheBottom?: boolean,
683
+ pluginActions?: React.ReactNode[],
684
+ EntityFormActionsComponent: React.FC<EntityFormActionsProps>;
685
+ showDefaultActions?: boolean;
686
+ }) {
687
+
688
+ const formex = formContext.formex;
689
+ const collection = formContext.collection;
690
+ const path = formContext.path;
691
+ const entity = formContext.entity;
692
+ const savingError = formContext.savingError;
693
+ const status = formContext.status;
694
+ const openEntityMode = formContext.openEntityMode;
695
+ const disabled = formex.isSubmitting || (!formex.dirty && status === "existing");
696
+
697
+ const customizationController = useCustomizationController();
698
+
699
+ if (!collection || !path) {
700
+ throw Error("INTERNAL: Collection and path must be defined in form context");
701
+ }
702
+
703
+ const dialogActions = <EntityFormActionsComponent
704
+ collection={collection}
705
+ path={path}
706
+ entity={entity}
707
+ layout={forceActionsAtTheBottom ? "bottom" : "side"}
708
+ savingError={savingError}
709
+ formex={formex}
710
+ disabled={disabled}
711
+ status={status}
712
+ pluginActions={pluginActions ?? []}
713
+ openEntityMode={openEntityMode}
714
+ showDefaultActions={showDefaultActions}
715
+ />;
716
+
717
+ return (
718
+ <Formex value={formContext.formex}>
719
+ <form
720
+ onSubmit={formContext.formex.handleSubmit}
721
+ onReset={() => formex.resetForm({
722
+ values: getInitialEntityValues(collection, path, status, entity, customizationController.propertyConfigs),
723
+ })}
724
+ noValidate
725
+ className={cls("flex-1 flex flex-row w-full overflow-y-auto justify-center", className)}>
726
+ <div
727
+ id={id}
728
+ className={cls("relative flex flex-row max-w-4xl lg:max-w-3xl xl:max-w-4xl 2xl:max-w-6xl w-full h-fit")}>
729
+
730
+ <div className={cls("flex flex-col w-full pt-12 pb-16 px-4 sm:px-8 md:px-10")}>
731
+
732
+ {formContext.formex.dirty
733
+ ? <Tooltip title={"Unsaved changes"}
734
+ className={"self-end sticky top-4 z-10"}>
735
+ <Chip size={"small"} colorScheme={"orangeDarker"}>
736
+ <EditIcon size={"smallest"}/>
737
+ </Chip>
738
+ </Tooltip>
739
+ : <Tooltip title={"In sync with the database"}
740
+ className={"self-end sticky top-4 z-10"}>
741
+ <Chip size={"small"}>
742
+ <CheckIcon size={"smallest"}/>
743
+ </Chip>
744
+ </Tooltip>}
745
+ {children}
746
+
747
+ </div>
748
+
749
+ </div>
750
+ {dialogActions}
751
+ </form>
752
+
753
+ </Formex>
754
+ );
755
+ }
756
+
757
+ function useOnAutoSave(autoSave: undefined | boolean, formex: FormexController<any>, lastSavedValues: any, save: (values: EntityValues<any>) => Promise<void>) {
758
+ if (!autoSave) return;
759
+ useEffect(() => {
760
+ if (autoSave) {
761
+ if (formex.values && !equal(formex.values, lastSavedValues.current)) {
762
+ save(formex.values);
763
+ }
764
+ }
765
+ }, [formex.values]);
766
+ }