@firecms/core 3.0.0-canary.10 → 3.0.0-canary.101
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/README.md +1 -1
- 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/CircularProgressCenter.d.ts +1 -1
- package/dist/components/ClearFilterSortButton.d.ts +5 -0
- package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +11 -12
- package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +1 -1
- package/dist/components/EntityCollectionTable/EntityCollectionTableProps.d.ts +5 -3
- package/dist/components/EntityCollectionTable/PropertyTableCell.d.ts +1 -0
- package/dist/components/EntityCollectionTable/column_utils.d.ts +1 -2
- package/dist/components/EntityCollectionTable/fields/TableReferenceField.d.ts +2 -0
- package/dist/components/EntityCollectionTable/internal/CollectionTableToolbar.d.ts +1 -4
- package/dist/components/EntityCollectionTable/internal/EntityTableCell.d.ts +2 -2
- package/dist/components/EntityCollectionTable/internal/popup_field/PopupFormField.d.ts +1 -1
- package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +12 -2
- package/dist/components/EntityCollectionView/EntityCollectionViewStartActions.d.ts +11 -0
- package/dist/components/EntityPreview.d.ts +5 -4
- 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/ReferenceWidget.d.ts +3 -1
- package/dist/components/SelectableTable/SelectableTable.d.ts +1 -1
- package/dist/components/SelectableTable/filters/ReferenceFilterField.d.ts +2 -1
- package/dist/components/VirtualTable/VirtualTableProps.d.ts +15 -12
- package/dist/components/VirtualTable/types.d.ts +3 -3
- package/dist/components/{EntityCollectionTable/internal → common}/default_entity_actions.d.ts +1 -1
- package/dist/components/common/index.d.ts +1 -0
- package/dist/components/common/table_height.d.ts +5 -0
- package/dist/components/common/types.d.ts +4 -6
- package/dist/components/common/useColumnsIds.d.ts +2 -1
- package/dist/components/common/useDataSourceEntityCollectionTableController.d.ts +3 -0
- package/dist/components/index.d.ts +2 -1
- package/dist/{components/FireCMSAppBar.d.ts → core/DefaultAppBar.d.ts} +6 -9
- package/dist/core/DefaultDrawer.d.ts +19 -0
- package/dist/core/DrawerNavigationItem.d.ts +9 -0
- package/dist/core/EntityEditView.d.ts +18 -4
- package/dist/core/FireCMS.d.ts +1 -1
- package/dist/core/NavigationRoutes.d.ts +3 -3
- package/dist/core/index.d.ts +3 -4
- package/dist/form/PropertiesForm.d.ts +8 -0
- package/dist/form/PropertyFieldBinding.d.ts +1 -1
- package/dist/form/components/ErrorFocus.d.ts +1 -1
- 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/hooks/data/save.d.ts +1 -2
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/useBuildNavigationController.d.ts +1 -2
- package/dist/hooks/useProjectLog.d.ts +2 -2
- package/dist/hooks/useValidateAuthenticator.d.ts +4 -8
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +15836 -12220
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +19716 -7
- package/dist/index.umd.js.map +1 -1
- package/dist/internal/useBuildDataSource.d.ts +1 -16
- package/dist/preview/PropertyPreviewProps.d.ts +5 -0
- package/dist/preview/components/ReferencePreview.d.ts +2 -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 +3 -3
- package/dist/types/collections.d.ts +28 -4
- package/dist/types/datasource.d.ts +33 -19
- package/dist/types/entities.d.ts +5 -1
- package/dist/types/entity_actions.d.ts +14 -0
- package/dist/types/fields.d.ts +36 -30
- package/dist/types/index.d.ts +0 -1
- package/dist/types/navigation.d.ts +5 -5
- package/dist/types/plugins.d.ts +14 -4
- package/dist/types/properties.d.ts +32 -19
- package/dist/types/storage.d.ts +11 -3
- package/dist/util/entities.d.ts +1 -1
- package/dist/util/icon_list.d.ts +5 -1
- package/dist/util/icon_synonyms.d.ts +1 -97
- package/dist/util/icons.d.ts +2 -2
- package/dist/util/navigation_utils.d.ts +2 -2
- package/dist/util/objects.d.ts +1 -1
- package/dist/util/plurals.d.ts +0 -2
- package/dist/util/resolutions.d.ts +14 -13
- package/dist/util/storage.d.ts +23 -2
- package/dist/util/useStorageUploadController.d.ts +1 -1
- package/package.json +128 -119
- package/src/app/AppBar.tsx +18 -0
- package/src/app/Drawer.tsx +25 -0
- package/src/app/Scaffold.tsx +254 -0
- package/src/app/index.ts +4 -0
- package/src/app/useApp.tsx +32 -0
- package/src/components/ArrayContainer.tsx +1 -1
- package/src/components/CircularProgressCenter.tsx +1 -1
- package/src/components/ClearFilterSortButton.tsx +41 -0
- package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +9 -18
- package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +21 -20
- package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +10 -6
- package/src/components/EntityCollectionTable/PropertyTableCell.tsx +38 -34
- package/src/components/EntityCollectionTable/column_utils.tsx +3 -3
- package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +11 -2
- package/src/components/EntityCollectionTable/fields/TableStorageUpload.tsx +14 -6
- package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +29 -34
- package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +16 -12
- package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +4 -5
- package/src/components/EntityCollectionView/EntityCollectionView.tsx +69 -45
- package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +68 -0
- package/src/components/EntityCollectionView/useSelectionController.tsx +19 -7
- package/src/components/EntityPreview.tsx +15 -9
- package/src/components/EntityView.tsx +5 -5
- package/src/components/ErrorView.tsx +1 -1
- package/src/components/HomePage/DefaultHomePage.tsx +24 -15
- package/src/components/HomePage/NavigationCard.tsx +3 -3
- package/src/components/HomePage/NavigationCardBinding.tsx +1 -1
- package/src/components/HomePage/SmallNavigationCard.tsx +5 -5
- package/src/components/PropertyIdCopyTooltipContent.tsx +2 -3
- package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +1 -0
- package/src/components/ReferenceWidget.tsx +22 -12
- package/src/components/SearchIconsView.tsx +9 -6
- package/src/components/SelectableTable/SelectableTable.tsx +7 -7
- package/src/components/SelectableTable/filters/BooleanFilterField.tsx +2 -3
- package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +22 -7
- package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +28 -6
- package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +35 -15
- package/src/components/VirtualTable/VirtualTable.tsx +70 -37
- package/src/components/VirtualTable/VirtualTableCell.tsx +1 -1
- package/src/components/VirtualTable/VirtualTableHeader.tsx +4 -4
- package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +2 -2
- package/src/components/VirtualTable/VirtualTableProps.tsx +18 -14
- package/src/components/VirtualTable/VirtualTableRow.tsx +4 -5
- package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +1 -1
- package/src/components/VirtualTable/types.tsx +2 -3
- package/src/components/{EntityCollectionTable/internal → common}/default_entity_actions.tsx +2 -2
- package/src/components/common/index.ts +1 -0
- package/src/components/{VirtualTable/common.tsx → common/table_height.tsx} +5 -2
- package/src/components/common/types.tsx +4 -6
- package/src/components/common/useColumnsIds.tsx +23 -2
- package/src/components/common/useDataSourceEntityCollectionTableController.tsx +11 -0
- package/src/components/common/useTableSearchHelper.ts +52 -12
- package/src/components/index.tsx +2 -1
- package/src/contexts/DialogsProvider.tsx +2 -2
- package/src/{components/FireCMSAppBar.tsx → core/DefaultAppBar.tsx} +52 -37
- package/src/core/DefaultDrawer.tsx +177 -0
- package/src/core/DrawerNavigationItem.tsx +62 -0
- package/src/core/EntityEditView.tsx +709 -140
- package/src/core/EntitySidePanel.tsx +1 -2
- package/src/core/FireCMS.tsx +39 -44
- package/src/core/NavigationRoutes.tsx +7 -8
- package/src/core/SideDialogs.tsx +1 -0
- package/src/core/field_configs.tsx +2 -3
- package/src/core/index.tsx +3 -4
- package/src/form/PropertiesForm.tsx +81 -0
- package/src/form/PropertyFieldBinding.tsx +38 -9
- package/src/form/components/FieldHelperText.tsx +3 -3
- package/src/form/components/StorageItemPreview.tsx +20 -11
- package/src/form/components/StorageUploadProgress.tsx +3 -3
- package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +8 -5
- package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +8 -5
- package/src/form/field_bindings/BlockFieldBinding.tsx +2 -2
- package/src/form/field_bindings/KeyValueFieldBinding.tsx +44 -39
- package/src/form/field_bindings/MapFieldBinding.tsx +11 -3
- package/src/form/field_bindings/MarkdownFieldBinding.tsx +2 -2
- package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +2 -9
- package/src/form/field_bindings/ReferenceFieldBinding.tsx +15 -13
- package/src/form/field_bindings/RepeatFieldBinding.tsx +11 -8
- package/src/form/field_bindings/SelectFieldBinding.tsx +3 -3
- package/src/form/field_bindings/StorageUploadFieldBinding.tsx +22 -43
- 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/hooks/data/save.ts +3 -1
- package/src/hooks/data/useDataSource.tsx +1 -1
- package/src/hooks/index.tsx +1 -0
- package/src/hooks/useBuildLocalConfigurationPersistence.tsx +8 -10
- package/src/hooks/useBuildModeController.tsx +11 -5
- package/src/hooks/useBuildNavigationController.tsx +86 -29
- package/src/hooks/useProjectLog.tsx +19 -8
- package/src/hooks/useResolvedNavigationFrom.tsx +1 -1
- package/src/hooks/useValidateAuthenticator.tsx +21 -41
- package/src/index.ts +1 -0
- package/src/internal/useBuildDataSource.ts +56 -49
- package/src/internal/useBuildSideEntityController.tsx +88 -21
- package/src/preview/PropertyPreview.tsx +9 -16
- package/src/preview/PropertyPreviewProps.tsx +6 -0
- package/src/preview/components/BooleanPreview.tsx +2 -2
- package/src/preview/components/EnumValuesChip.tsx +1 -1
- package/src/preview/components/ImagePreview.tsx +21 -33
- package/src/preview/components/ReferencePreview.tsx +23 -23
- package/src/preview/components/StorageThumbnail.tsx +5 -1
- package/src/preview/components/UrlComponentPreview.tsx +44 -11
- package/src/preview/property_previews/ArrayOfMapsPreview.tsx +0 -1
- package/src/preview/property_previews/ArrayOfReferencesPreview.tsx +2 -1
- package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +0 -1
- package/src/preview/property_previews/ArrayOfStringsPreview.tsx +0 -1
- package/src/preview/property_previews/ArrayOneOfPreview.tsx +2 -3
- package/src/preview/property_previews/ArrayPropertyPreview.tsx +2 -3
- package/src/preview/property_previews/MapPropertyPreview.tsx +5 -5
- package/src/preview/property_previews/StringPropertyPreview.tsx +2 -2
- package/src/types/auth.tsx +3 -3
- package/src/types/collections.ts +35 -6
- package/src/types/customization_controller.tsx +0 -1
- package/src/types/datasource.ts +40 -23
- package/src/types/entities.ts +9 -1
- package/src/types/entity_actions.tsx +16 -3
- package/src/types/fields.tsx +39 -33
- package/src/types/index.ts +0 -1
- package/src/types/navigation.ts +6 -7
- package/src/types/plugins.tsx +16 -6
- package/src/types/properties.ts +37 -20
- package/src/types/storage.ts +12 -3
- package/src/util/entities.ts +5 -4
- package/src/util/enums.ts +1 -1
- package/src/util/icon_list.ts +15 -10
- package/src/util/icon_synonyms.ts +3 -99
- package/src/util/navigation_utils.ts +6 -6
- package/src/util/objects.ts +25 -28
- package/src/util/permissions.ts +1 -0
- package/src/util/plurals.ts +0 -2
- package/src/util/resolutions.ts +44 -39
- package/src/util/storage.ts +75 -21
- package/src/util/strings.ts +2 -2
- package/src/util/useStorageUploadController.tsx +21 -3
- package/src/util/useTraceUpdate.tsx +1 -0
- package/dist/components/VirtualTable/common.d.ts +0 -2
- package/dist/core/Drawer.d.ts +0 -23
- package/dist/core/Scaffold.d.ts +0 -55
- package/dist/core/SideEntityView.d.ts +0 -7
- package/dist/form/EntityForm.d.ts +0 -77
- package/dist/internal/useBuildCustomizationController.d.ts +0 -2
- package/dist/internal/useLocaleConfig.d.ts +0 -1
- package/dist/types/appcheck.d.ts +0 -26
- package/src/core/Drawer.tsx +0 -191
- package/src/core/Scaffold.tsx +0 -281
- package/src/core/SideEntityView.tsx +0 -38
- package/src/form/EntityForm.tsx +0 -728
- package/src/internal/useBuildCustomizationController.tsx +0 -5
- package/src/internal/useLocaleConfig.tsx +0 -18
- 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
|
|
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 {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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.
|
|
98
|
+
* side panel.
|
|
57
99
|
*/
|
|
58
100
|
export function EntityEditView<M extends Record<string, any>, UserType extends User>({
|
|
59
|
-
path,
|
|
60
101
|
entityId,
|
|
61
|
-
|
|
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
|
|
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
|
|
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 [
|
|
211
|
+
const [entityId, setEntityId] = React.useState<string | undefined>(initialEntityId);
|
|
108
212
|
|
|
109
|
-
const
|
|
110
|
-
const
|
|
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
|
|
113
|
-
const
|
|
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
|
|
226
|
+
const [customIdLoading, setCustomIdLoading] = React.useState<boolean>(false);
|
|
119
227
|
|
|
120
|
-
const defaultSelectedView =
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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={
|
|
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={
|
|
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
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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
|
-
|
|
400
|
-
let form = <
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
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
|
|
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
|
-
<
|
|
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
|
-
|
|
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
|
-
|
|
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=
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
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
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
className={" w-full"}>
|
|
1028
|
+
{globalLoading && <div
|
|
1029
|
+
className="self-center">
|
|
1030
|
+
<CircularProgress size={"small"}/>
|
|
1031
|
+
</div>}
|
|
543
1032
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
1033
|
+
<Tabs
|
|
1034
|
+
value={selectedTabRef.current}
|
|
1035
|
+
onValueChange={(value) => {
|
|
1036
|
+
onSideTabClick(value);
|
|
1037
|
+
}}
|
|
1038
|
+
className="pl-4 pr-4 pt-0">
|
|
547
1039
|
|
|
548
|
-
|
|
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
|
-
{
|
|
1048
|
+
{customViewTabs}
|
|
551
1049
|
|
|
552
|
-
{
|
|
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
|
-
|
|
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
|
+
}
|