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

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 (387) 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 +19 -8
  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 +9 -15
  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 +8 -5
  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 +5 -2
  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 +2 -2
  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/{components → form/components}/LabelWithIcon.d.ts +1 -1
  64. package/dist/form/components/LabelWithIconAndTooltip.d.ts +15 -0
  65. package/dist/form/components/StorageItemPreview.d.ts +4 -3
  66. package/dist/form/components/StorageUploadProgress.d.ts +1 -1
  67. package/dist/form/components/index.d.ts +2 -2
  68. package/dist/form/field_bindings/ArrayCustomShapedFieldBinding.d.ts +1 -1
  69. package/dist/form/field_bindings/ArrayOfReferencesFieldBinding.d.ts +1 -1
  70. package/dist/form/field_bindings/BlockFieldBinding.d.ts +1 -1
  71. package/dist/form/field_bindings/KeyValueFieldBinding.d.ts +1 -1
  72. package/dist/form/field_bindings/MapFieldBinding.d.ts +1 -1
  73. package/dist/form/field_bindings/MarkdownEditorFieldBinding.d.ts +11 -0
  74. package/dist/form/field_bindings/{MultiSelectBinding.d.ts → MultiSelectFieldBinding.d.ts} +1 -1
  75. package/dist/form/field_bindings/ReadOnlyFieldBinding.d.ts +1 -1
  76. package/dist/form/field_bindings/ReferenceFieldBinding.d.ts +2 -2
  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/SwitchFieldBinding.d.ts +1 -2
  81. package/dist/form/field_bindings/TextFieldBinding.d.ts +2 -2
  82. package/dist/form/index.d.ts +4 -5
  83. package/dist/form/useClearRestoreValue.d.ts +2 -2
  84. package/dist/form/validation.d.ts +1 -1
  85. package/dist/hooks/data/delete.d.ts +4 -4
  86. package/dist/hooks/data/save.d.ts +4 -5
  87. package/dist/hooks/data/useCollectionFetch.d.ts +1 -1
  88. package/dist/hooks/data/useDataSource.d.ts +2 -2
  89. package/dist/hooks/data/useEntityFetch.d.ts +3 -3
  90. package/dist/hooks/index.d.ts +5 -1
  91. package/dist/hooks/useAuthController.d.ts +1 -1
  92. package/dist/{core → hooks}/useBuildModeController.d.ts +1 -1
  93. package/dist/hooks/useBuildNavigationController.d.ts +23 -0
  94. package/dist/hooks/useFireCMSContext.d.ts +1 -1
  95. package/dist/hooks/useProjectLog.d.ts +14 -0
  96. package/dist/hooks/useResolvedNavigationFrom.d.ts +3 -3
  97. package/dist/hooks/useSnackbarController.d.ts +1 -0
  98. package/dist/hooks/useStorageSource.d.ts +2 -2
  99. package/dist/hooks/useValidateAuthenticator.d.ts +21 -0
  100. package/dist/index.d.ts +1 -0
  101. package/dist/index.es.js +20246 -13863
  102. package/dist/index.es.js.map +1 -1
  103. package/dist/index.umd.js +22772 -588
  104. package/dist/index.umd.js.map +1 -1
  105. package/dist/internal/useBuildDataSource.d.ts +1 -12
  106. package/dist/preview/PropertyPreview.d.ts +1 -1
  107. package/dist/preview/PropertyPreviewProps.d.ts +7 -5
  108. package/dist/preview/components/BooleanPreview.d.ts +5 -1
  109. package/dist/preview/components/EnumValuesChip.d.ts +1 -1
  110. package/dist/preview/components/ReferencePreview.d.ts +4 -9
  111. package/dist/preview/components/StorageThumbnail.d.ts +2 -1
  112. package/dist/preview/components/UrlComponentPreview.d.ts +2 -1
  113. package/dist/preview/util.d.ts +3 -3
  114. package/dist/types/analytics.d.ts +1 -1
  115. package/dist/types/auth.d.ts +37 -3
  116. package/dist/types/collections.d.ts +68 -20
  117. package/dist/types/datasource.d.ts +55 -39
  118. package/dist/types/entities.d.ts +7 -1
  119. package/dist/types/entity_actions.d.ts +18 -4
  120. package/dist/types/entity_callbacks.d.ts +17 -17
  121. package/dist/types/entity_overrides.d.ts +6 -0
  122. package/dist/types/export_import.d.ts +4 -4
  123. package/dist/types/fields.d.ts +63 -42
  124. package/dist/types/firecms.d.ts +10 -25
  125. package/dist/types/firecms_context.d.ts +1 -1
  126. package/dist/types/index.d.ts +2 -1
  127. package/dist/types/navigation.d.ts +18 -16
  128. package/dist/types/permissions.d.ts +8 -4
  129. package/dist/types/plugins.d.ts +39 -39
  130. package/dist/types/properties.d.ts +36 -23
  131. package/dist/types/property_config.d.ts +2 -4
  132. package/dist/types/roles.d.ts +31 -0
  133. package/dist/types/side_dialogs_controller.d.ts +10 -0
  134. package/dist/types/storage.d.ts +86 -3
  135. package/dist/types/user.d.ts +6 -0
  136. package/dist/util/builders.d.ts +2 -2
  137. package/dist/util/collections.d.ts +9 -1
  138. package/dist/util/entities.d.ts +3 -3
  139. package/dist/util/icon_list.d.ts +5 -1
  140. package/dist/util/icon_synonyms.d.ts +1 -98
  141. package/dist/util/icons.d.ts +13 -4
  142. package/dist/util/index.d.ts +3 -1
  143. package/dist/util/navigation_utils.d.ts +2 -2
  144. package/dist/util/objects.d.ts +2 -1
  145. package/dist/util/permissions.d.ts +4 -4
  146. package/dist/util/plurals.d.ts +0 -2
  147. package/dist/util/property_utils.d.ts +3 -3
  148. package/dist/util/references.d.ts +4 -2
  149. package/dist/util/resolutions.d.ts +31 -17
  150. package/dist/util/storage.d.ts +23 -2
  151. package/dist/util/strings.d.ts +1 -1
  152. package/dist/util/useDebouncedCallback.d.ts +1 -0
  153. package/dist/util/useStorageUploadController.d.ts +3 -3
  154. package/dist/util/useTraceUpdate.d.ts +1 -0
  155. package/package.json +70 -47
  156. package/src/app/AppBar.tsx +18 -0
  157. package/src/app/Drawer.tsx +24 -0
  158. package/src/app/Scaffold.tsx +243 -0
  159. package/src/app/index.ts +4 -0
  160. package/src/app/useApp.tsx +32 -0
  161. package/src/components/ArrayContainer.tsx +122 -90
  162. package/src/components/CircularProgressCenter.tsx +2 -2
  163. package/src/components/ClearFilterSortButton.tsx +41 -0
  164. package/src/components/{DeleteConfirmationDialog.tsx → ConfirmationDialog.tsx} +3 -3
  165. package/src/components/DeleteEntityDialog.tsx +13 -22
  166. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +51 -59
  167. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +258 -453
  168. package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +13 -14
  169. package/src/components/EntityCollectionTable/PropertyTableCell.tsx +60 -52
  170. package/src/components/EntityCollectionTable/column_utils.tsx +4 -4
  171. package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +40 -33
  172. package/src/components/EntityCollectionTable/fields/TableStorageUpload.tsx +22 -20
  173. package/src/components/EntityCollectionTable/index.tsx +1 -9
  174. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +44 -46
  175. package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +36 -34
  176. package/src/components/EntityCollectionTable/internal/EntityTableCellActions.tsx +20 -8
  177. package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +191 -178
  178. package/src/components/EntityCollectionView/EntityCollectionView.tsx +187 -175
  179. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +5 -6
  180. package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +68 -0
  181. package/src/components/EntityCollectionView/useSelectionController.tsx +43 -0
  182. package/src/components/EntityCollectionView/utils.ts +19 -0
  183. package/src/components/EntityPreview.tsx +218 -72
  184. package/src/components/EntityView.tsx +84 -0
  185. package/src/components/ErrorView.tsx +4 -4
  186. package/src/components/FieldCaption.tsx +14 -0
  187. package/src/components/HomePage/DefaultHomePage.tsx +39 -28
  188. package/src/components/HomePage/FavouritesView.tsx +6 -11
  189. package/src/components/HomePage/NavigationCard.tsx +69 -0
  190. package/src/components/HomePage/NavigationCardBinding.tsx +111 -0
  191. package/src/components/HomePage/NavigationGroup.tsx +2 -2
  192. package/src/components/HomePage/SmallNavigationCard.tsx +44 -0
  193. package/src/components/HomePage/index.tsx +3 -1
  194. package/src/components/PropertyConfigBadge.tsx +23 -0
  195. package/src/components/PropertyIdCopyTooltip.tsx +48 -0
  196. package/src/components/{ReferenceSelectionInner.tsx → ReferenceTable/ReferenceSelectionTable.tsx} +38 -24
  197. package/src/components/ReferenceWidget.tsx +26 -16
  198. package/src/components/SearchIconsView.tsx +10 -7
  199. package/src/components/{EntityCollectionTable/SimpleEntityCollectionTable.tsx → SelectableTable/SelectableTable.tsx} +46 -84
  200. package/src/components/SelectableTable/SelectableTableContext.tsx +6 -0
  201. package/src/components/{EntityCollectionTable → SelectableTable}/filters/BooleanFilterField.tsx +2 -3
  202. package/src/components/{EntityCollectionTable → SelectableTable}/filters/DateTimeFilterField.tsx +25 -8
  203. package/src/components/{EntityCollectionTable → SelectableTable}/filters/ReferenceFilterField.tsx +40 -24
  204. package/src/components/{EntityCollectionTable → SelectableTable}/filters/StringNumberFilterField.tsx +92 -24
  205. package/src/components/VirtualTable/VirtualTable.tsx +77 -46
  206. package/src/components/VirtualTable/VirtualTableCell.tsx +1 -9
  207. package/src/components/VirtualTable/VirtualTableHeader.tsx +19 -13
  208. package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +2 -2
  209. package/src/components/VirtualTable/VirtualTableProps.tsx +19 -15
  210. package/src/components/VirtualTable/VirtualTableRow.tsx +5 -6
  211. package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +5 -5
  212. package/src/components/VirtualTable/fields/VirtualTableInput.tsx +4 -3
  213. package/src/components/VirtualTable/fields/VirtualTableNumberInput.tsx +4 -3
  214. package/src/components/VirtualTable/fields/VirtualTableSelect.tsx +16 -28
  215. package/src/components/VirtualTable/types.tsx +2 -3
  216. package/src/components/{EntityCollectionTable/internal → common}/default_entity_actions.tsx +17 -9
  217. package/src/components/common/index.ts +6 -0
  218. package/src/components/{VirtualTable/common.tsx → common/table_height.tsx} +5 -2
  219. package/src/components/{EntityCollectionTable → common}/types.tsx +9 -6
  220. package/src/components/{EntityCollectionView → common}/useColumnsIds.tsx +23 -2
  221. package/src/components/{EntityCollectionTable → common}/useDataSourceEntityCollectionTableController.tsx +27 -16
  222. package/src/components/common/useTableSearchHelper.ts +88 -0
  223. package/src/components/index.tsx +15 -8
  224. package/src/contexts/AuthControllerContext.tsx +1 -1
  225. package/src/contexts/DialogsProvider.tsx +2 -2
  226. package/src/contexts/SnackbarProvider.tsx +2 -0
  227. package/src/core/DefaultAppBar.tsx +177 -0
  228. package/src/core/DefaultDrawer.tsx +185 -0
  229. package/src/core/DrawerNavigationItem.tsx +66 -0
  230. package/src/core/EntityEditView.tsx +1125 -0
  231. package/src/core/EntitySidePanel.tsx +9 -8
  232. package/src/core/FireCMS.tsx +71 -63
  233. package/src/core/NavigationRoutes.tsx +25 -17
  234. package/src/core/SideDialogs.tsx +2 -1
  235. package/src/core/field_configs.tsx +22 -23
  236. package/src/core/index.tsx +3 -6
  237. package/src/form/PropertyFieldBinding.tsx +79 -45
  238. package/src/form/components/CustomIdField.tsx +53 -36
  239. package/src/form/components/ErrorFocus.tsx +3 -3
  240. package/src/form/components/FieldHelperText.tsx +4 -4
  241. package/src/form/components/LabelWithIcon.tsx +41 -0
  242. package/src/form/components/LabelWithIconAndTooltip.tsx +28 -0
  243. package/src/form/components/StorageItemPreview.tsx +23 -11
  244. package/src/form/components/StorageUploadProgress.tsx +8 -8
  245. package/src/form/components/index.tsx +2 -2
  246. package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +30 -19
  247. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +52 -46
  248. package/src/form/field_bindings/BlockFieldBinding.tsx +67 -47
  249. package/src/form/field_bindings/DateTimeFieldBinding.tsx +20 -17
  250. package/src/form/field_bindings/KeyValueFieldBinding.tsx +76 -67
  251. package/src/form/field_bindings/MapFieldBinding.tsx +81 -60
  252. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +150 -0
  253. package/src/form/field_bindings/{MultiSelectBinding.tsx → MultiSelectFieldBinding.tsx} +26 -22
  254. package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +12 -18
  255. package/src/form/field_bindings/ReferenceFieldBinding.tsx +46 -34
  256. package/src/form/field_bindings/RepeatFieldBinding.tsx +55 -35
  257. package/src/form/field_bindings/SelectFieldBinding.tsx +24 -17
  258. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +42 -43
  259. package/src/form/field_bindings/SwitchFieldBinding.tsx +30 -27
  260. package/src/form/field_bindings/TextFieldBinding.tsx +34 -30
  261. package/src/form/index.tsx +5 -9
  262. package/src/form/useClearRestoreValue.tsx +2 -2
  263. package/src/form/validation.ts +4 -21
  264. package/src/hooks/data/delete.ts +7 -6
  265. package/src/hooks/data/save.ts +12 -8
  266. package/src/hooks/data/useCollectionFetch.tsx +3 -3
  267. package/src/hooks/data/useDataSource.tsx +15 -2
  268. package/src/hooks/data/useEntityFetch.tsx +6 -6
  269. package/src/hooks/index.tsx +7 -1
  270. package/src/hooks/useAuthController.tsx +1 -1
  271. package/src/{core → hooks}/useBuildLocalConfigurationPersistence.tsx +8 -10
  272. package/src/{core → hooks}/useBuildModeController.tsx +12 -6
  273. package/src/{internal → hooks}/useBuildNavigationController.tsx +245 -101
  274. package/src/hooks/useFireCMSContext.tsx +53 -5
  275. package/src/hooks/useLargeLayout.tsx +10 -7
  276. package/src/hooks/useProjectLog.tsx +63 -0
  277. package/src/hooks/useReferenceDialog.tsx +3 -3
  278. package/src/hooks/useResolvedNavigationFrom.tsx +6 -6
  279. package/src/hooks/useSnackbarController.tsx +1 -2
  280. package/src/hooks/useStorageSource.tsx +7 -2
  281. package/src/hooks/useValidateAuthenticator.tsx +115 -0
  282. package/src/index.ts +1 -0
  283. package/src/internal/useBuildDataSource.ts +80 -85
  284. package/src/internal/useBuildSideDialogsController.tsx +1 -0
  285. package/src/internal/useBuildSideEntityController.tsx +103 -23
  286. package/src/internal/useUnsavedChangesDialog.tsx +5 -3
  287. package/src/preview/PropertyPreview.tsx +15 -22
  288. package/src/preview/PropertyPreviewProps.tsx +6 -10
  289. package/src/preview/components/BooleanPreview.tsx +19 -4
  290. package/src/preview/components/EmptyValue.tsx +1 -1
  291. package/src/preview/components/EnumValuesChip.tsx +2 -2
  292. package/src/preview/components/ImagePreview.tsx +25 -36
  293. package/src/preview/components/ReferencePreview.tsx +73 -176
  294. package/src/preview/components/StorageThumbnail.tsx +5 -1
  295. package/src/preview/components/UrlComponentPreview.tsx +44 -11
  296. package/src/preview/property_previews/ArrayOfMapsPreview.tsx +2 -3
  297. package/src/preview/property_previews/ArrayOfReferencesPreview.tsx +4 -3
  298. package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +2 -3
  299. package/src/preview/property_previews/ArrayOfStringsPreview.tsx +1 -2
  300. package/src/preview/property_previews/ArrayOneOfPreview.tsx +4 -5
  301. package/src/preview/property_previews/ArrayPropertyPreview.tsx +4 -5
  302. package/src/preview/property_previews/MapPropertyPreview.tsx +9 -9
  303. package/src/preview/property_previews/SkeletonPropertyComponent.tsx +12 -12
  304. package/src/preview/property_previews/StringPropertyPreview.tsx +8 -7
  305. package/src/preview/util.ts +10 -10
  306. package/src/types/analytics.ts +1 -0
  307. package/src/types/auth.tsx +48 -3
  308. package/src/types/collections.ts +77 -20
  309. package/src/types/customization_controller.tsx +0 -1
  310. package/src/types/datasource.ts +64 -47
  311. package/src/types/entities.ts +15 -1
  312. package/src/types/entity_actions.tsx +21 -4
  313. package/src/types/entity_callbacks.ts +20 -19
  314. package/src/types/entity_overrides.tsx +7 -0
  315. package/src/types/export_import.ts +4 -4
  316. package/src/types/fields.tsx +69 -45
  317. package/src/types/firecms.tsx +11 -28
  318. package/src/types/firecms_context.tsx +1 -1
  319. package/src/types/index.ts +2 -1
  320. package/src/types/navigation.ts +20 -19
  321. package/src/types/permissions.ts +10 -5
  322. package/src/types/plugins.tsx +46 -48
  323. package/src/types/properties.ts +42 -24
  324. package/src/types/property_config.tsx +2 -4
  325. package/src/types/roles.ts +41 -0
  326. package/src/types/side_dialogs_controller.tsx +13 -0
  327. package/src/types/side_entity_controller.tsx +1 -0
  328. package/src/types/storage.ts +94 -3
  329. package/src/types/user.ts +9 -0
  330. package/src/util/builders.ts +6 -6
  331. package/src/util/collections.ts +22 -0
  332. package/src/util/entities.ts +9 -6
  333. package/src/util/enums.ts +1 -1
  334. package/src/util/icon_list.ts +16 -10
  335. package/src/util/icon_synonyms.ts +3 -100
  336. package/src/util/icons.tsx +26 -8
  337. package/src/util/index.ts +3 -1
  338. package/src/util/make_properties_editable.ts +13 -5
  339. package/src/util/navigation_utils.ts +6 -6
  340. package/src/util/objects.ts +45 -28
  341. package/src/util/permissions.ts +20 -16
  342. package/src/util/plurals.ts +0 -2
  343. package/src/util/property_utils.tsx +11 -4
  344. package/src/util/references.ts +45 -5
  345. package/src/util/resolutions.ts +80 -47
  346. package/src/util/storage.ts +79 -21
  347. package/src/util/strings.ts +4 -3
  348. package/src/util/{useDebounce.ts → useDebouncedCallback.ts} +1 -1
  349. package/src/util/useStorageUploadController.tsx +34 -8
  350. package/src/util/useTraceUpdate.tsx +2 -1
  351. package/dist/components/VirtualTable/common.d.ts +0 -2
  352. package/dist/core/Drawer.d.ts +0 -23
  353. package/dist/core/Scaffold.d.ts +0 -52
  354. package/dist/form/EntityForm.d.ts +0 -75
  355. package/dist/form/components/FormikArrayContainer.d.ts +0 -18
  356. package/dist/form/components/SubmitListener.d.ts +0 -1
  357. package/dist/form/components/SwitchControl.d.ts +0 -8
  358. package/dist/form/field_bindings/MarkdownFieldBinding.d.ts +0 -9
  359. package/dist/hooks/useVerifyLicense.d.ts +0 -0
  360. package/dist/internal/EntityView.d.ts +0 -22
  361. package/dist/internal/useBuildCustomizationController.d.ts +0 -2
  362. package/dist/internal/useBuildNavigationController.d.ts +0 -14
  363. package/dist/internal/useLocaleConfig.d.ts +0 -1
  364. package/dist/types/appcheck.d.ts +0 -26
  365. package/dist/util/useDebounce.d.ts +0 -1
  366. package/src/components/FieldConfigBadge.tsx +0 -17
  367. package/src/components/FireCMSAppBar.tsx +0 -157
  368. package/src/components/HomePage/NavigationCollectionCard.tsx +0 -146
  369. package/src/components/LabelWithIcon.tsx +0 -33
  370. package/src/core/Drawer.tsx +0 -162
  371. package/src/core/Scaffold.tsx +0 -278
  372. package/src/form/EntityForm.tsx +0 -657
  373. package/src/form/components/FormikArrayContainer.tsx +0 -44
  374. package/src/form/components/SubmitListener.tsx +0 -32
  375. package/src/form/components/SwitchControl.tsx +0 -39
  376. package/src/form/field_bindings/MarkdownFieldBinding.tsx +0 -696
  377. package/src/hooks/useVerifyLicense.tsx +0 -1
  378. package/src/internal/EntityView.tsx +0 -578
  379. package/src/internal/useBuildCustomizationController.tsx +0 -5
  380. package/src/internal/useLocaleConfig.tsx +0 -18
  381. package/src/types/appcheck.ts +0 -29
  382. /package/dist/components/{EntityCollectionTable → SelectableTable}/filters/BooleanFilterField.d.ts +0 -0
  383. /package/dist/components/{EntityCollectionTable → SelectableTable}/filters/DateTimeFilterField.d.ts +0 -0
  384. /package/dist/components/{EntityCollectionTable → SelectableTable}/filters/StringNumberFilterField.d.ts +0 -0
  385. /package/dist/components/{EntityCollectionTable → common}/useDebouncedData.d.ts +0 -0
  386. /package/dist/{core → hooks}/useBuildLocalConfigurationPersistence.d.ts +0 -0
  387. /package/src/components/{EntityCollectionTable → common}/useDebouncedData.ts +0 -0
