@firecms/core 3.0.0-canary.99 → 3.0.0-rc.2

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