@firecms/core 3.0.0-canary.10 → 3.0.0-canary.100

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