@@ -0,0 +1,1125 @@
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>, USER 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, USER>({
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
+ propertyConfigs: 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
+ propertyConfigs: 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
+ : <>
782
+ {additionalField.value?.({
783
+ entity,
784
+ context
785
+ })?.toString()}
786
+ </>;
787
+ return (
788
+ <div key={`additional_${key}`}>
789
+ <LabelWithIconAndTooltip
790
+ propertyKey={key}
791
+ icon={<NotesIcon size={"small"}/>}
792
+ title={additionalField.name}
793
+ className={"text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>
794
+ <div
795
+ className={cls(paperMixin, "min-h-14 p-4 md:p-6 overflow-x-scroll no-scrollbar")}>
796
+
797
+ <ErrorBoundary>
798
+ {child}
799
+ </ErrorBoundary>
800
+
801
+ </div>
802
+ </div>
803
+ );
804
+ }
805
+
806
+ console.warn(`Property ${key} not found in collection ${resolvedCollection.name} in properties or additional fields. Skipping.`);
807
+ return null;
808
+ })
809
+ .filter(Boolean)}
810
+
811
+ </>);
812
+
813
+ const disabled = formex.isSubmitting || (!modified && status === "existing");
814
+ const formRef = React.useRef<HTMLDivElement>(null);
815
+
816
+ const entityActions = getActionsForEntity({
817
+ entity,
818
+ customEntityActions: inputCollection.entityActions
819
+ });
820
+ const formActions = entityActions.filter(a => a.includeInForm === undefined || a.includeInForm);
821
+
822
+ const dialogActions = <DialogActions position={"absolute"}>
823
+
824
+ {savingError &&
825
+ <div className="text-right">
826
+ <Typography color={"error"}>
827
+ {savingError.message}
828
+ </Typography>
829
+ </div>}
830
+
831
+ {entity && formActions.length > 0 && <div className="flex-grow flex overflow-auto no-scrollbar">
832
+ {formActions.map(action => (
833
+ <IconButton
834
+ key={action.name}
835
+ color="primary"
836
+ onClick={(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
837
+ event.stopPropagation();
838
+ if (entity)
839
+ action.onClick({
840
+ entity,
841
+ fullPath: resolvedCollection.path,
842
+ collection: resolvedCollection,
843
+ context,
844
+ sideEntityController
845
+ });
846
+ }}>
847
+ {action.icon}
848
+ </IconButton>
849
+ ))}
850
+ </div>}
851
+ {formex.isSubmitting && <CircularProgress size={"small"}/>}
852
+ <Button
853
+ variant="text"
854
+ disabled={disabled || formex.isSubmitting}
855
+ type="reset">
856
+ {status === "existing" ? "Discard" : "Clear"}
857
+ </Button>
858
+
859
+ <Button
860
+ variant="text"
861
+ color="primary"
862
+ type="submit"
863
+ disabled={disabled || formex.isSubmitting}
864
+ onClick={() => {
865
+ closeAfterSaveRef.current = false;
866
+ }}>
867
+ {status === "existing" && "Save"}
868
+ {status === "copy" && "Create copy"}
869
+ {status === "new" && "Create"}
870
+ </Button>
871
+
872
+ <Button
873
+ variant="filled"
874
+ color="primary"
875
+ type="submit"
876
+ disabled={disabled || formex.isSubmitting}
877
+ onClick={() => {
878
+ closeAfterSaveRef.current = true;
879
+ }}>
880
+ {status === "existing" && "Save and close"}
881
+ {status === "copy" && "Create copy and close"}
882
+ {status === "new" && "Create and close"}
883
+ </Button>
884
+
885
+ </DialogActions>;
886
+
887
+ function buildForm() {
888
+
889
+ let form = <>
890
+
891
+ {pluginActions.length > 0 && <div
892
+ className={cls("w-full flex justify-end items-center sticky top-0 right-0 left-0 z-10 bg-opacity-60 bg-surface-accent-200 dark:bg-opacity-60 dark:bg-surface-accent-800 backdrop-blur-md")}>
893
+ {pluginActions}
894
+ </div>}
895
+
896
+ <div className="pt-12 pb-16 pl-4 sm:px-8 md:px-10">
897
+ <div
898
+ className={`w-full py-2 flex flex-col items-start mt-${4 + (pluginActions ? 8 : 0)} lg:mt-${8 + (pluginActions ? 8 : 0)} mb-8`}>
899
+
900
+ <Typography
901
+ className={"mt-4 flex-grow line-clamp-1 " + inputCollection.hideIdFromForm ? "mb-2" : "mb-0"}
902
+ variant={"h4"}>{title ?? inputCollection.singularName ?? inputCollection.name}
903
+ </Typography>
904
+ <Alert color={"base"} className={"w-full"} size={"small"}>
905
+ <code className={"text-xs select-all"}>{path}/{entityId}</code>
906
+ </Alert>
907
+ </div>
908
+
909
+ {!collection.hideIdFromForm &&
910
+ <CustomIdField customId={inputCollection.customId}
911
+ entityId={entityId}
912
+ status={status}
913
+ onChange={setEntityId}
914
+ error={entityIdError}
915
+ loading={customIdLoading}
916
+ entity={entity}/>}
917
+
918
+ {entityId && formContext && <>
919
+ <div className="mt-12 flex flex-col gap-8"
920
+ ref={formRef}>
921
+
922
+ {formFields}
923
+
924
+ <ErrorFocus containerRef={formRef}/>
925
+
926
+ </div>
927
+
928
+ <div className="h-14"/>
929
+
930
+ </>}
931
+
932
+ </div>
933
+ </>;
934
+
935
+ if (plugins) {
936
+ plugins.forEach((plugin: FireCMSPlugin) => {
937
+ if (plugin.form?.provider) {
938
+ form = (
939
+ <plugin.form.provider.Component
940
+ status={status}
941
+ path={path}
942
+ collection={collection}
943
+ onDiscard={onDiscard}
944
+ entity={usedEntity}
945
+ context={context}
946
+ formContext={formContext}
947
+ {...plugin.form.provider.props}>
948
+ {form}
949
+ </plugin.form.provider.Component>
950
+ );
951
+ }
952
+ });
953
+ }
954
+ return <ErrorBoundary>{form}</ErrorBoundary>;
955
+ }
956
+
957
+ const entityView = (readOnly === undefined)
958
+ ? <></>
959
+ : (!readOnly
960
+ ? buildForm()
961
+ : (
962
+ <>
963
+ <Typography
964
+ className={"mt-16 mb-8 mx-8"}
965
+ variant={"h4"}>{collection.singularName ?? collection.name}
966
+ </Typography>
967
+ <EntityView
968
+ className={"px-12"}
969
+ entity={usedEntity as Entity<M>}
970
+ path={path}
971
+ collection={collection}/>
972
+
973
+ </>
974
+ ));
975
+
976
+ const subcollectionTabs = subcollections && subcollections.map(
977
+ (subcollection) =>
978
+ <Tab
979
+ className="text-sm min-w-[140px]"
980
+ value={subcollection.id}
981
+ key={`entity_detail_collection_tab_${subcollection.name}`}>
982
+ {subcollection.name}
983
+ </Tab>
984
+ );
985
+
986
+ const customViewTabs = resolvedEntityViews.map(
987
+ (view) =>
988
+
989
+ <Tab
990
+ className="text-sm min-w-[140px]"
991
+ value={view.key}
992
+ key={`entity_detail_collection_tab_${view.name}`}>
993
+ {view.name}
994
+ </Tab>
995
+ );
996
+
997
+ useEffect(() => {
998
+ if (entityId && onIdChange)
999
+ onIdChange(entityId);
1000
+ }, [entityId, onIdChange]);
1001
+
1002
+ return (
1003
+ <Formex value={formex}>
1004
+
1005
+ <div className="flex flex-col h-full w-full">
1006
+
1007
+ <div
1008
+ className={cls(defaultBorderMixin, "no-scrollbar h-16 border-b pl-2 pr-2 pt-1 flex items-end overflow-scroll bg-surface-50 dark:bg-surface-950")}>
1009
+
1010
+ <div
1011
+ className="pb-1 self-center">
1012
+ <IconButton
1013
+ onClick={() => {
1014
+ onClose?.();
1015
+ return sideDialogContext.close(false);
1016
+ }}>
1017
+ <CloseIcon size={"small"}/>
1018
+ </IconButton>
1019
+ </div>
1020
+
1021
+ <div className={"flex-grow"}/>
1022
+
1023
+ {globalLoading && <div
1024
+ className="self-center">
1025
+ <CircularProgress size={"small"}/>
1026
+ </div>}
1027
+
1028
+ <Tabs
1029
+ value={selectedTab}
1030
+ onValueChange={(value) => {
1031
+ onSideTabClick(value);
1032
+ }}
1033
+ innerClassName="pl-4 pr-4 pt-0">
1034
+
1035
+ <Tab
1036
+ disabled={!hasAdditionalViews}
1037
+ value={MAIN_TAB_VALUE}
1038
+ className={`${
1039
+ !hasAdditionalViews ? "hidden" : ""
1040
+ } text-sm min-w-[140px]`}
1041
+ >{collection.singularName ?? collection.name}</Tab>
1042
+
1043
+ {customViewTabs}
1044
+
1045
+ {subcollectionTabs}
1046
+ </Tabs>
1047
+
1048
+ </div>
1049
+
1050
+ <form
1051
+ onSubmit={formex.handleSubmit}
1052
+ onReset={() => {
1053
+ formex.resetForm();
1054
+ return onDiscard && onDiscard();
1055
+ }}
1056
+ noValidate
1057
+ className={"flex-grow h-full flex overflow-auto flex-col w-full"}>
1058
+
1059
+ <div
1060
+ role="tabpanel"
1061
+ hidden={!mainViewVisible}
1062
+ id={`form_${path}`}
1063
+ className={"relative w-full"}>
1064
+
1065
+ {globalLoading
1066
+ ? <CircularProgressCenter/>
1067
+ : entityView}
1068
+
1069
+ </div>
1070
+
1071
+ {customViewsView}
1072
+
1073
+ {subCollectionsViews}
1074
+
1075
+ {shouldShowEntityActions && dialogActions}
1076
+
1077
+ </form>
1078
+
1079
+ </div>
1080
+ </Formex>
1081
+ );
1082
+ }
1083
+
1084
+ function getDataSourceEntityValues<M extends object>(initialResolvedCollection: ResolvedEntityCollection,
1085
+ status: "new" | "existing" | "copy",
1086
+ entity: Entity<M> | undefined): Partial<EntityValues<M>> {
1087
+
1088
+ const properties = initialResolvedCollection.properties;
1089
+ if ((status === "existing" || status === "copy") && entity) {
1090
+ return entity.values ?? getDefaultValuesFor(properties);
1091
+ } else if (status === "new") {
1092
+ return getDefaultValuesFor(properties);
1093
+ } else {
1094
+ console.error({
1095
+ status,
1096
+ entity
1097
+ });
1098
+ throw new Error("Form has not been initialised with the correct parameters");
1099
+ }
1100
+ }
1101
+
1102
+ export type EntityFormSaveParams<M extends Record<string, any>> = {
1103
+ collection: ResolvedEntityCollection<M>,
1104
+ path: string,
1105
+ entityId: string | undefined,
1106
+ values: EntityValues<M>,
1107
+ previousValues?: EntityValues<M>,
1108
+ closeAfterSave: boolean,
1109
+ autoSave: boolean
1110
+ };
1111
+
1112
+ export function yupToFormErrors(yupError: ValidationError): Record<string, any> {
1113
+ let errors: Record<string, any> = {};
1114
+ if (yupError.inner) {
1115
+ if (yupError.inner.length === 0) {
1116
+ return setIn(errors, yupError.path!, yupError.message);
1117
+ }
1118
+ for (const err of yupError.inner) {
1119
+ if (!getIn(errors, err.path!)) {
1120
+ errors = setIn(errors, err.path!, err.message);
1121
+ }
1122
+ }
1123
+ }
1124
+ return errors;
1125
+ }