@firecms/core 3.0.0-beta.7 → 3.0.0-beta.8
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.
- package/dist/app/AppBar.d.ts +12 -0
- package/dist/app/Drawer.d.ts +17 -0
- package/dist/app/Scaffold.d.ts +30 -0
- package/dist/app/index.d.ts +4 -0
- package/dist/app/useApp.d.ts +16 -0
- package/dist/components/VirtualTable/VirtualTableProps.d.ts +4 -0
- package/dist/components/index.d.ts +1 -1
- package/dist/{components/FireCMSAppBar.d.ts → core/DefaultAppBar.d.ts} +2 -7
- package/dist/core/DefaultDrawer.d.ts +19 -0
- package/dist/core/EntityEditView.d.ts +17 -3
- package/dist/core/NavigationRoutes.d.ts +1 -1
- package/dist/core/index.d.ts +2 -2
- package/dist/form/PropertiesForm.d.ts +8 -0
- package/dist/form/components/FieldHelperText.d.ts +3 -3
- package/dist/form/components/StorageItemPreview.d.ts +4 -4
- package/dist/form/field_bindings/MapFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/StorageUploadFieldBinding.d.ts +2 -4
- package/dist/form/index.d.ts +0 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +5595 -5574
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +5 -5
- package/dist/index.umd.js.map +1 -1
- package/dist/preview/PropertyPreviewProps.d.ts +5 -0
- package/dist/preview/components/StorageThumbnail.d.ts +2 -1
- package/dist/preview/components/UrlComponentPreview.d.ts +2 -1
- package/dist/types/auth.d.ts +1 -1
- package/dist/types/collections.d.ts +14 -0
- package/dist/types/datasource.d.ts +18 -8
- package/dist/types/fields.d.ts +31 -30
- package/dist/types/plugins.d.ts +11 -3
- package/dist/types/properties.d.ts +7 -2
- package/dist/util/icons.d.ts +2 -2
- package/dist/util/storage.d.ts +23 -2
- package/dist/util/useStorageUploadController.d.ts +1 -1
- package/package.json +13 -22
- package/src/app/AppBar.tsx +18 -0
- package/src/app/Drawer.tsx +25 -0
- package/src/{core → app}/Scaffold.tsx +61 -107
- package/src/app/index.ts +4 -0
- package/src/app/useApp.tsx +32 -0
- package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +7 -9
- package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +2 -1
- package/src/components/EntityCollectionView/EntityCollectionView.tsx +7 -4
- package/src/components/VirtualTable/VirtualTable.tsx +3 -1
- package/src/components/VirtualTable/VirtualTableProps.tsx +5 -0
- package/src/components/common/useTableSearchHelper.ts +52 -12
- package/src/components/index.tsx +1 -1
- package/src/contexts/DialogsProvider.tsx +2 -2
- package/src/{components/FireCMSAppBar.tsx → core/DefaultAppBar.tsx} +26 -28
- package/src/core/DefaultDrawer.tsx +177 -0
- package/src/core/EntityEditView.tsx +669 -129
- package/src/core/EntitySidePanel.tsx +0 -1
- package/src/core/NavigationRoutes.tsx +3 -3
- package/src/core/index.tsx +2 -2
- package/src/form/PropertiesForm.tsx +81 -0
- package/src/form/PropertyFieldBinding.tsx +29 -5
- package/src/form/components/FieldHelperText.tsx +3 -3
- package/src/form/components/StorageItemPreview.tsx +19 -10
- package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +8 -5
- package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +5 -4
- package/src/form/field_bindings/KeyValueFieldBinding.tsx +40 -35
- package/src/form/field_bindings/MapFieldBinding.tsx +11 -3
- package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +0 -7
- package/src/form/field_bindings/RepeatFieldBinding.tsx +10 -7
- package/src/form/field_bindings/StorageUploadFieldBinding.tsx +6 -35
- package/src/form/field_bindings/SwitchFieldBinding.tsx +1 -1
- package/src/form/index.tsx +4 -4
- package/src/form/validation.ts +1 -17
- package/src/index.ts +1 -0
- package/src/internal/useBuildDataSource.ts +12 -0
- package/src/preview/PropertyPreview.tsx +5 -2
- package/src/preview/PropertyPreviewProps.tsx +6 -0
- package/src/preview/components/ImagePreview.tsx +21 -33
- package/src/preview/components/ReferencePreview.tsx +2 -1
- package/src/preview/components/StorageThumbnail.tsx +5 -1
- package/src/preview/components/UrlComponentPreview.tsx +44 -11
- package/src/types/auth.tsx +1 -1
- package/src/types/collections.ts +14 -0
- package/src/types/customization_controller.tsx +0 -1
- package/src/types/datasource.ts +20 -16
- package/src/types/fields.tsx +33 -33
- package/src/types/plugins.tsx +12 -3
- package/src/types/properties.ts +7 -2
- package/src/util/entities.ts +4 -3
- package/src/util/objects.ts +17 -7
- package/src/util/permissions.ts +1 -0
- package/src/util/storage.ts +75 -21
- package/src/util/useStorageUploadController.tsx +21 -3
- package/dist/core/Drawer.d.ts +0 -16
- package/dist/core/Scaffold.d.ts +0 -51
- package/dist/form/EntityForm.d.ts +0 -77
- package/src/core/Drawer.tsx +0 -139
- package/src/form/EntityForm.tsx +0 -736
|
@@ -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
|
|
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,10 +50,29 @@ import {
|
|
|
28
50
|
useSideEntityController,
|
|
29
51
|
useSnackbarController
|
|
30
52
|
} from "../hooks";
|
|
31
|
-
import {
|
|
32
|
-
|
|
33
|
-
|
|
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";
|
|
34
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";
|
|
35
76
|
|
|
36
77
|
const MAIN_TAB_VALUE = "main_##Q$SC^#S6";
|
|
37
78
|
|
|
@@ -42,7 +83,6 @@ export interface EntityEditViewProps<M extends Record<string, any>> {
|
|
|
42
83
|
copy?: boolean;
|
|
43
84
|
selectedSubPath?: string;
|
|
44
85
|
parentCollectionIds: string[];
|
|
45
|
-
formWidth?: number | string;
|
|
46
86
|
onValuesAreModified: (modified: boolean) => void;
|
|
47
87
|
onUpdate?: (params: { entity: Entity<any> }) => void;
|
|
48
88
|
onClose?: () => void;
|
|
@@ -55,17 +95,47 @@ export interface EntityEditViewProps<M extends Record<string, any>> {
|
|
|
55
95
|
* side panel. Instead, you might want to use {@link EntityForm} or {@link EntityCollectionView}
|
|
56
96
|
*/
|
|
57
97
|
export function EntityEditView<M extends Record<string, any>, UserType extends User>({
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
selectedSubPath,
|
|
61
|
-
copy,
|
|
62
|
-
collection,
|
|
63
|
-
parentCollectionIds,
|
|
64
|
-
onValuesAreModified,
|
|
65
|
-
formWidth,
|
|
66
|
-
onUpdate,
|
|
67
|
-
onClose,
|
|
98
|
+
entityId: entityIdProp,
|
|
99
|
+
...props
|
|
68
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
|
+
}) {
|
|
69
139
|
|
|
70
140
|
if (collection.customId && collection.formAutoSave) {
|
|
71
141
|
console.warn(`The collection ${collection.path} has customId and formAutoSave enabled. This is not supported and formAutoSave will be ignored`);
|
|
@@ -92,30 +162,61 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
|
|
|
92
162
|
// const largeLayoutTabSelected = useRef(!largeLayout);
|
|
93
163
|
// const resolvedFormWidth: string = typeof formWidth === "number" ? `${formWidth}px` : formWidth ?? FORM_CONTAINER_WIDTH;
|
|
94
164
|
|
|
165
|
+
const inputCollection = collection;
|
|
166
|
+
|
|
167
|
+
const authController = useAuthController();
|
|
95
168
|
const dataSource = useDataSource(collection);
|
|
96
169
|
const sideDialogContext = useSideDialogContext();
|
|
97
170
|
const sideEntityController = useSideEntityController();
|
|
98
171
|
const snackbarController = useSnackbarController();
|
|
99
172
|
const customizationController = useCustomizationController();
|
|
100
173
|
const context = useFireCMSContext();
|
|
101
|
-
const authController = useAuthController<UserType>();
|
|
102
174
|
|
|
103
|
-
const
|
|
175
|
+
const closeAfterSaveRef = useRef(false);
|
|
104
176
|
|
|
105
|
-
const
|
|
177
|
+
const analyticsController = useAnalyticsController();
|
|
106
178
|
|
|
107
|
-
const
|
|
108
|
-
|
|
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]);
|
|
109
201
|
|
|
110
|
-
const
|
|
111
|
-
const subcollectionsCount = subcollections?.length ?? 0;
|
|
112
|
-
const customViews = collection.entityViews;
|
|
113
|
-
const customViewsCount = customViews?.length ?? 0;
|
|
114
|
-
const autoSave = collection.formAutoSave && !collection.customId;
|
|
202
|
+
const [entityId, setEntityId] = React.useState<string | undefined>(initialEntityId);
|
|
115
203
|
|
|
116
|
-
const
|
|
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
|
+
// };
|
|
213
|
+
|
|
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);
|
|
117
218
|
|
|
118
|
-
const defaultSelectedView =
|
|
219
|
+
const defaultSelectedView = selectedSubPathProp ?? resolveDefaultSelectedView(
|
|
119
220
|
collection ? collection.defaultSelectedView : undefined,
|
|
120
221
|
{
|
|
121
222
|
status,
|
|
@@ -124,20 +225,23 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
|
|
|
124
225
|
);
|
|
125
226
|
|
|
126
227
|
const selectedTabRef = useRef<string>(defaultSelectedView ?? MAIN_TAB_VALUE);
|
|
228
|
+
const baseDataSourceValuesRef = useRef<Partial<EntityValues<M>>>(getDataSourceEntityValues(initialResolvedCollection, status, entity));
|
|
127
229
|
|
|
128
230
|
const mainViewVisible = selectedTabRef.current === MAIN_TAB_VALUE;
|
|
129
231
|
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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;
|
|
141
245
|
|
|
142
246
|
const [usedEntity, setUsedEntity] = useState<Entity<M> | undefined>(entity);
|
|
143
247
|
const [readOnly, setReadOnly] = useState<boolean | undefined>(undefined);
|
|
@@ -279,11 +383,115 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
|
|
|
279
383
|
}
|
|
280
384
|
};
|
|
281
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
|
+
|
|
282
487
|
const resolvedEntityViews = customViews ? customViews
|
|
283
488
|
.map(e => resolveEntityView(e, customizationController.entityViews))
|
|
284
489
|
.filter(Boolean) as EntityCustomView[]
|
|
285
490
|
: [];
|
|
286
491
|
|
|
492
|
+
const selectedEntityView = resolvedEntityViews.find(e => e.key === selectedTabRef.current);
|
|
493
|
+
const shouldShowEntityActions = !autoSave && (selectedTabRef.current === MAIN_TAB_VALUE || selectedEntityView?.includeActions);
|
|
494
|
+
|
|
287
495
|
const customViewsView: React.ReactNode[] | undefined = customViews && resolvedEntityViews
|
|
288
496
|
.map(
|
|
289
497
|
(customView, colIndex) => {
|
|
@@ -305,7 +513,7 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
|
|
|
305
513
|
{formContext && <Builder
|
|
306
514
|
collection={collection}
|
|
307
515
|
entity={usedEntity}
|
|
308
|
-
modifiedValues={
|
|
516
|
+
modifiedValues={formex.values ?? usedEntity?.values}
|
|
309
517
|
formContext={formContext}
|
|
310
518
|
/>}
|
|
311
519
|
</ErrorBoundary>
|
|
@@ -368,9 +576,9 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
|
|
|
368
576
|
});
|
|
369
577
|
};
|
|
370
578
|
|
|
371
|
-
const onValuesChanged = useCallback((values?: EntityValues<M>) => {
|
|
372
|
-
|
|
373
|
-
}, []);
|
|
579
|
+
// const onValuesChanged = useCallback((values?: EntityValues<M>) => {
|
|
580
|
+
// modifiedValuesRef.current = values;
|
|
581
|
+
// }, []);
|
|
374
582
|
|
|
375
583
|
// eslint-disable-next-line n/handle-callback-err
|
|
376
584
|
const onIdUpdateError = useCallback((error: any) => {
|
|
@@ -389,28 +597,316 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
|
|
|
389
597
|
: undefined);
|
|
390
598
|
}, []);
|
|
391
599
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
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));
|
|
395
644
|
}
|
|
396
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
|
+
|
|
397
862
|
function buildForm() {
|
|
398
|
-
|
|
399
|
-
let form = <
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
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
|
+
|
|
414
910
|
if (plugins) {
|
|
415
911
|
plugins.forEach((plugin: FireCMSPlugin) => {
|
|
416
912
|
if (plugin.form?.provider) {
|
|
@@ -420,8 +916,6 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
|
|
|
420
916
|
path={path}
|
|
421
917
|
collection={collection}
|
|
422
918
|
onDiscard={onDiscard}
|
|
423
|
-
onValuesChanged={onValuesChanged}
|
|
424
|
-
onModified={onModified}
|
|
425
919
|
entity={usedEntity}
|
|
426
920
|
context={context}
|
|
427
921
|
formContext={formContext}
|
|
@@ -435,7 +929,7 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
|
|
|
435
929
|
return <ErrorBoundary>{form}</ErrorBoundary>;
|
|
436
930
|
}
|
|
437
931
|
|
|
438
|
-
const
|
|
932
|
+
const entityView = (readOnly === undefined)
|
|
439
933
|
? <></>
|
|
440
934
|
: (!readOnly
|
|
441
935
|
? buildForm()
|
|
@@ -450,6 +944,7 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
|
|
|
450
944
|
entity={usedEntity as Entity<M>}
|
|
451
945
|
path={path}
|
|
452
946
|
collection={collection}/>
|
|
947
|
+
|
|
453
948
|
</>
|
|
454
949
|
));
|
|
455
950
|
|
|
@@ -474,87 +969,132 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
|
|
|
474
969
|
</Tab>
|
|
475
970
|
);
|
|
476
971
|
|
|
972
|
+
useEffect(() => {
|
|
973
|
+
if (entityId && onIdChange)
|
|
974
|
+
onIdChange(entityId);
|
|
975
|
+
}, [entityId, onIdChange]);
|
|
976
|
+
|
|
477
977
|
return (
|
|
478
|
-
<
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
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")}>
|
|
482
984
|
|
|
483
985
|
<div
|
|
484
|
-
className=
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
size="large">
|
|
494
|
-
<CloseIcon/>
|
|
495
|
-
</IconButton>
|
|
496
|
-
</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>
|
|
497
995
|
|
|
498
|
-
|
|
996
|
+
<div className={"flex-grow"}/>
|
|
499
997
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
998
|
+
{globalLoading && <div
|
|
999
|
+
className="self-center">
|
|
1000
|
+
<CircularProgress size={"small"}/>
|
|
1001
|
+
</div>}
|
|
504
1002
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
1003
|
+
<Tabs
|
|
1004
|
+
value={selectedTabRef.current}
|
|
1005
|
+
onValueChange={(value) => {
|
|
1006
|
+
onSideTabClick(value);
|
|
1007
|
+
}}
|
|
1008
|
+
className="pl-4 pr-4 pt-0">
|
|
511
1009
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
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>
|
|
519
1017
|
|
|
520
|
-
|
|
1018
|
+
{customViewTabs}
|
|
521
1019
|
|
|
522
|
-
|
|
523
|
-
|
|
1020
|
+
{subcollectionTabs}
|
|
1021
|
+
</Tabs>
|
|
524
1022
|
|
|
525
|
-
|
|
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"}>
|
|
526
1033
|
|
|
527
1034
|
<div
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
// [`@media (max-width: ${resolvedFormWidth})`]: {
|
|
533
|
-
// width: resolvedFormWidth
|
|
534
|
-
// }
|
|
535
|
-
}}>
|
|
536
|
-
|
|
537
|
-
<div
|
|
538
|
-
role="tabpanel"
|
|
539
|
-
hidden={!mainViewVisible}
|
|
540
|
-
id={`form_${path}`}
|
|
541
|
-
className={" w-full"}>
|
|
542
|
-
|
|
543
|
-
{globalLoading
|
|
544
|
-
? <CircularProgressCenter/>
|
|
545
|
-
: form}
|
|
1035
|
+
role="tabpanel"
|
|
1036
|
+
hidden={!mainViewVisible}
|
|
1037
|
+
id={`form_${path}`}
|
|
1038
|
+
className={" w-full"}>
|
|
546
1039
|
|
|
547
|
-
|
|
1040
|
+
{globalLoading
|
|
1041
|
+
? <CircularProgressCenter/>
|
|
1042
|
+
: entityView}
|
|
1043
|
+
|
|
1044
|
+
</div>
|
|
548
1045
|
|
|
549
|
-
|
|
1046
|
+
{customViewsView}
|
|
550
1047
|
|
|
551
|
-
|
|
1048
|
+
{subCollectionsViews}
|
|
552
1049
|
|
|
553
|
-
|
|
1050
|
+
{shouldShowEntityActions && dialogActions}
|
|
554
1051
|
|
|
555
|
-
|
|
556
|
-
}
|
|
1052
|
+
</form>
|
|
557
1053
|
|
|
558
|
-
|
|
1054
|
+
</div>
|
|
1055
|
+
</Formex>
|
|
559
1056
|
);
|
|
560
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
|
+
}
|