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