@firecms/core 3.0.0-beta.11 → 3.0.0-beta.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/Scaffold.d.ts +4 -0
- package/dist/components/ArrayContainer.d.ts +14 -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 +8 -0
- package/dist/components/EntityJsonPreview.d.ts +3 -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/DefaultAppBar.d.ts +8 -2
- package/dist/core/EntityEditView.d.ts +36 -21
- package/dist/core/EntityEditViewFormActions.d.ts +2 -0
- package/dist/core/FireCMS.d.ts +2 -2
- 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 +49 -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/save.d.ts +1 -1
- 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 +15842 -14396
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +15833 -14389
- package/dist/index.umd.js.map +1 -1
- package/dist/internal/useBuildDataSource.d.ts +3 -2
- package/dist/internal/useBuildSideEntityController.d.ts +3 -3
- 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 +36 -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 +13 -3
- package/dist/types/firecms.d.ts +1 -1
- package/dist/types/navigation.d.ts +33 -12
- package/dist/types/plugins.d.ts +2 -0
- package/dist/types/properties.d.ts +40 -4
- package/dist/types/side_entity_controller.d.ts +6 -1
- package/dist/util/builders.d.ts +1 -1
- package/dist/util/createFormexStub.d.ts +2 -0
- 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 +13 -1
- package/dist/util/objects.d.ts +1 -1
- package/dist/util/property_utils.d.ts +2 -2
- package/dist/util/references.d.ts +2 -2
- package/dist/util/resolutions.d.ts +12 -2
- package/package.json +20 -21
- package/src/app/Scaffold.tsx +13 -3
- package/src/components/ArrayContainer.tsx +60 -24
- package/src/components/CircularProgressCenter.tsx +1 -1
- package/src/components/DeleteEntityDialog.tsx +5 -3
- package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +46 -23
- 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 +10 -13
- package/src/components/EntityCollectionView/EntityCollectionView.tsx +105 -47
- package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +4 -2
- package/src/components/EntityJsonPreview.tsx +66 -0
- package/src/components/EntityPreview.tsx +10 -3
- package/src/components/EntityView.tsx +4 -1
- package/src/components/NotFoundPage.tsx +2 -2
- package/src/components/PropertyIdCopyTooltip.tsx +2 -3
- package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +14 -7
- 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 +48 -3
- 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 +37 -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 +59 -17
- package/src/core/DefaultDrawer.tsx +1 -1
- package/src/core/EntityEditView.tsx +354 -1000
- package/src/core/EntityEditViewFormActions.tsx +199 -0
- package/src/core/EntitySidePanel.tsx +85 -15
- package/src/core/FireCMS.tsx +20 -16
- 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 +782 -0
- package/src/form/EntityFormActions.tsx +169 -0
- package/src/form/PropertyFieldBinding.tsx +21 -16
- 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 +8 -5
- package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +5 -2
- package/src/form/field_bindings/BlockFieldBinding.tsx +5 -3
- package/src/form/field_bindings/DateTimeFieldBinding.tsx +1 -0
- package/src/form/field_bindings/KeyValueFieldBinding.tsx +1 -1
- package/src/form/field_bindings/MapFieldBinding.tsx +14 -16
- package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +15 -8
- package/src/form/field_bindings/MultiSelectFieldBinding.tsx +3 -3
- 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 +13 -4
- package/src/form/field_bindings/SelectFieldBinding.tsx +2 -2
- package/src/form/field_bindings/StorageUploadFieldBinding.tsx +11 -6
- 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 +25 -30
- 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 +81 -24
- package/src/hooks/useFireCMSContext.tsx +0 -30
- package/src/hooks/useLargeLayout.tsx +0 -35
- package/src/hooks/useModeController.tsx +1 -2
- package/src/hooks/useResolvedNavigationFrom.tsx +4 -6
- package/src/internal/useBuildDataSource.ts +9 -5
- package/src/internal/useBuildSideDialogsController.tsx +3 -2
- package/src/internal/useBuildSideEntityController.tsx +135 -88
- package/src/internal/useUnsavedChangesDialog.tsx +126 -92
- package/src/preview/PropertyPreview.tsx +12 -10
- package/src/preview/components/UrlComponentPreview.tsx +17 -18
- package/src/preview/property_previews/ArrayOfMapsPreview.tsx +4 -3
- package/src/preview/property_previews/ArrayOfReferencesPreview.tsx +4 -3
- package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +4 -2
- package/src/preview/property_previews/ArrayOfStringsPreview.tsx +4 -3
- package/src/preview/property_previews/ArrayOneOfPreview.tsx +4 -2
- package/src/preview/property_previews/ArrayPropertyPreview.tsx +4 -2
- package/src/preview/property_previews/MapPropertyPreview.tsx +3 -2
- package/src/routes/CustomCMSRoute.tsx +21 -0
- package/src/routes/FireCMSRoute.tsx +246 -0
- package/src/routes/HomePageRoute.tsx +17 -0
- package/src/types/collections.ts +45 -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 +18 -3
- package/src/types/firecms.tsx +1 -1
- package/src/types/navigation.ts +41 -16
- package/src/types/plugins.tsx +2 -0
- package/src/types/properties.ts +48 -4
- package/src/types/side_dialogs_controller.tsx +2 -0
- package/src/types/side_entity_controller.tsx +11 -1
- package/src/util/builders.ts +5 -3
- package/src/util/createFormexStub.tsx +62 -0
- 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 +6 -1
- package/src/util/navigation_from_path.ts +18 -7
- package/src/util/navigation_utils.ts +115 -14
- package/src/util/objects.ts +9 -2
- package/src/util/parent_references_from_path.ts +3 -3
- package/src/util/property_utils.tsx +7 -3
- package/src/util/references.ts +8 -6
- package/src/util/resolutions.ts +41 -12
- package/src/util/useStorageUploadController.tsx +60 -23
- package/src/components/common/useDataSourceEntityCollectionTableController.tsx +0 -236
- /package/src/util/{common.tsx → common.ts} +0 -0
|
@@ -1,104 +1,77 @@
|
|
|
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
8
|
resolveCollection,
|
|
39
9
|
resolveDefaultSelectedView,
|
|
40
|
-
|
|
41
|
-
useDebouncedCallback
|
|
10
|
+
resolvedSelectedEntityView
|
|
42
11
|
} from "../util";
|
|
43
12
|
|
|
44
13
|
import {
|
|
45
|
-
saveEntityWithCallbacks,
|
|
46
14
|
useAuthController,
|
|
47
15
|
useCustomizationController,
|
|
48
|
-
useDataSource,
|
|
49
16
|
useEntityFetch,
|
|
50
17
|
useFireCMSContext,
|
|
51
|
-
|
|
52
|
-
useSnackbarController
|
|
18
|
+
useLargeLayout
|
|
53
19
|
} from "../hooks";
|
|
54
|
-
import {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
20
|
+
import { CircularProgress, cls, CodeIcon, defaultBorderMixin, Tab, Tabs, Typography } from "@firecms/ui";
|
|
21
|
+
import { getEntityFromCache } from "../util/entity_cache";
|
|
22
|
+
import { EntityForm, EntityFormProps } from "../form";
|
|
23
|
+
import { EntityEditViewFormActions } from "./EntityEditViewFormActions";
|
|
24
|
+
import { EntityJsonPreview } from "../components/EntityJsonPreview";
|
|
25
|
+
import { createFormexStub } from "../util/createFormexStub";
|
|
26
|
+
|
|
27
|
+
export const MAIN_TAB_VALUE = "__main_##Q$SC^#S6";
|
|
28
|
+
export const JSON_TAB_VALUE = "__json";
|
|
29
|
+
|
|
30
|
+
export type OnUpdateParams = {
|
|
31
|
+
entity: Entity<any>,
|
|
32
|
+
status: EntityStatus,
|
|
33
|
+
path: string,
|
|
34
|
+
entityId?: string;
|
|
35
|
+
selectedTab?: string;
|
|
36
|
+
collection: EntityCollection<any>
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type OnTabChangeParams<M extends Record<string, any>> = {
|
|
40
|
+
path: string;
|
|
41
|
+
entityId?: string;
|
|
42
|
+
selectedTab?: string;
|
|
43
|
+
collection: EntityCollection<M>;
|
|
44
|
+
};
|
|
79
45
|
|
|
80
46
|
export interface EntityEditViewProps<M extends Record<string, any>> {
|
|
81
47
|
path: string;
|
|
48
|
+
/**
|
|
49
|
+
* The navigation path to the entity.
|
|
50
|
+
*/
|
|
51
|
+
fullIdPath?: string;
|
|
82
52
|
collection: EntityCollection<M>;
|
|
83
53
|
entityId?: string;
|
|
54
|
+
databaseId?: string;
|
|
84
55
|
copy?: boolean;
|
|
85
|
-
|
|
56
|
+
selectedTab?: string;
|
|
86
57
|
parentCollectionIds: string[];
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
58
|
+
onValuesModified?: (modified: boolean) => void;
|
|
59
|
+
onSaved?: (params: OnUpdateParams) => void;
|
|
60
|
+
onTabChange?: (props: OnTabChangeParams<M>) => void;
|
|
61
|
+
layout?: "side_panel" | "full_screen";
|
|
62
|
+
barActions?: React.ReactNode;
|
|
63
|
+
formProps?: Partial<EntityFormProps<M>>,
|
|
90
64
|
}
|
|
91
65
|
|
|
92
66
|
/**
|
|
93
67
|
* This is the default view that is used as the content of a side panel when
|
|
94
68
|
* 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
69
|
*/
|
|
98
70
|
export function EntityEditView<M extends Record<string, any>, USER extends User>({
|
|
99
71
|
entityId,
|
|
100
72
|
...props
|
|
101
73
|
}: EntityEditViewProps<M>) {
|
|
74
|
+
|
|
102
75
|
const {
|
|
103
76
|
entity,
|
|
104
77
|
dataLoading,
|
|
@@ -108,1018 +81,399 @@ export function EntityEditView<M extends Record<string, any>, USER extends User>
|
|
|
108
81
|
path: props.path,
|
|
109
82
|
entityId: entityId,
|
|
110
83
|
collection: props.collection,
|
|
84
|
+
databaseId: props.databaseId,
|
|
111
85
|
useCache: false
|
|
112
86
|
});
|
|
113
87
|
|
|
114
|
-
|
|
115
|
-
|
|
88
|
+
const cachedValues = entityId
|
|
89
|
+
? getEntityFromCache(props.path + "/" + entityId)
|
|
90
|
+
: getEntityFromCache(props.path + "#new");
|
|
91
|
+
|
|
92
|
+
const authController = useAuthController();
|
|
93
|
+
|
|
94
|
+
const initialStatus = props.copy ? "copy" : (entityId ? "existing" : "new");
|
|
95
|
+
const [status, setStatus] = useState<EntityStatus>(initialStatus);
|
|
96
|
+
|
|
97
|
+
const canEdit = useMemo(() => {
|
|
98
|
+
if (status === "new" || status === "copy") {
|
|
99
|
+
return true;
|
|
100
|
+
} else {
|
|
101
|
+
return entity ? canEditEntity(props.collection, authController, props.path, entity ?? null) : undefined;
|
|
102
|
+
}
|
|
103
|
+
}, [authController, entity, status]);
|
|
104
|
+
|
|
105
|
+
if ((dataLoading && !cachedValues) || (!entity || canEdit === undefined) && (status === "existing" || status === "copy")) {
|
|
106
|
+
return <CircularProgressCenter/>;
|
|
116
107
|
}
|
|
117
108
|
|
|
118
|
-
if (entityId && !entity) {
|
|
119
|
-
console.error(`Entity with id ${entityId} not found in collection ${props.
|
|
109
|
+
if (entityId && !entity && !cachedValues) {
|
|
110
|
+
console.error(`Entity with id ${entityId} not found in collection ${props.path}`);
|
|
120
111
|
}
|
|
121
112
|
|
|
122
113
|
return <EntityEditViewInner<M> {...props}
|
|
123
114
|
entityId={entityId}
|
|
124
115
|
entity={entity}
|
|
125
|
-
|
|
116
|
+
cachedDirtyValues={cachedValues as Partial<M>}
|
|
117
|
+
dataLoading={dataLoading}
|
|
118
|
+
status={status}
|
|
119
|
+
setStatus={setStatus}
|
|
120
|
+
canEdit={canEdit}
|
|
121
|
+
/>;
|
|
126
122
|
}
|
|
127
123
|
|
|
128
124
|
export function EntityEditViewInner<M extends Record<string, any>>({
|
|
129
125
|
path,
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
126
|
+
fullIdPath,
|
|
127
|
+
entityId,
|
|
128
|
+
selectedTab: selectedTabProp,
|
|
133
129
|
collection,
|
|
134
130
|
parentCollectionIds,
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
131
|
+
onValuesModified,
|
|
132
|
+
onSaved,
|
|
133
|
+
onTabChange,
|
|
138
134
|
entity,
|
|
139
|
-
|
|
135
|
+
cachedDirtyValues,
|
|
136
|
+
dataLoading,
|
|
137
|
+
layout = "side_panel",
|
|
138
|
+
barActions,
|
|
139
|
+
status,
|
|
140
|
+
setStatus,
|
|
141
|
+
formProps,
|
|
142
|
+
canEdit
|
|
140
143
|
}: EntityEditViewProps<M> & {
|
|
141
144
|
entity?: Entity<M>,
|
|
142
|
-
|
|
145
|
+
cachedDirtyValues?: Partial<M>, // dirty cached entity in memory
|
|
146
|
+
dataLoading: boolean,
|
|
147
|
+
status: EntityStatus,
|
|
148
|
+
setStatus: (status: EntityStatus) => void,
|
|
149
|
+
canEdit?: boolean,
|
|
143
150
|
}) {
|
|
144
151
|
|
|
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
152
|
const context = useFireCMSContext();
|
|
179
153
|
|
|
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]);
|
|
154
|
+
const [usedEntity, setUsedEntity] = useState<Entity<M> | undefined>(entity);
|
|
208
155
|
|
|
209
|
-
|
|
156
|
+
useEffect(() => {
|
|
157
|
+
if (entity)
|
|
158
|
+
setUsedEntity(entity);
|
|
159
|
+
}, [entity]);
|
|
210
160
|
|
|
211
|
-
//
|
|
212
|
-
|
|
213
|
-
// setInternalValues(values);
|
|
214
|
-
// if (onValuesChanged)
|
|
215
|
-
// onValuesChanged(values);
|
|
216
|
-
// if (autoSave && values && !equal(values, initialValues)) {
|
|
217
|
-
// save(values);
|
|
218
|
-
// }
|
|
219
|
-
// };
|
|
161
|
+
// Instead of using a ref (which does not trigger re-render), we use state for the form context.
|
|
162
|
+
const [formContext, setFormContext] = useState<FormContext<M> | undefined>(undefined);
|
|
220
163
|
|
|
221
|
-
const
|
|
222
|
-
const [savingError, setSavingError] = React.useState<Error | undefined>();
|
|
164
|
+
const largeLayout = useLargeLayout();
|
|
223
165
|
|
|
224
|
-
const
|
|
166
|
+
const customizationController = useCustomizationController();
|
|
225
167
|
|
|
226
|
-
const defaultSelectedView =
|
|
168
|
+
const defaultSelectedView = useMemo(() => resolveDefaultSelectedView(
|
|
227
169
|
collection ? collection.defaultSelectedView : undefined,
|
|
228
170
|
{
|
|
229
171
|
status,
|
|
230
172
|
entityId
|
|
231
173
|
}
|
|
232
|
-
);
|
|
233
|
-
|
|
234
|
-
const [selectedTab, setSelectedTab] = useState<string>(defaultSelectedView ?? MAIN_TAB_VALUE);
|
|
174
|
+
), []);
|
|
235
175
|
|
|
236
|
-
const
|
|
176
|
+
const [selectedTab, setSelectedTab] = useState<string>(selectedTabProp ?? defaultSelectedView ?? MAIN_TAB_VALUE);
|
|
177
|
+
useEffect(() => {
|
|
178
|
+
if ((selectedTabProp ?? defaultSelectedView ?? MAIN_TAB_VALUE) !== selectedTab) {
|
|
179
|
+
setSelectedTab(selectedTabProp ?? defaultSelectedView ?? MAIN_TAB_VALUE);
|
|
180
|
+
}
|
|
181
|
+
}, [selectedTabProp]);
|
|
237
182
|
|
|
238
183
|
const subcollections = (collection.subcollections ?? []).filter(c => !c.hideFromNavigation);
|
|
239
184
|
const subcollectionsCount = subcollections?.length ?? 0;
|
|
240
185
|
const customViews = collection.entityViews;
|
|
241
186
|
const customViewsCount = customViews?.length ?? 0;
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
const hasAdditionalViews = customViewsCount > 0 || subcollectionsCount > 0;
|
|
245
|
-
|
|
246
|
-
const [usedEntity, setUsedEntity] = useState<Entity<M> | undefined>(entity);
|
|
247
|
-
const [readOnly, setReadOnly] = useState<boolean | undefined>(undefined);
|
|
248
|
-
|
|
249
|
-
const baseDataSourceValuesRef = useRef<Partial<EntityValues<M>>>(getDataSourceEntityValues(initialResolvedCollection, status, usedEntity));
|
|
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
|
-
}
|
|
187
|
+
const includeJsonView = collection.includeJsonView === undefined ? true : collection.includeJsonView;
|
|
188
|
+
const hasAdditionalViews = customViewsCount > 0 || subcollectionsCount > 0 || includeJsonView;
|
|
314
189
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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
|
-
};
|
|
190
|
+
const {
|
|
191
|
+
resolvedEntityViews,
|
|
192
|
+
selectedEntityView,
|
|
193
|
+
selectedSecondaryForm
|
|
194
|
+
} = resolvedSelectedEntityView(customViews, customizationController, selectedTab, canEdit);
|
|
360
195
|
|
|
361
|
-
const
|
|
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
|
-
};
|
|
196
|
+
const actionsAtTheBottom = !largeLayout || layout === "side_panel" || selectedEntityView?.includeActions === "bottom";
|
|
386
197
|
|
|
387
|
-
const
|
|
198
|
+
const mainViewVisible = selectedTab === MAIN_TAB_VALUE || Boolean(selectedSecondaryForm);
|
|
388
199
|
|
|
389
|
-
|
|
390
|
-
console.error("Missing custom Id");
|
|
391
|
-
setEntityIdError(true);
|
|
392
|
-
formexController.setSubmitting(false);
|
|
393
|
-
return;
|
|
394
|
-
}
|
|
200
|
+
const authController = useAuthController();
|
|
395
201
|
|
|
396
|
-
|
|
397
|
-
|
|
202
|
+
const customViewsView: React.ReactNode[] | undefined = customViews && resolvedEntityViews
|
|
203
|
+
.filter(e => !e.includeActions)
|
|
204
|
+
.map((customView) => {
|
|
398
205
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
if (
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
}
|
|
206
|
+
if (!customView)
|
|
207
|
+
return null;
|
|
208
|
+
const Builder = customView.Builder;
|
|
209
|
+
if (!Builder) {
|
|
210
|
+
console.error("INTERNAL: customView.Builder is not defined");
|
|
211
|
+
return null;
|
|
406
212
|
}
|
|
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
|
-
};
|
|
487
213
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
: [];
|
|
492
|
-
|
|
493
|
-
const selectedEntityView = resolvedEntityViews.find(e => e.key === selectedTab);
|
|
494
|
-
const shouldShowEntityActions = !autoSave && (selectedTab === MAIN_TAB_VALUE || selectedEntityView?.includeActions);
|
|
495
|
-
|
|
496
|
-
const customViewsView: React.ReactNode[] | undefined = customViews && resolvedEntityViews
|
|
497
|
-
.map(
|
|
498
|
-
(customView, colIndex) => {
|
|
499
|
-
if (!customView)
|
|
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>;
|
|
214
|
+
if (!entityId) {
|
|
215
|
+
console.error("INTERNAL: entityId is not defined");
|
|
216
|
+
return null;
|
|
522
217
|
}
|
|
523
|
-
).filter(Boolean);
|
|
524
|
-
|
|
525
|
-
const globalLoading = (dataLoading && !usedEntity) ||
|
|
526
|
-
((!usedEntity || readOnly === undefined) && (status === "existing" || status === "copy"));
|
|
527
|
-
|
|
528
|
-
const loading = globalLoading || saving;
|
|
529
218
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
219
|
+
const formexStub = createFormexStub<M>(usedEntity?.values ?? {} as M);
|
|
220
|
+
const usedFormContext: FormContext = formContext ?? {
|
|
221
|
+
entityId,
|
|
222
|
+
disabled: false,
|
|
223
|
+
openEntityMode: layout,
|
|
224
|
+
status: status,
|
|
225
|
+
values: usedEntity?.values ?? {},
|
|
226
|
+
setFieldValue: (key: string, value: any) => {
|
|
227
|
+
throw new Error("You can't update values in read only mode");
|
|
228
|
+
},
|
|
229
|
+
save: () => {
|
|
230
|
+
throw new Error("You can't save in read only mode");
|
|
231
|
+
},
|
|
232
|
+
collection: resolveCollection<M>({
|
|
233
|
+
collection,
|
|
234
|
+
path,
|
|
235
|
+
entityId,
|
|
236
|
+
values: usedEntity?.values ?? {},
|
|
237
|
+
previousValues: usedEntity?.values ?? {},
|
|
238
|
+
propertyConfigs: customizationController.propertyConfigs,
|
|
239
|
+
authController
|
|
240
|
+
}),
|
|
241
|
+
path,
|
|
242
|
+
entity: usedEntity,
|
|
243
|
+
savingError: undefined,
|
|
244
|
+
formex: formexStub
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
return <div
|
|
248
|
+
className={cls(defaultBorderMixin,
|
|
249
|
+
"relative flex-1 w-full h-full overflow-auto",
|
|
250
|
+
{ "hidden": selectedTab !== customView.key }
|
|
251
|
+
)}
|
|
252
|
+
key={`custom_view_${customView.key}`}
|
|
253
|
+
role="tabpanel">
|
|
254
|
+
<ErrorBoundary>
|
|
255
|
+
{usedFormContext && <Builder
|
|
256
|
+
collection={collection}
|
|
257
|
+
entity={usedEntity}
|
|
258
|
+
modifiedValues={usedFormContext?.formex?.values ?? usedEntity?.values}
|
|
259
|
+
formContext={usedFormContext}
|
|
260
|
+
/>}
|
|
261
|
+
</ErrorBoundary>
|
|
262
|
+
</div>;
|
|
263
|
+
}).filter(Boolean);
|
|
264
|
+
|
|
265
|
+
const globalLoading = dataLoading && !usedEntity;
|
|
266
|
+
|
|
267
|
+
const jsonView = <div
|
|
268
|
+
className={cls("relative flex-1 h-full overflow-auto w-full",
|
|
269
|
+
{ "hidden": selectedTab !== JSON_TAB_VALUE })}
|
|
270
|
+
key={"json_view"}
|
|
271
|
+
role="tabpanel">
|
|
272
|
+
<ErrorBoundary>
|
|
273
|
+
<EntityJsonPreview
|
|
274
|
+
values={formContext?.values ?? entity?.values ?? {}}/>
|
|
275
|
+
</ErrorBoundary>
|
|
276
|
+
</div>;
|
|
277
|
+
|
|
278
|
+
const subCollectionsViews = subcollections && subcollections.map((subcollection) => {
|
|
279
|
+
const subcollectionId = subcollection.id ?? subcollection.path;
|
|
280
|
+
const newFullPath = usedEntity ? `${path}/${usedEntity?.id}/${removeInitialAndTrailingSlashes(subcollectionId)}` : undefined;
|
|
281
|
+
const newFullIdPath = fullIdPath ? `${fullIdPath}/${usedEntity?.id}/${removeInitialAndTrailingSlashes(subcollectionId)}` : undefined;
|
|
282
|
+
|
|
283
|
+
if (selectedTab !== subcollectionId) return null;
|
|
284
|
+
return (
|
|
285
|
+
<div
|
|
286
|
+
className={"relative flex-1 h-full overflow-auto w-full"}
|
|
287
|
+
key={`subcol_${subcollectionId}`}
|
|
288
|
+
role="tabpanel">
|
|
289
|
+
|
|
290
|
+
{globalLoading && <CircularProgressCenter/>}
|
|
291
|
+
|
|
292
|
+
{!globalLoading &&
|
|
293
|
+
(usedEntity && newFullPath
|
|
294
|
+
? <EntityCollectionView
|
|
295
|
+
fullPath={newFullPath}
|
|
296
|
+
fullIdPath={newFullIdPath}
|
|
297
|
+
parentCollectionIds={[...parentCollectionIds, collection.id]}
|
|
298
|
+
isSubCollection={true}
|
|
299
|
+
updateUrl={false}
|
|
300
|
+
{...subcollection}
|
|
301
|
+
openEntityMode={layout}/>
|
|
302
|
+
: <div className="flex items-center justify-center w-full h-full p-3">
|
|
303
|
+
<Typography variant={"label"}>
|
|
304
|
+
You need to save your entity before
|
|
305
|
+
adding additional collections
|
|
306
|
+
</Typography>
|
|
307
|
+
</div>)
|
|
308
|
+
}
|
|
564
309
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
}
|
|
310
|
+
</div>
|
|
311
|
+
);
|
|
312
|
+
}).filter(Boolean);
|
|
568
313
|
|
|
569
314
|
const onSideTabClick = (value: string) => {
|
|
570
315
|
setSelectedTab(value);
|
|
571
316
|
if (status === "existing") {
|
|
572
|
-
|
|
573
|
-
path,
|
|
317
|
+
onTabChange?.({
|
|
318
|
+
path: fullIdPath ?? path,
|
|
574
319
|
entityId,
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
collection,
|
|
320
|
+
selectedTab: value === MAIN_TAB_VALUE ? undefined : value,
|
|
321
|
+
collection
|
|
578
322
|
});
|
|
579
323
|
}
|
|
580
324
|
};
|
|
581
325
|
|
|
582
|
-
const
|
|
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
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
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>
|
|
326
|
+
const entityReadOnlyView = !canEdit && entity ? <div
|
|
327
|
+
className={cls("flex-1 flex flex-row w-full overflow-y-auto justify-center", (canEdit || !mainViewVisible || selectedSecondaryForm) ? "hidden" : "")}>
|
|
328
|
+
<div
|
|
329
|
+
className={cls("relative flex flex-col max-w-4xl lg:max-w-3xl xl:max-w-4xl 2xl:max-w-6xl w-full h-fit")}>
|
|
330
|
+
<Typography className={"mt-16 mb-8 mx-8"} variant={"h4"}>
|
|
331
|
+
{collection.singularName ?? collection.name}
|
|
332
|
+
</Typography>
|
|
333
|
+
<EntityView
|
|
334
|
+
className={"px-8 h-full overflow-auto"}
|
|
335
|
+
entity={entity}
|
|
336
|
+
path={path}
|
|
337
|
+
collection={collection}/>
|
|
338
|
+
</div>
|
|
339
|
+
</div> : null;
|
|
340
|
+
|
|
341
|
+
const entityView = <EntityForm<M>
|
|
342
|
+
collection={collection}
|
|
343
|
+
path={path}
|
|
344
|
+
entityId={entityId ?? usedEntity?.id}
|
|
345
|
+
onValuesModified={onValuesModified}
|
|
346
|
+
entity={entity}
|
|
347
|
+
initialDirtyValues={cachedDirtyValues}
|
|
348
|
+
openEntityMode={layout}
|
|
349
|
+
forceActionsAtTheBottom={actionsAtTheBottom}
|
|
350
|
+
initialStatus={status}
|
|
351
|
+
className={cls((!mainViewVisible || !canEdit) && !selectedSecondaryForm ? "hidden" : "", formProps?.className)}
|
|
352
|
+
EntityFormActionsComponent={EntityEditViewFormActions}
|
|
353
|
+
disabled={!canEdit}
|
|
354
|
+
{...formProps}
|
|
355
|
+
onEntityChange={(entity) => {
|
|
356
|
+
setUsedEntity(entity);
|
|
357
|
+
formProps?.onEntityChange?.(entity);
|
|
358
|
+
}}
|
|
359
|
+
onStatusChange={(status) => {
|
|
360
|
+
setStatus(status);
|
|
361
|
+
formProps?.onStatusChange?.(status);
|
|
362
|
+
}}
|
|
363
|
+
onFormContextReady={(formContext) => {
|
|
364
|
+
setFormContext(formContext);
|
|
365
|
+
formProps?.onFormContextReady?.(formContext);
|
|
366
|
+
}}
|
|
367
|
+
onSaved={(params) => {
|
|
368
|
+
const res = {
|
|
369
|
+
...params,
|
|
370
|
+
selectedTab: MAIN_TAB_VALUE === selectedTab ? undefined : selectedTab
|
|
371
|
+
};
|
|
372
|
+
onSaved?.(res);
|
|
373
|
+
formProps?.onSaved?.(res);
|
|
374
|
+
}}
|
|
375
|
+
Builder={selectedSecondaryForm?.Builder}
|
|
376
|
+
/>;
|
|
377
|
+
|
|
378
|
+
const subcollectionTabs = subcollections && subcollections.map((subcollection) =>
|
|
379
|
+
<Tab
|
|
380
|
+
className="text-sm min-w-[120px]"
|
|
381
|
+
value={subcollection.id}
|
|
382
|
+
key={`entity_detail_collection_tab_${subcollection.name}`}>
|
|
383
|
+
{subcollection.name}
|
|
384
|
+
</Tab>
|
|
984
385
|
);
|
|
985
386
|
|
|
986
|
-
const customViewTabs = resolvedEntityViews.map(
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
{view.name}
|
|
994
|
-
</Tab>
|
|
387
|
+
const customViewTabs = resolvedEntityViews.map((view) =>
|
|
388
|
+
<Tab
|
|
389
|
+
className="text-sm min-w-[120px]"
|
|
390
|
+
value={view.key}
|
|
391
|
+
key={`entity_detail_collection_tab_${view.name}`}>
|
|
392
|
+
{view.name}
|
|
393
|
+
</Tab>
|
|
995
394
|
);
|
|
996
395
|
|
|
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")}>
|
|
396
|
+
const shouldShowTopBar = Boolean(barActions) || hasAdditionalViews;
|
|
1009
397
|
|
|
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>
|
|
398
|
+
let result = <div className="relative flex flex-col h-full w-full bg-white dark:bg-surface-900">
|
|
1020
399
|
|
|
1021
|
-
|
|
400
|
+
{shouldShowTopBar && <div
|
|
401
|
+
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
402
|
|
|
1023
|
-
|
|
1024
|
-
className="self-center">
|
|
1025
|
-
<CircularProgress size={"small"}/>
|
|
1026
|
-
</div>}
|
|
403
|
+
{barActions}
|
|
1027
404
|
|
|
1028
|
-
|
|
1029
|
-
value={selectedTab}
|
|
1030
|
-
onValueChange={(value) => {
|
|
1031
|
-
onSideTabClick(value);
|
|
1032
|
-
}}
|
|
1033
|
-
innerClassName="pl-4 pr-4 pt-0">
|
|
405
|
+
<div className={"flex-grow"}/>
|
|
1034
406
|
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
className={`${
|
|
1039
|
-
!hasAdditionalViews ? "hidden" : ""
|
|
1040
|
-
} text-sm min-w-[140px]`}
|
|
1041
|
-
>{collection.singularName ?? collection.name}</Tab>
|
|
407
|
+
{globalLoading && <div className="self-center">
|
|
408
|
+
<CircularProgress size={"small"}/>
|
|
409
|
+
</div>}
|
|
1042
410
|
|
|
1043
|
-
|
|
411
|
+
{hasAdditionalViews && <Tabs
|
|
412
|
+
value={selectedTab}
|
|
413
|
+
onValueChange={(value) => {
|
|
414
|
+
onSideTabClick(value);
|
|
415
|
+
}}>
|
|
1044
416
|
|
|
1045
|
-
|
|
1046
|
-
|
|
417
|
+
{includeJsonView && <Tab
|
|
418
|
+
disabled={!hasAdditionalViews}
|
|
419
|
+
value={JSON_TAB_VALUE}
|
|
420
|
+
innerClassName={"block"}
|
|
421
|
+
className={"text-sm"}>
|
|
422
|
+
<CodeIcon size={"small"}/>
|
|
423
|
+
</Tab>}
|
|
1047
424
|
|
|
1048
|
-
|
|
425
|
+
<Tab
|
|
426
|
+
disabled={!hasAdditionalViews}
|
|
427
|
+
value={MAIN_TAB_VALUE}
|
|
428
|
+
className={"text-sm min-w-[120px]"}>
|
|
429
|
+
{collection.singularName ?? collection.name}
|
|
430
|
+
</Tab>
|
|
1049
431
|
|
|
1050
|
-
<form
|
|
1051
|
-
onSubmit={formex.handleSubmit}
|
|
1052
|
-
onReset={() => {
|
|
1053
|
-
formex.resetForm();
|
|
1054
|
-
return onDiscard && onDiscard();
|
|
1055
|
-
}}
|
|
1056
|
-
noValidate
|
|
1057
|
-
className={"flex-grow h-full flex overflow-auto flex-col w-full"}>
|
|
1058
432
|
|
|
1059
|
-
|
|
1060
|
-
role="tabpanel"
|
|
1061
|
-
hidden={!mainViewVisible}
|
|
1062
|
-
id={`form_${path}`}
|
|
1063
|
-
className={"relative w-full"}>
|
|
433
|
+
{customViewTabs}
|
|
1064
434
|
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
435
|
+
{subcollectionTabs}
|
|
436
|
+
</Tabs>}
|
|
437
|
+
</div>}
|
|
1068
438
|
|
|
1069
|
-
|
|
439
|
+
{globalLoading
|
|
440
|
+
? <div className="w-full pt-12 pb-16 px-4 sm:px-8 md:px-10">
|
|
441
|
+
<CircularProgressCenter/>
|
|
442
|
+
</div>
|
|
443
|
+
: <>
|
|
444
|
+
{entityReadOnlyView}
|
|
445
|
+
{entityView}
|
|
446
|
+
</>}
|
|
1070
447
|
|
|
1071
|
-
|
|
448
|
+
{jsonView}
|
|
1072
449
|
|
|
1073
|
-
|
|
450
|
+
{customViewsView}
|
|
1074
451
|
|
|
1075
|
-
|
|
452
|
+
{subCollectionsViews}
|
|
1076
453
|
|
|
1077
|
-
|
|
454
|
+
</div>;
|
|
1078
455
|
|
|
1079
|
-
|
|
1080
|
-
</Formex>
|
|
1081
|
-
);
|
|
1082
|
-
}
|
|
456
|
+
const plugins = customizationController.plugins;
|
|
1083
457
|
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
458
|
+
if (plugins) {
|
|
459
|
+
plugins.forEach((plugin: FireCMSPlugin) => {
|
|
460
|
+
if (plugin.form?.provider) {
|
|
461
|
+
result = (
|
|
462
|
+
<plugin.form.provider.Component
|
|
463
|
+
status={status}
|
|
464
|
+
path={path}
|
|
465
|
+
collection={collection}
|
|
466
|
+
entity={usedEntity}
|
|
467
|
+
context={context}
|
|
468
|
+
formContext={formContext}
|
|
469
|
+
{...plugin.form.provider.props}>
|
|
470
|
+
{result}
|
|
471
|
+
</plugin.form.provider.Component>
|
|
472
|
+
);
|
|
473
|
+
}
|
|
1097
474
|
});
|
|
1098
|
-
throw new Error("Form has not been initialised with the correct parameters");
|
|
1099
475
|
}
|
|
1100
|
-
}
|
|
1101
476
|
|
|
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;
|
|
477
|
+
return result;
|
|
1125
478
|
}
|
|
479
|
+
|