@firecms/core 3.0.0-beta.11 → 3.0.0-beta.12
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/components/ArrayContainer.d.ts +7 -3
- package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +2 -1
- package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +2 -2
- package/dist/components/EntityCollectionTable/EntityCollectionTableProps.d.ts +15 -1
- package/dist/components/EntityCollectionTable/index.d.ts +1 -1
- package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +4 -0
- package/dist/components/PropertyIdCopyTooltip.d.ts +1 -1
- package/dist/components/SelectableTable/SelectableTable.d.ts +12 -2
- package/dist/components/SelectableTable/filters/ReferenceFilterField.d.ts +1 -1
- package/dist/components/UnsavedChangesDialog.d.ts +8 -0
- package/dist/components/VirtualTable/VirtualTableProps.d.ts +13 -0
- package/dist/components/common/default_entity_actions.d.ts +0 -2
- package/dist/components/common/index.d.ts +1 -1
- package/dist/components/common/useColumnsIds.d.ts +1 -0
- package/dist/components/common/{useDataSourceEntityCollectionTableController.d.ts → useDataSourceTableController.d.ts} +10 -2
- package/dist/components/common/useDebouncedCallback.d.ts +1 -0
- package/dist/components/common/useScrollRestoration.d.ts +14 -0
- package/dist/contexts/BreacrumbsContext.d.ts +8 -0
- package/dist/core/EntityEditView.d.ts +29 -21
- package/dist/core/EntityEditViewFormActions.d.ts +2 -0
- package/dist/core/FireCMSRouter.d.ts +4 -0
- package/dist/core/NavigationRoutes.d.ts +0 -1
- package/dist/core/SideDialogs.d.ts +4 -2
- package/dist/core/index.d.ts +2 -1
- package/dist/form/EntityForm.d.ts +45 -0
- package/dist/form/EntityFormActions.d.ts +17 -0
- package/dist/form/PropertyFieldBinding.d.ts +1 -1
- package/dist/form/components/FormEntry.d.ts +6 -0
- package/dist/form/components/FormLayout.d.ts +5 -0
- package/dist/form/components/index.d.ts +2 -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/MapFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/MarkdownEditorFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/RepeatFieldBinding.d.ts +1 -1
- package/dist/form/index.d.ts +16 -16
- package/dist/hooks/data/useEntityFetch.d.ts +2 -1
- package/dist/hooks/useBreadcrumbsController.d.ts +26 -0
- package/dist/hooks/useBuildNavigationController.d.ts +4 -1
- package/dist/hooks/useModeController.d.ts +1 -2
- package/dist/index.es.js +18213 -16819
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +18164 -16771
- package/dist/index.umd.js.map +1 -1
- package/dist/internal/useBuildSideEntityController.d.ts +2 -2
- package/dist/internal/useUnsavedChangesDialog.d.ts +7 -9
- package/dist/routes/CustomCMSRoute.d.ts +4 -0
- package/dist/routes/FireCMSRoute.d.ts +1 -0
- package/dist/routes/HomePageRoute.d.ts +3 -0
- package/dist/types/collections.d.ts +30 -10
- package/dist/types/datasource.d.ts +2 -2
- package/dist/types/dialogs_controller.d.ts +7 -3
- package/dist/types/entities.d.ts +1 -1
- package/dist/types/entity_actions.d.ts +10 -0
- package/dist/types/fields.d.ts +9 -2
- package/dist/types/navigation.d.ts +29 -12
- package/dist/types/plugins.d.ts +1 -0
- package/dist/types/properties.d.ts +18 -3
- package/dist/types/side_entity_controller.d.ts +6 -1
- package/dist/util/builders.d.ts +1 -1
- package/dist/util/entity_actions.d.ts +2 -0
- package/dist/util/entity_cache.d.ts +23 -0
- package/dist/util/index.d.ts +1 -0
- package/dist/util/navigation_from_path.d.ts +6 -1
- package/dist/util/navigation_utils.d.ts +12 -1
- package/dist/util/resolutions.d.ts +6 -1
- package/package.json +19 -21
- package/src/components/ArrayContainer.tsx +48 -29
- package/src/components/CircularProgressCenter.tsx +1 -1
- package/src/components/DeleteEntityDialog.tsx +1 -1
- package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +40 -19
- package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +10 -4
- package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +18 -1
- package/src/components/EntityCollectionTable/index.tsx +1 -1
- package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +3 -3
- package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +5 -11
- package/src/components/EntityCollectionView/EntityCollectionView.tsx +83 -41
- package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +4 -2
- package/src/components/NotFoundPage.tsx +2 -2
- package/src/components/PropertyIdCopyTooltip.tsx +2 -3
- package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +11 -6
- package/src/components/SelectableTable/SelectableTable.tsx +18 -3
- package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +7 -7
- package/src/components/UnsavedChangesDialog.tsx +42 -0
- package/src/components/VirtualTable/VirtualTable.tsx +19 -2
- package/src/components/VirtualTable/VirtualTableProps.tsx +15 -0
- package/src/components/VirtualTable/fields/VirtualTableInput.tsx +1 -1
- package/src/components/common/default_entity_actions.tsx +26 -37
- package/src/components/common/index.ts +1 -1
- package/src/components/common/useColumnsIds.tsx +1 -1
- package/src/components/common/useDataSourceTableController.tsx +420 -0
- package/src/components/common/useDebouncedCallback.tsx +20 -0
- package/src/components/common/useScrollRestoration.tsx +68 -0
- package/src/contexts/BreacrumbsContext.tsx +38 -0
- package/src/contexts/DialogsProvider.tsx +3 -2
- package/src/contexts/ModeController.tsx +1 -3
- package/src/core/DefaultAppBar.tsx +44 -14
- package/src/core/DefaultDrawer.tsx +1 -1
- package/src/core/EntityEditView.tsx +278 -1000
- package/src/core/EntityEditViewFormActions.tsx +199 -0
- package/src/core/EntitySidePanel.tsx +85 -15
- package/src/core/FireCMS.tsx +7 -4
- package/src/core/FireCMSRouter.tsx +17 -0
- package/src/core/NavigationRoutes.tsx +23 -32
- package/src/core/SideDialogs.tsx +20 -11
- package/src/core/index.tsx +4 -2
- package/src/form/EntityForm.tsx +774 -0
- package/src/form/EntityFormActions.tsx +169 -0
- package/src/form/PropertyFieldBinding.tsx +13 -11
- package/src/form/components/FormEntry.tsx +22 -0
- package/src/form/components/FormLayout.tsx +16 -0
- package/src/form/components/LabelWithIcon.tsx +6 -3
- package/src/form/components/StorageUploadProgress.tsx +1 -1
- package/src/form/components/index.tsx +2 -0
- package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +4 -4
- package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +4 -2
- package/src/form/field_bindings/BlockFieldBinding.tsx +4 -2
- package/src/form/field_bindings/DateTimeFieldBinding.tsx +1 -0
- package/src/form/field_bindings/MapFieldBinding.tsx +14 -16
- package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +9 -6
- package/src/form/field_bindings/MultiSelectFieldBinding.tsx +1 -1
- package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +2 -2
- package/src/form/field_bindings/ReferenceFieldBinding.tsx +1 -1
- package/src/form/field_bindings/RepeatFieldBinding.tsx +5 -2
- package/src/form/field_bindings/SelectFieldBinding.tsx +1 -1
- package/src/form/field_bindings/StorageUploadFieldBinding.tsx +6 -4
- package/src/form/field_bindings/SwitchFieldBinding.tsx +1 -0
- package/src/form/field_bindings/TextFieldBinding.tsx +1 -0
- package/src/form/index.tsx +16 -32
- package/src/form/validation.ts +12 -6
- package/src/hooks/data/save.ts +1 -1
- package/src/hooks/data/useCollectionFetch.tsx +1 -1
- package/src/hooks/data/useEntityFetch.tsx +5 -1
- package/src/hooks/useBreadcrumbsController.tsx +31 -0
- package/src/hooks/useBuildModeController.tsx +15 -28
- package/src/hooks/useBuildNavigationController.tsx +70 -24
- package/src/hooks/useLargeLayout.tsx +0 -35
- package/src/hooks/useModeController.tsx +1 -2
- package/src/hooks/useResolvedNavigationFrom.tsx +4 -6
- package/src/internal/useBuildSideDialogsController.tsx +3 -2
- package/src/internal/useBuildSideEntityController.tsx +124 -83
- package/src/internal/useUnsavedChangesDialog.tsx +126 -92
- package/src/preview/PropertyPreview.tsx +8 -8
- package/src/preview/components/UrlComponentPreview.tsx +17 -18
- package/src/preview/property_previews/MapPropertyPreview.tsx +3 -2
- package/src/routes/CustomCMSRoute.tsx +21 -0
- package/src/routes/FireCMSRoute.tsx +235 -0
- package/src/routes/HomePageRoute.tsx +17 -0
- package/src/types/collections.ts +38 -13
- package/src/types/datasource.ts +2 -1
- package/src/types/dialogs_controller.tsx +7 -3
- package/src/types/entities.ts +1 -1
- package/src/types/entity_actions.tsx +12 -0
- package/src/types/fields.tsx +13 -2
- package/src/types/navigation.ts +36 -16
- package/src/types/plugins.tsx +1 -0
- package/src/types/properties.ts +21 -3
- package/src/types/side_dialogs_controller.tsx +2 -0
- package/src/types/side_entity_controller.tsx +6 -1
- package/src/util/builders.ts +5 -3
- package/src/util/entity_actions.ts +28 -0
- package/src/util/entity_cache.ts +204 -0
- package/src/util/index.ts +1 -0
- package/src/util/join_collections.ts +3 -0
- package/src/util/navigation_from_path.ts +13 -6
- package/src/util/navigation_utils.ts +53 -1
- package/src/util/objects.ts +4 -1
- package/src/util/parent_references_from_path.ts +3 -3
- package/src/util/resolutions.ts +26 -5
- package/src/util/useStorageUploadController.tsx +39 -21
- package/src/components/common/useDataSourceEntityCollectionTableController.tsx +0 -236
- /package/src/util/{common.tsx → common.ts} +0 -0
|
@@ -1,104 +1,69 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
import {
|
|
3
|
-
CMSAnalyticsEvent,
|
|
4
|
-
Entity,
|
|
5
|
-
EntityAction,
|
|
6
|
-
EntityCollection,
|
|
7
|
-
EntityCustomView,
|
|
8
|
-
EntityStatus,
|
|
9
|
-
EntityValues,
|
|
10
|
-
FireCMSPlugin,
|
|
11
|
-
FormContext,
|
|
12
|
-
PluginFormActionProps,
|
|
13
|
-
PropertyFieldBindingProps,
|
|
14
|
-
ResolvedEntityCollection,
|
|
15
|
-
User
|
|
16
|
-
} from "../types";
|
|
17
|
-
import equal from "react-fast-compare"
|
|
1
|
+
import React, { useEffect, useMemo, useState } from "react";
|
|
2
|
+
import { Entity, EntityCollection, EntityStatus, FireCMSPlugin, FormContext, User } from "../types";
|
|
18
3
|
|
|
4
|
+
import { CircularProgressCenter, EntityCollectionView, EntityView, ErrorBoundary } from "../components";
|
|
19
5
|
import {
|
|
20
|
-
CircularProgressCenter,
|
|
21
|
-
copyEntityAction,
|
|
22
|
-
deleteEntityAction,
|
|
23
|
-
EntityCollectionView,
|
|
24
|
-
EntityView,
|
|
25
|
-
ErrorBoundary,
|
|
26
|
-
getFormFieldKeys,
|
|
27
|
-
} from "../components";
|
|
28
|
-
import {
|
|
29
|
-
canCreateEntity,
|
|
30
|
-
canDeleteEntity,
|
|
31
6
|
canEditEntity,
|
|
32
|
-
getDefaultValuesFor,
|
|
33
|
-
getEntityTitlePropertyKey,
|
|
34
|
-
getValueInPath,
|
|
35
|
-
isHidden,
|
|
36
|
-
isReadOnly,
|
|
37
7
|
removeInitialAndTrailingSlashes,
|
|
38
|
-
resolveCollection,
|
|
39
8
|
resolveDefaultSelectedView,
|
|
40
|
-
|
|
41
|
-
useDebouncedCallback
|
|
9
|
+
resolvedSelectedEntityView
|
|
42
10
|
} from "../util";
|
|
43
11
|
|
|
44
12
|
import {
|
|
45
|
-
saveEntityWithCallbacks,
|
|
46
13
|
useAuthController,
|
|
47
14
|
useCustomizationController,
|
|
48
|
-
useDataSource,
|
|
49
15
|
useEntityFetch,
|
|
50
16
|
useFireCMSContext,
|
|
51
|
-
|
|
52
|
-
useSnackbarController
|
|
17
|
+
useLargeLayout
|
|
53
18
|
} from "../hooks";
|
|
54
|
-
import {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
CloseIcon,
|
|
59
|
-
cls,
|
|
60
|
-
defaultBorderMixin,
|
|
61
|
-
DialogActions,
|
|
62
|
-
IconButton,
|
|
63
|
-
NotesIcon,
|
|
64
|
-
paperMixin,
|
|
65
|
-
Tab,
|
|
66
|
-
Tabs,
|
|
67
|
-
Typography
|
|
68
|
-
} from "@firecms/ui";
|
|
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";
|
|
19
|
+
import { CircularProgress, cls, defaultBorderMixin, Tab, Tabs, Typography } from "@firecms/ui";
|
|
20
|
+
import { getEntityFromCache } from "../util/entity_cache";
|
|
21
|
+
import { EntityForm, EntityFormProps } from "../form";
|
|
22
|
+
import { EntityEditViewFormActions } from "./EntityEditViewFormActions";
|
|
77
23
|
|
|
78
24
|
const MAIN_TAB_VALUE = "main_##Q$SC^#S6";
|
|
79
25
|
|
|
26
|
+
export type OnUpdateParams = {
|
|
27
|
+
entity: Entity<any>,
|
|
28
|
+
status: EntityStatus,
|
|
29
|
+
path: string,
|
|
30
|
+
entityId?: string;
|
|
31
|
+
selectedTab?: string;
|
|
32
|
+
collection: EntityCollection<any>
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type OnTabChangeParams<M extends Record<string, any>> = {
|
|
36
|
+
path: string;
|
|
37
|
+
entityId?: string;
|
|
38
|
+
selectedTab?: string;
|
|
39
|
+
collection: EntityCollection<M>;
|
|
40
|
+
};
|
|
41
|
+
|
|
80
42
|
export interface EntityEditViewProps<M extends Record<string, any>> {
|
|
81
43
|
path: string;
|
|
82
44
|
collection: EntityCollection<M>;
|
|
83
45
|
entityId?: string;
|
|
46
|
+
databaseId?: string;
|
|
84
47
|
copy?: boolean;
|
|
85
|
-
|
|
48
|
+
selectedTab?: string;
|
|
86
49
|
parentCollectionIds: string[];
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
50
|
+
onValuesModified?: (modified: boolean) => void;
|
|
51
|
+
onSaved?: (params: OnUpdateParams) => void;
|
|
52
|
+
onTabChange?: (props: OnTabChangeParams<M>) => void;
|
|
53
|
+
layout?: "side_panel" | "full_screen";
|
|
54
|
+
barActions?: React.ReactNode;
|
|
55
|
+
formProps?: Partial<EntityFormProps<M>>
|
|
90
56
|
}
|
|
91
57
|
|
|
92
58
|
/**
|
|
93
59
|
* This is the default view that is used as the content of a side panel when
|
|
94
60
|
* an entity is opened.
|
|
95
|
-
* You probably don't want to use this view directly since it is bound to the
|
|
96
|
-
* side panel.
|
|
97
61
|
*/
|
|
98
62
|
export function EntityEditView<M extends Record<string, any>, USER extends User>({
|
|
99
63
|
entityId,
|
|
100
64
|
...props
|
|
101
65
|
}: EntityEditViewProps<M>) {
|
|
66
|
+
|
|
102
67
|
const {
|
|
103
68
|
entity,
|
|
104
69
|
dataLoading,
|
|
@@ -108,1018 +73,331 @@ export function EntityEditView<M extends Record<string, any>, USER extends User>
|
|
|
108
73
|
path: props.path,
|
|
109
74
|
entityId: entityId,
|
|
110
75
|
collection: props.collection,
|
|
76
|
+
databaseId: props.databaseId,
|
|
111
77
|
useCache: false
|
|
112
78
|
});
|
|
113
79
|
|
|
114
|
-
|
|
115
|
-
|
|
80
|
+
const cachedValues = entityId
|
|
81
|
+
? getEntityFromCache(props.path + "/" + entityId)
|
|
82
|
+
: getEntityFromCache(props.path + "#new");
|
|
83
|
+
|
|
84
|
+
const authController = useAuthController();
|
|
85
|
+
|
|
86
|
+
const initialStatus = props.copy ? "copy" : (entityId ? "existing" : "new");
|
|
87
|
+
const [status, setStatus] = useState<EntityStatus>(initialStatus);
|
|
88
|
+
|
|
89
|
+
const canEdit = useMemo(() => {
|
|
90
|
+
if (status === "new" || status === "copy") {
|
|
91
|
+
return true;
|
|
92
|
+
} else {
|
|
93
|
+
return entity ? canEditEntity(props.collection, authController, props.path, entity ?? null) : undefined;
|
|
94
|
+
}
|
|
95
|
+
}, [authController, entity, status]);
|
|
96
|
+
|
|
97
|
+
if ((dataLoading && !cachedValues) || (!entity || canEdit === undefined) && (status === "existing" || status === "copy")) {
|
|
98
|
+
return <CircularProgressCenter/>;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (entityId && !entity && !cachedValues) {
|
|
102
|
+
console.error(`Entity with id ${entityId} not found in collection ${props.path}`);
|
|
116
103
|
}
|
|
117
104
|
|
|
118
|
-
if (
|
|
119
|
-
|
|
105
|
+
if (!canEdit) {
|
|
106
|
+
return <div className={"flex flex-col"}>
|
|
107
|
+
<Typography className={"mt-16 mb-8 mx-8"} variant={"h4"}>
|
|
108
|
+
{props.collection.singularName ?? props.collection.name}
|
|
109
|
+
</Typography>
|
|
110
|
+
<EntityView
|
|
111
|
+
className={"px-8"}
|
|
112
|
+
entity={entity as Entity<M>}
|
|
113
|
+
path={props.path}
|
|
114
|
+
collection={props.collection}/>
|
|
115
|
+
</div>
|
|
120
116
|
}
|
|
121
117
|
|
|
122
118
|
return <EntityEditViewInner<M> {...props}
|
|
123
119
|
entityId={entityId}
|
|
124
120
|
entity={entity}
|
|
125
|
-
|
|
121
|
+
cachedDirtyValues={cachedValues as Partial<M>}
|
|
122
|
+
dataLoading={dataLoading}
|
|
123
|
+
status={status}
|
|
124
|
+
setStatus={setStatus}
|
|
125
|
+
/>;
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
export function EntityEditViewInner<M extends Record<string, any>>({
|
|
129
129
|
path,
|
|
130
|
-
entityId
|
|
131
|
-
|
|
132
|
-
copy,
|
|
130
|
+
entityId,
|
|
131
|
+
selectedTab: selectedTabProp,
|
|
133
132
|
collection,
|
|
134
133
|
parentCollectionIds,
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
134
|
+
onValuesModified,
|
|
135
|
+
onSaved,
|
|
136
|
+
onTabChange,
|
|
138
137
|
entity,
|
|
139
|
-
|
|
138
|
+
cachedDirtyValues,
|
|
139
|
+
dataLoading,
|
|
140
|
+
layout = "side_panel",
|
|
141
|
+
barActions,
|
|
142
|
+
status,
|
|
143
|
+
setStatus,
|
|
144
|
+
formProps,
|
|
140
145
|
}: EntityEditViewProps<M> & {
|
|
141
146
|
entity?: Entity<M>,
|
|
142
|
-
|
|
147
|
+
cachedDirtyValues?: Partial<M>, // dirty cached entity in memory
|
|
148
|
+
dataLoading: boolean,
|
|
149
|
+
status: EntityStatus,
|
|
150
|
+
setStatus: (status: EntityStatus) => void,
|
|
143
151
|
}) {
|
|
144
152
|
|
|
145
|
-
if (collection.customId && collection.formAutoSave) {
|
|
146
|
-
console.warn(`The collection ${collection.path} has customId and formAutoSave enabled. This is not supported and formAutoSave will be ignored`);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const [saving, setSaving] = useState(false);
|
|
150
|
-
/**
|
|
151
|
-
* These are the values that are being saved. They are debounced.
|
|
152
|
-
* We use this only when autoSave is enabled.
|
|
153
|
-
*/
|
|
154
|
-
const [valuesToBeSaved, setValuesToBeSaved] = useState<EntityValues<M> | undefined>(undefined);
|
|
155
|
-
useDebouncedCallback(valuesToBeSaved, () => {
|
|
156
|
-
if (valuesToBeSaved)
|
|
157
|
-
saveEntity({
|
|
158
|
-
entityId: usedEntity?.id,
|
|
159
|
-
collection,
|
|
160
|
-
path,
|
|
161
|
-
values: valuesToBeSaved,
|
|
162
|
-
closeAfterSave: false
|
|
163
|
-
});
|
|
164
|
-
}, false, 2000);
|
|
165
|
-
|
|
166
|
-
// const largeLayout = useLargeLayout();
|
|
167
|
-
// const largeLayoutTabSelected = useRef(!largeLayout);
|
|
168
|
-
// const resolvedFormWidth: string = typeof formWidth === "number" ? `${formWidth}px` : formWidth ?? FORM_CONTAINER_WIDTH;
|
|
169
|
-
|
|
170
|
-
const inputCollection = collection;
|
|
171
|
-
|
|
172
|
-
const authController = useAuthController();
|
|
173
|
-
const dataSource = useDataSource(collection);
|
|
174
|
-
const sideDialogContext = useSideDialogContext();
|
|
175
|
-
const sideEntityController = useSideEntityController();
|
|
176
|
-
const snackbarController = useSnackbarController();
|
|
177
|
-
const customizationController = useCustomizationController();
|
|
178
153
|
const context = useFireCMSContext();
|
|
179
154
|
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
const analyticsController = useAnalyticsController();
|
|
183
|
-
|
|
184
|
-
const initialResolvedCollection = useMemo(() => resolveCollection({
|
|
185
|
-
collection: inputCollection,
|
|
186
|
-
path,
|
|
187
|
-
values: entity?.values,
|
|
188
|
-
propertyConfigs: 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]);
|
|
155
|
+
const [usedEntity, setUsedEntity] = useState<Entity<M> | undefined>(entity);
|
|
208
156
|
|
|
209
|
-
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
if (entity)
|
|
159
|
+
setUsedEntity(entity);
|
|
160
|
+
}, [entity]);
|
|
210
161
|
|
|
211
|
-
//
|
|
212
|
-
|
|
213
|
-
// setInternalValues(values);
|
|
214
|
-
// if (onValuesChanged)
|
|
215
|
-
// onValuesChanged(values);
|
|
216
|
-
// if (autoSave && values && !equal(values, initialValues)) {
|
|
217
|
-
// save(values);
|
|
218
|
-
// }
|
|
219
|
-
// };
|
|
162
|
+
// Instead of using a ref (which does not trigger re-render), we use state for the form context.
|
|
163
|
+
const [formContext, setFormContext] = useState<FormContext<M> | undefined>(undefined);
|
|
220
164
|
|
|
221
|
-
const
|
|
222
|
-
const [savingError, setSavingError] = React.useState<Error | undefined>();
|
|
165
|
+
const largeLayout = useLargeLayout();
|
|
223
166
|
|
|
224
|
-
const
|
|
167
|
+
const customizationController = useCustomizationController();
|
|
225
168
|
|
|
226
|
-
const defaultSelectedView =
|
|
169
|
+
const defaultSelectedView = useMemo(() => resolveDefaultSelectedView(
|
|
227
170
|
collection ? collection.defaultSelectedView : undefined,
|
|
228
171
|
{
|
|
229
172
|
status,
|
|
230
173
|
entityId
|
|
231
174
|
}
|
|
232
|
-
);
|
|
175
|
+
), []);
|
|
233
176
|
|
|
234
|
-
const [selectedTab, setSelectedTab] = useState<string>(defaultSelectedView ?? MAIN_TAB_VALUE);
|
|
177
|
+
const [selectedTab, setSelectedTab] = useState<string>(selectedTabProp ?? defaultSelectedView ?? MAIN_TAB_VALUE);
|
|
235
178
|
|
|
236
|
-
|
|
179
|
+
useEffect(() => {
|
|
180
|
+
if ((selectedTabProp ?? MAIN_TAB_VALUE) !== selectedTab) {
|
|
181
|
+
setSelectedTab(selectedTabProp ?? MAIN_TAB_VALUE);
|
|
182
|
+
}
|
|
183
|
+
}, [selectedTabProp]);
|
|
237
184
|
|
|
238
185
|
const subcollections = (collection.subcollections ?? []).filter(c => !c.hideFromNavigation);
|
|
239
186
|
const subcollectionsCount = subcollections?.length ?? 0;
|
|
240
187
|
const customViews = collection.entityViews;
|
|
241
188
|
const customViewsCount = customViews?.length ?? 0;
|
|
242
|
-
const autoSave = collection.formAutoSave && !collection.customId;
|
|
243
|
-
|
|
244
189
|
const hasAdditionalViews = customViewsCount > 0 || subcollectionsCount > 0;
|
|
245
190
|
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
useEffect(() => {
|
|
252
|
-
if (entity)
|
|
253
|
-
setUsedEntity(entity);
|
|
254
|
-
}, [entity]);
|
|
255
|
-
|
|
256
|
-
useEffect(() => {
|
|
257
|
-
if (status === "new") {
|
|
258
|
-
setReadOnly(false);
|
|
259
|
-
} else {
|
|
260
|
-
const editEnabled = usedEntity ? canEditEntity(collection, authController, path, usedEntity ?? null) : false;
|
|
261
|
-
if (usedEntity)
|
|
262
|
-
setReadOnly(!editEnabled);
|
|
263
|
-
}
|
|
264
|
-
}, [authController, usedEntity, status]);
|
|
265
|
-
|
|
266
|
-
const onPreSaveHookError = useCallback((e: Error) => {
|
|
267
|
-
setSaving(false);
|
|
268
|
-
snackbarController.open({
|
|
269
|
-
type: "error",
|
|
270
|
-
message: "Error before saving: " + e?.message
|
|
271
|
-
});
|
|
272
|
-
console.error(e);
|
|
273
|
-
}, [snackbarController]);
|
|
274
|
-
|
|
275
|
-
const onSaveSuccessHookError = useCallback((e: Error) => {
|
|
276
|
-
setSaving(false);
|
|
277
|
-
snackbarController.open({
|
|
278
|
-
type: "error",
|
|
279
|
-
message: "Error after saving (entity is saved): " + e?.message
|
|
280
|
-
});
|
|
281
|
-
console.error(e);
|
|
282
|
-
}, [snackbarController]);
|
|
283
|
-
|
|
284
|
-
const onSaveSuccess = (updatedEntity: Entity<M>, closeAfterSave: boolean) => {
|
|
285
|
-
|
|
286
|
-
setSaving(false);
|
|
287
|
-
if (!autoSave)
|
|
288
|
-
snackbarController.open({
|
|
289
|
-
type: "success",
|
|
290
|
-
message: `${collection.singularName ?? collection.name}: Saved correctly`
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
setUsedEntity(updatedEntity);
|
|
294
|
-
setStatus("existing");
|
|
295
|
-
|
|
296
|
-
onValuesAreModified(false);
|
|
297
|
-
|
|
298
|
-
if (onUpdate)
|
|
299
|
-
onUpdate({ entity: updatedEntity });
|
|
300
|
-
|
|
301
|
-
if (closeAfterSave) {
|
|
302
|
-
sideDialogContext.setBlocked(false);
|
|
303
|
-
sideDialogContext.close(true);
|
|
304
|
-
onClose?.();
|
|
305
|
-
} else if (status !== "existing") {
|
|
306
|
-
sideEntityController.replace({
|
|
307
|
-
path,
|
|
308
|
-
entityId: updatedEntity.id,
|
|
309
|
-
selectedSubPath: MAIN_TAB_VALUE === selectedTab ? undefined : selectedTab,
|
|
310
|
-
updateUrl: true,
|
|
311
|
-
collection,
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
};
|
|
316
|
-
|
|
317
|
-
const onSaveFailure = useCallback((e: Error) => {
|
|
318
|
-
|
|
319
|
-
setSaving(false);
|
|
320
|
-
snackbarController.open({
|
|
321
|
-
type: "error",
|
|
322
|
-
message: "Error saving: " + e?.message
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
console.error("Error saving entity", path, entityId);
|
|
326
|
-
console.error(e);
|
|
327
|
-
}, [entityId, path, snackbarController]);
|
|
328
|
-
|
|
329
|
-
const saveEntity = ({
|
|
330
|
-
values,
|
|
331
|
-
previousValues,
|
|
332
|
-
closeAfterSave,
|
|
333
|
-
entityId,
|
|
334
|
-
collection,
|
|
335
|
-
path
|
|
336
|
-
}: {
|
|
337
|
-
collection: EntityCollection<M>,
|
|
338
|
-
path: string,
|
|
339
|
-
entityId: string | undefined,
|
|
340
|
-
values: M,
|
|
341
|
-
previousValues?: M,
|
|
342
|
-
closeAfterSave: boolean,
|
|
343
|
-
}) => {
|
|
344
|
-
setSaving(true);
|
|
345
|
-
return saveEntityWithCallbacks({
|
|
346
|
-
path,
|
|
347
|
-
entityId,
|
|
348
|
-
values,
|
|
349
|
-
previousValues,
|
|
350
|
-
collection,
|
|
351
|
-
status,
|
|
352
|
-
dataSource,
|
|
353
|
-
context,
|
|
354
|
-
onSaveSuccess: (updatedEntity: Entity<M>) => onSaveSuccess(updatedEntity, closeAfterSave),
|
|
355
|
-
onSaveFailure,
|
|
356
|
-
onPreSaveHookError,
|
|
357
|
-
onSaveSuccessHookError
|
|
358
|
-
}).then();
|
|
359
|
-
};
|
|
360
|
-
|
|
361
|
-
const onSaveEntityRequest = async ({
|
|
362
|
-
collection,
|
|
363
|
-
path,
|
|
364
|
-
entityId,
|
|
365
|
-
values,
|
|
366
|
-
previousValues,
|
|
367
|
-
closeAfterSave,
|
|
368
|
-
autoSave
|
|
369
|
-
}: EntityFormSaveParams<M>): Promise<void> => {
|
|
370
|
-
if (!status)
|
|
371
|
-
return;
|
|
372
|
-
|
|
373
|
-
if (autoSave) {
|
|
374
|
-
setValuesToBeSaved(values);
|
|
375
|
-
} else {
|
|
376
|
-
return saveEntity({
|
|
377
|
-
collection,
|
|
378
|
-
path,
|
|
379
|
-
entityId,
|
|
380
|
-
values,
|
|
381
|
-
previousValues,
|
|
382
|
-
closeAfterSave
|
|
383
|
-
});
|
|
384
|
-
}
|
|
385
|
-
};
|
|
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
|
-
propertyConfigs: 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
|
-
};
|
|
191
|
+
const {
|
|
192
|
+
resolvedEntityViews,
|
|
193
|
+
selectedEntityView,
|
|
194
|
+
selectedSecondaryForm
|
|
195
|
+
} = resolvedSelectedEntityView(customViews, customizationController, selectedTab);
|
|
487
196
|
|
|
488
|
-
const
|
|
489
|
-
.map(e => resolveEntityView(e, customizationController.entityViews))
|
|
490
|
-
.filter(Boolean) as EntityCustomView[]
|
|
491
|
-
: [];
|
|
197
|
+
const actionsAtTheBottom = !largeLayout || layout === "side_panel" || selectedEntityView?.includeActions === "bottom";
|
|
492
198
|
|
|
493
|
-
const
|
|
494
|
-
const shouldShowEntityActions = !autoSave && (selectedTab === MAIN_TAB_VALUE || selectedEntityView?.includeActions);
|
|
199
|
+
const mainViewVisible = selectedTab === MAIN_TAB_VALUE || Boolean(selectedSecondaryForm);
|
|
495
200
|
|
|
496
201
|
const customViewsView: React.ReactNode[] | undefined = customViews && resolvedEntityViews
|
|
497
|
-
.
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
return null;
|
|
501
|
-
if (selectedTab !== customView.key)
|
|
502
|
-
return null;
|
|
503
|
-
const Builder = customView.Builder;
|
|
504
|
-
if (!Builder) {
|
|
505
|
-
console.error("customView.Builder is not defined");
|
|
506
|
-
return null;
|
|
507
|
-
}
|
|
508
|
-
return <div
|
|
509
|
-
className={cls(defaultBorderMixin,
|
|
510
|
-
"relative flex-grow w-full h-full overflow-auto ")}
|
|
511
|
-
key={`custom_view_${customView.key}`}
|
|
512
|
-
role="tabpanel">
|
|
513
|
-
<ErrorBoundary>
|
|
514
|
-
{formContext && <Builder
|
|
515
|
-
collection={collection}
|
|
516
|
-
entity={usedEntity}
|
|
517
|
-
modifiedValues={formex.values ?? usedEntity?.values}
|
|
518
|
-
formContext={formContext}
|
|
519
|
-
/>}
|
|
520
|
-
</ErrorBoundary>
|
|
521
|
-
</div>;
|
|
522
|
-
}
|
|
523
|
-
).filter(Boolean);
|
|
524
|
-
|
|
525
|
-
const globalLoading = (dataLoading && !usedEntity) ||
|
|
526
|
-
((!usedEntity || readOnly === undefined) && (status === "existing" || status === "copy"));
|
|
527
|
-
|
|
528
|
-
const loading = globalLoading || saving;
|
|
529
|
-
|
|
530
|
-
const subCollectionsViews = subcollections && subcollections.map(
|
|
531
|
-
(subcollection, colIndex) => {
|
|
532
|
-
const subcollectionId = subcollection.id ?? subcollection.path;
|
|
533
|
-
const fullPath = usedEntity ? `${path}/${usedEntity?.id}/${removeInitialAndTrailingSlashes(subcollectionId)}` : undefined;
|
|
534
|
-
if (selectedTab !== subcollectionId)
|
|
202
|
+
.filter(e => !e.includeActions)
|
|
203
|
+
.map((customView) => {
|
|
204
|
+
if (!customView)
|
|
535
205
|
return null;
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
{loading && <CircularProgressCenter/>}
|
|
543
|
-
|
|
544
|
-
{!globalLoading &&
|
|
545
|
-
(usedEntity && fullPath
|
|
546
|
-
? <EntityCollectionView
|
|
547
|
-
fullPath={fullPath}
|
|
548
|
-
parentCollectionIds={[...parentCollectionIds, collection.id]}
|
|
549
|
-
isSubCollection={true}
|
|
550
|
-
{...subcollection}/>
|
|
551
|
-
: <div
|
|
552
|
-
className="flex items-center justify-center w-full h-full p-3">
|
|
553
|
-
<Typography variant={"label"}>
|
|
554
|
-
You need to save your entity before
|
|
555
|
-
adding additional collections
|
|
556
|
-
</Typography>
|
|
557
|
-
</div>)
|
|
558
|
-
}
|
|
206
|
+
const Builder = customView.Builder;
|
|
207
|
+
if (!Builder) {
|
|
208
|
+
console.error("customView.Builder is not defined");
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
559
211
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
212
|
+
return <div
|
|
213
|
+
className={cls(defaultBorderMixin,
|
|
214
|
+
"relative flex-1 w-full h-full overflow-auto",
|
|
215
|
+
{ "hidden": selectedTab !== customView.key }
|
|
216
|
+
)}
|
|
217
|
+
key={`custom_view_${customView.key}`}
|
|
218
|
+
role="tabpanel">
|
|
219
|
+
<ErrorBoundary>
|
|
220
|
+
{formContext && <Builder
|
|
221
|
+
collection={collection}
|
|
222
|
+
entity={usedEntity}
|
|
223
|
+
modifiedValues={formContext.formex.values ?? usedEntity?.values}
|
|
224
|
+
formContext={formContext}
|
|
225
|
+
/>}
|
|
226
|
+
</ErrorBoundary>
|
|
227
|
+
</div>;
|
|
228
|
+
}).filter(Boolean);
|
|
229
|
+
|
|
230
|
+
const globalLoading = dataLoading && !usedEntity;
|
|
231
|
+
|
|
232
|
+
const subCollectionsViews = subcollections && subcollections.map((subcollection) => {
|
|
233
|
+
const subcollectionId = subcollection.id ?? subcollection.path;
|
|
234
|
+
const fullPath = usedEntity ? `${path}/${usedEntity?.id}/${removeInitialAndTrailingSlashes(subcollectionId)}` : undefined;
|
|
235
|
+
if (selectedTab !== subcollectionId) return null;
|
|
236
|
+
return (
|
|
237
|
+
<div
|
|
238
|
+
className={"relative flex-1 h-full overflow-auto w-full"}
|
|
239
|
+
key={`subcol_${subcollectionId}`}
|
|
240
|
+
role="tabpanel">
|
|
241
|
+
|
|
242
|
+
{globalLoading && <CircularProgressCenter/>}
|
|
243
|
+
|
|
244
|
+
{!globalLoading &&
|
|
245
|
+
(usedEntity && fullPath
|
|
246
|
+
? <EntityCollectionView
|
|
247
|
+
fullPath={fullPath}
|
|
248
|
+
parentCollectionIds={[...parentCollectionIds, collection.id]}
|
|
249
|
+
isSubCollection={true}
|
|
250
|
+
updateUrl={false}
|
|
251
|
+
{...subcollection}
|
|
252
|
+
openEntityMode={layout}/>
|
|
253
|
+
: <div className="flex items-center justify-center w-full h-full p-3">
|
|
254
|
+
<Typography variant={"label"}>
|
|
255
|
+
You need to save your entity before
|
|
256
|
+
adding additional collections
|
|
257
|
+
</Typography>
|
|
258
|
+
</div>)
|
|
259
|
+
}
|
|
564
260
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
}
|
|
261
|
+
</div>
|
|
262
|
+
);
|
|
263
|
+
}).filter(Boolean);
|
|
568
264
|
|
|
569
265
|
const onSideTabClick = (value: string) => {
|
|
570
266
|
setSelectedTab(value);
|
|
571
267
|
if (status === "existing") {
|
|
572
|
-
|
|
268
|
+
onTabChange?.({
|
|
573
269
|
path,
|
|
574
270
|
entityId,
|
|
575
|
-
|
|
576
|
-
updateUrl: true,
|
|
271
|
+
selectedTab: value === MAIN_TAB_VALUE ? undefined : value,
|
|
577
272
|
collection,
|
|
578
273
|
});
|
|
579
274
|
}
|
|
580
275
|
};
|
|
581
276
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
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));
|
|
642
|
-
}
|
|
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?.(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
|
-
: <>
|
|
782
|
-
{additionalField.value?.({
|
|
783
|
-
entity,
|
|
784
|
-
context
|
|
785
|
-
})?.toString()}
|
|
786
|
-
</>;
|
|
787
|
-
return (
|
|
788
|
-
<div key={`additional_${key}`}>
|
|
789
|
-
<LabelWithIconAndTooltip
|
|
790
|
-
propertyKey={key}
|
|
791
|
-
icon={<NotesIcon size={"small"}/>}
|
|
792
|
-
title={additionalField.name}
|
|
793
|
-
className={"text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>
|
|
794
|
-
<div
|
|
795
|
-
className={cls(paperMixin, "min-h-14 p-4 md:p-6 overflow-x-scroll no-scrollbar")}>
|
|
796
|
-
|
|
797
|
-
<ErrorBoundary>
|
|
798
|
-
{child}
|
|
799
|
-
</ErrorBoundary>
|
|
800
|
-
|
|
801
|
-
</div>
|
|
802
|
-
</div>
|
|
803
|
-
);
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
console.warn(`Property ${key} not found in collection ${resolvedCollection.name} in properties or additional fields. Skipping.`);
|
|
807
|
-
return null;
|
|
808
|
-
})
|
|
809
|
-
.filter(Boolean)}
|
|
810
|
-
|
|
811
|
-
</>);
|
|
812
|
-
|
|
813
|
-
const disabled = formex.isSubmitting || (!modified && status === "existing");
|
|
814
|
-
const formRef = React.useRef<HTMLDivElement>(null);
|
|
815
|
-
|
|
816
|
-
const entityActions = getActionsForEntity({
|
|
817
|
-
entity,
|
|
818
|
-
customEntityActions: inputCollection.entityActions
|
|
819
|
-
});
|
|
820
|
-
const formActions = entityActions.filter(a => a.includeInForm === undefined || a.includeInForm);
|
|
821
|
-
|
|
822
|
-
const dialogActions = <DialogActions position={"absolute"}>
|
|
823
|
-
|
|
824
|
-
{savingError &&
|
|
825
|
-
<div className="text-right">
|
|
826
|
-
<Typography color={"error"}>
|
|
827
|
-
{savingError.message}
|
|
828
|
-
</Typography>
|
|
829
|
-
</div>}
|
|
830
|
-
|
|
831
|
-
{entity && formActions.length > 0 && <div className="flex-grow flex overflow-auto no-scrollbar">
|
|
832
|
-
{formActions.map(action => (
|
|
833
|
-
<IconButton
|
|
834
|
-
key={action.name}
|
|
835
|
-
color="primary"
|
|
836
|
-
onClick={(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
|
837
|
-
event.stopPropagation();
|
|
838
|
-
if (entity)
|
|
839
|
-
action.onClick({
|
|
840
|
-
entity,
|
|
841
|
-
fullPath: resolvedCollection.path,
|
|
842
|
-
collection: resolvedCollection,
|
|
843
|
-
context,
|
|
844
|
-
sideEntityController
|
|
845
|
-
});
|
|
846
|
-
}}>
|
|
847
|
-
{action.icon}
|
|
848
|
-
</IconButton>
|
|
849
|
-
))}
|
|
850
|
-
</div>}
|
|
851
|
-
{formex.isSubmitting && <CircularProgress size={"small"}/>}
|
|
852
|
-
<Button
|
|
853
|
-
variant="text"
|
|
854
|
-
disabled={disabled || formex.isSubmitting}
|
|
855
|
-
type="reset">
|
|
856
|
-
{status === "existing" ? "Discard" : "Clear"}
|
|
857
|
-
</Button>
|
|
858
|
-
|
|
859
|
-
<Button
|
|
860
|
-
variant="text"
|
|
861
|
-
color="primary"
|
|
862
|
-
type="submit"
|
|
863
|
-
disabled={disabled || formex.isSubmitting}
|
|
864
|
-
onClick={() => {
|
|
865
|
-
closeAfterSaveRef.current = false;
|
|
866
|
-
}}>
|
|
867
|
-
{status === "existing" && "Save"}
|
|
868
|
-
{status === "copy" && "Create copy"}
|
|
869
|
-
{status === "new" && "Create"}
|
|
870
|
-
</Button>
|
|
871
|
-
|
|
872
|
-
<Button
|
|
873
|
-
variant="filled"
|
|
874
|
-
color="primary"
|
|
875
|
-
type="submit"
|
|
876
|
-
disabled={disabled || formex.isSubmitting}
|
|
877
|
-
onClick={() => {
|
|
878
|
-
closeAfterSaveRef.current = true;
|
|
879
|
-
}}>
|
|
880
|
-
{status === "existing" && "Save and close"}
|
|
881
|
-
{status === "copy" && "Create copy and close"}
|
|
882
|
-
{status === "new" && "Create and close"}
|
|
883
|
-
</Button>
|
|
884
|
-
|
|
885
|
-
</DialogActions>;
|
|
886
|
-
|
|
887
|
-
function buildForm() {
|
|
888
|
-
|
|
889
|
-
let form = <>
|
|
890
|
-
|
|
891
|
-
{pluginActions.length > 0 && <div
|
|
892
|
-
className={cls("w-full flex justify-end items-center sticky top-0 right-0 left-0 z-10 bg-opacity-60 bg-surface-accent-200 dark:bg-opacity-60 dark:bg-surface-accent-800 backdrop-blur-md")}>
|
|
893
|
-
{pluginActions}
|
|
894
|
-
</div>}
|
|
895
|
-
|
|
896
|
-
<div className="pt-12 pb-16 pl-4 sm:px-8 md:px-10">
|
|
897
|
-
<div
|
|
898
|
-
className={`w-full py-2 flex flex-col items-start mt-${4 + (pluginActions ? 8 : 0)} lg:mt-${8 + (pluginActions ? 8 : 0)} mb-8`}>
|
|
899
|
-
|
|
900
|
-
<Typography
|
|
901
|
-
className={"mt-4 flex-grow line-clamp-1 " + inputCollection.hideIdFromForm ? "mb-2" : "mb-0"}
|
|
902
|
-
variant={"h4"}>{title ?? inputCollection.singularName ?? inputCollection.name}
|
|
903
|
-
</Typography>
|
|
904
|
-
<Alert color={"base"} className={"w-full"} size={"small"}>
|
|
905
|
-
<code className={"text-xs select-all"}>{path}/{entityId}</code>
|
|
906
|
-
</Alert>
|
|
907
|
-
</div>
|
|
908
|
-
|
|
909
|
-
{!collection.hideIdFromForm &&
|
|
910
|
-
<CustomIdField customId={inputCollection.customId}
|
|
911
|
-
entityId={entityId}
|
|
912
|
-
status={status}
|
|
913
|
-
onChange={setEntityId}
|
|
914
|
-
error={entityIdError}
|
|
915
|
-
loading={customIdLoading}
|
|
916
|
-
entity={entity}/>}
|
|
917
|
-
|
|
918
|
-
{entityId && formContext && <>
|
|
919
|
-
<div className="mt-12 flex flex-col gap-8"
|
|
920
|
-
ref={formRef}>
|
|
921
|
-
|
|
922
|
-
{formFields}
|
|
923
|
-
|
|
924
|
-
<ErrorFocus containerRef={formRef}/>
|
|
925
|
-
|
|
926
|
-
</div>
|
|
927
|
-
|
|
928
|
-
<div className="h-14"/>
|
|
929
|
-
|
|
930
|
-
</>}
|
|
931
|
-
|
|
932
|
-
</div>
|
|
933
|
-
</>;
|
|
934
|
-
|
|
935
|
-
if (plugins) {
|
|
936
|
-
plugins.forEach((plugin: FireCMSPlugin) => {
|
|
937
|
-
if (plugin.form?.provider) {
|
|
938
|
-
form = (
|
|
939
|
-
<plugin.form.provider.Component
|
|
940
|
-
status={status}
|
|
941
|
-
path={path}
|
|
942
|
-
collection={collection}
|
|
943
|
-
onDiscard={onDiscard}
|
|
944
|
-
entity={usedEntity}
|
|
945
|
-
context={context}
|
|
946
|
-
formContext={formContext}
|
|
947
|
-
{...plugin.form.provider.props}>
|
|
948
|
-
{form}
|
|
949
|
-
</plugin.form.provider.Component>
|
|
950
|
-
);
|
|
951
|
-
}
|
|
952
|
-
});
|
|
953
|
-
}
|
|
954
|
-
return <ErrorBoundary>{form}</ErrorBoundary>;
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
const entityView = (readOnly === undefined)
|
|
958
|
-
? <></>
|
|
959
|
-
: (!readOnly
|
|
960
|
-
? buildForm()
|
|
961
|
-
: (
|
|
962
|
-
<>
|
|
963
|
-
<Typography
|
|
964
|
-
className={"mt-16 mb-8 mx-8"}
|
|
965
|
-
variant={"h4"}>{collection.singularName ?? collection.name}
|
|
966
|
-
</Typography>
|
|
967
|
-
<EntityView
|
|
968
|
-
className={"px-12"}
|
|
969
|
-
entity={usedEntity as Entity<M>}
|
|
970
|
-
path={path}
|
|
971
|
-
collection={collection}/>
|
|
972
|
-
|
|
973
|
-
</>
|
|
974
|
-
));
|
|
975
|
-
|
|
976
|
-
const subcollectionTabs = subcollections && subcollections.map(
|
|
977
|
-
(subcollection) =>
|
|
978
|
-
<Tab
|
|
979
|
-
className="text-sm min-w-[140px]"
|
|
980
|
-
value={subcollection.id}
|
|
981
|
-
key={`entity_detail_collection_tab_${subcollection.name}`}>
|
|
982
|
-
{subcollection.name}
|
|
983
|
-
</Tab>
|
|
277
|
+
// Render the main entity form view (or a read-only view if the user cannot edit)
|
|
278
|
+
const entityView = <EntityForm<M>
|
|
279
|
+
collection={collection}
|
|
280
|
+
path={path}
|
|
281
|
+
entityId={entityId ?? usedEntity?.id}
|
|
282
|
+
onValuesModified={onValuesModified}
|
|
283
|
+
entity={entity}
|
|
284
|
+
initialDirtyValues={cachedDirtyValues}
|
|
285
|
+
openEntityMode={layout}
|
|
286
|
+
forceActionsAtTheBottom={actionsAtTheBottom}
|
|
287
|
+
initialStatus={status}
|
|
288
|
+
className={cls(!mainViewVisible ? "hidden" : "", formProps?.className)}
|
|
289
|
+
EntityFormActionsComponent={EntityEditViewFormActions}
|
|
290
|
+
{...formProps}
|
|
291
|
+
onEntityChange={(entity) => {
|
|
292
|
+
setUsedEntity(entity);
|
|
293
|
+
formProps?.onEntityChange?.(entity);
|
|
294
|
+
}}
|
|
295
|
+
onStatusChange={(status) => {
|
|
296
|
+
setStatus(status);
|
|
297
|
+
formProps?.onStatusChange?.(status);
|
|
298
|
+
}}
|
|
299
|
+
onFormContextReady={(formContext) => {
|
|
300
|
+
setFormContext(formContext);
|
|
301
|
+
formProps?.onFormContextReady?.(formContext);
|
|
302
|
+
}}
|
|
303
|
+
onSaved={(params) => {
|
|
304
|
+
const res = {
|
|
305
|
+
...params,
|
|
306
|
+
selectedTab: MAIN_TAB_VALUE === selectedTab ? undefined : selectedTab,
|
|
307
|
+
};
|
|
308
|
+
onSaved?.(res);
|
|
309
|
+
formProps?.onSaved?.(res);
|
|
310
|
+
}}
|
|
311
|
+
Builder={selectedSecondaryForm?.Builder}
|
|
312
|
+
/>;
|
|
313
|
+
|
|
314
|
+
const subcollectionTabs = subcollections && subcollections.map((subcollection) =>
|
|
315
|
+
<Tab
|
|
316
|
+
className="text-sm min-w-[120px]"
|
|
317
|
+
value={subcollection.id}
|
|
318
|
+
key={`entity_detail_collection_tab_${subcollection.name}`}>
|
|
319
|
+
{subcollection.name}
|
|
320
|
+
</Tab>
|
|
984
321
|
);
|
|
985
322
|
|
|
986
|
-
const customViewTabs = resolvedEntityViews.map(
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
{view.name}
|
|
994
|
-
</Tab>
|
|
323
|
+
const customViewTabs = resolvedEntityViews.map((view) =>
|
|
324
|
+
<Tab
|
|
325
|
+
className="text-sm min-w-[120px]"
|
|
326
|
+
value={view.key}
|
|
327
|
+
key={`entity_detail_collection_tab_${view.name}`}>
|
|
328
|
+
{view.name}
|
|
329
|
+
</Tab>
|
|
995
330
|
);
|
|
996
331
|
|
|
997
|
-
|
|
998
|
-
if (entityId && onIdChange)
|
|
999
|
-
onIdChange(entityId);
|
|
1000
|
-
}, [entityId, onIdChange]);
|
|
1001
|
-
|
|
1002
|
-
return (
|
|
1003
|
-
<Formex value={formex}>
|
|
1004
|
-
|
|
1005
|
-
<div className="flex flex-col h-full w-full">
|
|
1006
|
-
|
|
1007
|
-
<div
|
|
1008
|
-
className={cls(defaultBorderMixin, "no-scrollbar h-16 border-b pl-2 pr-2 pt-1 flex items-end overflow-scroll bg-surface-50 dark:bg-surface-950")}>
|
|
332
|
+
const shouldShowTopBar = Boolean(barActions) || hasAdditionalViews;
|
|
1009
333
|
|
|
1010
|
-
|
|
1011
|
-
className="pb-1 self-center">
|
|
1012
|
-
<IconButton
|
|
1013
|
-
onClick={() => {
|
|
1014
|
-
onClose?.();
|
|
1015
|
-
return sideDialogContext.close(false);
|
|
1016
|
-
}}>
|
|
1017
|
-
<CloseIcon size={"small"}/>
|
|
1018
|
-
</IconButton>
|
|
1019
|
-
</div>
|
|
334
|
+
let result = <div className="relative flex flex-col h-full w-full bg-white dark:bg-surface-900">
|
|
1020
335
|
|
|
1021
|
-
|
|
336
|
+
{shouldShowTopBar && <div
|
|
337
|
+
className={cls("h-14 flex overflow-visible overflow-x-scroll w-full no-scrollbar h-14 border-b pl-2 pr-2 pt-1 flex items-end bg-surface-50 dark:bg-surface-900", defaultBorderMixin)}>
|
|
1022
338
|
|
|
1023
|
-
|
|
1024
|
-
className="self-center">
|
|
1025
|
-
<CircularProgress size={"small"}/>
|
|
1026
|
-
</div>}
|
|
339
|
+
{barActions}
|
|
1027
340
|
|
|
1028
|
-
|
|
1029
|
-
value={selectedTab}
|
|
1030
|
-
onValueChange={(value) => {
|
|
1031
|
-
onSideTabClick(value);
|
|
1032
|
-
}}
|
|
1033
|
-
innerClassName="pl-4 pr-4 pt-0">
|
|
341
|
+
<div className={"flex-grow"}/>
|
|
1034
342
|
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
className={`${
|
|
1039
|
-
!hasAdditionalViews ? "hidden" : ""
|
|
1040
|
-
} text-sm min-w-[140px]`}
|
|
1041
|
-
>{collection.singularName ?? collection.name}</Tab>
|
|
1042
|
-
|
|
1043
|
-
{customViewTabs}
|
|
1044
|
-
|
|
1045
|
-
{subcollectionTabs}
|
|
1046
|
-
</Tabs>
|
|
1047
|
-
|
|
1048
|
-
</div>
|
|
343
|
+
{globalLoading && <div className="self-center">
|
|
344
|
+
<CircularProgress size={"small"}/>
|
|
345
|
+
</div>}
|
|
1049
346
|
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
}}
|
|
1056
|
-
noValidate
|
|
1057
|
-
className={"flex-grow h-full flex overflow-auto flex-col w-full"}>
|
|
347
|
+
{hasAdditionalViews && <Tabs
|
|
348
|
+
value={selectedTab}
|
|
349
|
+
onValueChange={(value) => {
|
|
350
|
+
onSideTabClick(value);
|
|
351
|
+
}}>
|
|
1058
352
|
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
353
|
+
<Tab
|
|
354
|
+
disabled={!hasAdditionalViews}
|
|
355
|
+
value={MAIN_TAB_VALUE}
|
|
356
|
+
className={"text-sm min-w-[120px]"}>
|
|
357
|
+
{collection.singularName ?? collection.name}
|
|
358
|
+
</Tab>
|
|
1064
359
|
|
|
1065
|
-
|
|
1066
|
-
? <CircularProgressCenter/>
|
|
1067
|
-
: entityView}
|
|
360
|
+
{customViewTabs}
|
|
1068
361
|
|
|
1069
|
-
|
|
362
|
+
{subcollectionTabs}
|
|
363
|
+
</Tabs>}
|
|
364
|
+
</div>}
|
|
1070
365
|
|
|
1071
|
-
|
|
366
|
+
{globalLoading
|
|
367
|
+
? <div className="w-full pt-12 pb-16 px-4 sm:px-8 md:px-10">
|
|
368
|
+
<CircularProgressCenter/>
|
|
369
|
+
</div>
|
|
370
|
+
: entityView}
|
|
1072
371
|
|
|
1073
|
-
|
|
372
|
+
{/*{secondaryForms}*/}
|
|
1074
373
|
|
|
1075
|
-
|
|
374
|
+
{customViewsView}
|
|
1076
375
|
|
|
1077
|
-
|
|
376
|
+
{subCollectionsViews}
|
|
1078
377
|
|
|
1079
|
-
|
|
1080
|
-
</Formex>
|
|
1081
|
-
);
|
|
1082
|
-
}
|
|
378
|
+
</div>;
|
|
1083
379
|
|
|
1084
|
-
|
|
1085
|
-
status: "new" | "existing" | "copy",
|
|
1086
|
-
entity: Entity<M> | undefined): Partial<EntityValues<M>> {
|
|
380
|
+
const plugins = customizationController.plugins;
|
|
1087
381
|
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
382
|
+
if (plugins) {
|
|
383
|
+
plugins.forEach((plugin: FireCMSPlugin) => {
|
|
384
|
+
if (plugin.form?.provider) {
|
|
385
|
+
result = (
|
|
386
|
+
<plugin.form.provider.Component
|
|
387
|
+
status={status}
|
|
388
|
+
path={path}
|
|
389
|
+
collection={collection}
|
|
390
|
+
entity={usedEntity}
|
|
391
|
+
context={context}
|
|
392
|
+
formContext={formContext}
|
|
393
|
+
{...plugin.form.provider.props}>
|
|
394
|
+
{result}
|
|
395
|
+
</plugin.form.provider.Component>
|
|
396
|
+
);
|
|
397
|
+
}
|
|
1097
398
|
});
|
|
1098
|
-
throw new Error("Form has not been initialised with the correct parameters");
|
|
1099
399
|
}
|
|
1100
|
-
}
|
|
1101
400
|
|
|
1102
|
-
|
|
1103
|
-
collection: ResolvedEntityCollection<M>,
|
|
1104
|
-
path: string,
|
|
1105
|
-
entityId: string | undefined,
|
|
1106
|
-
values: EntityValues<M>,
|
|
1107
|
-
previousValues?: EntityValues<M>,
|
|
1108
|
-
closeAfterSave: boolean,
|
|
1109
|
-
autoSave: boolean
|
|
1110
|
-
};
|
|
1111
|
-
|
|
1112
|
-
export function yupToFormErrors(yupError: ValidationError): Record<string, any> {
|
|
1113
|
-
let errors: Record<string, any> = {};
|
|
1114
|
-
if (yupError.inner) {
|
|
1115
|
-
if (yupError.inner.length === 0) {
|
|
1116
|
-
return setIn(errors, yupError.path!, yupError.message);
|
|
1117
|
-
}
|
|
1118
|
-
for (const err of yupError.inner) {
|
|
1119
|
-
if (!getIn(errors, err.path!)) {
|
|
1120
|
-
errors = setIn(errors, err.path!, err.message);
|
|
1121
|
-
}
|
|
1122
|
-
}
|
|
1123
|
-
}
|
|
1124
|
-
return errors;
|
|
401
|
+
return result;
|
|
1125
402
|
}
|
|
403
|
+
|