@firecms/core 3.0.0-canary.25 → 3.0.0-canary.250

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