@firecms/core 3.0.0-canary.17 → 3.0.0-canary.171

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