@firecms/core 3.0.0-canary.18 → 3.0.0-canary.182

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