@firecms/core 3.0.0-beta.1 → 3.0.0-beta.10

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