@firecms/core 3.0.0-canary.8 → 3.0.0-canary.81

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