@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
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import React, { useMemo } from "react";
|
|
2
|
+
import { Entity, EntityAction, FireCMSContext, ResolvedEntityCollection, SideEntityController } from "../types";
|
|
3
|
+
|
|
4
|
+
import { copyEntityAction, deleteEntityAction } from "../components";
|
|
5
|
+
import { canCreateEntity, canDeleteEntity, mergeEntityActions } from "../util";
|
|
6
|
+
import { Button, cls, defaultBorderMixin, DialogActions, IconButton, LoadingButton, Typography } from "@firecms/ui";
|
|
7
|
+
import { useAuthController, useFireCMSContext, useSideEntityController } from "../hooks";
|
|
8
|
+
import { EntityFormActionsProps } from "../form/EntityFormActions";
|
|
9
|
+
import { SideDialogController, useSideDialogContext } from "./SideDialogs";
|
|
10
|
+
|
|
11
|
+
export function EntityEditViewFormActions({
|
|
12
|
+
collection,
|
|
13
|
+
path,
|
|
14
|
+
entity,
|
|
15
|
+
layout,
|
|
16
|
+
savingError,
|
|
17
|
+
formex,
|
|
18
|
+
disabled,
|
|
19
|
+
status,
|
|
20
|
+
pluginActions,
|
|
21
|
+
openEntityMode,
|
|
22
|
+
showDefaultActions = true
|
|
23
|
+
}: EntityFormActionsProps) {
|
|
24
|
+
|
|
25
|
+
const authController = useAuthController();
|
|
26
|
+
const context = useFireCMSContext();
|
|
27
|
+
const sideEntityController = useSideEntityController();
|
|
28
|
+
const sideDialogContext = useSideDialogContext();
|
|
29
|
+
|
|
30
|
+
const entityActions = useMemo((): EntityAction[] => {
|
|
31
|
+
const customEntityActions = collection.entityActions
|
|
32
|
+
const createEnabled = canCreateEntity(collection, authController, path, null);
|
|
33
|
+
const deleteEnabled = entity ? canDeleteEntity(collection, authController, path, entity) : false;
|
|
34
|
+
const actions: EntityAction[] = [];
|
|
35
|
+
if (createEnabled)
|
|
36
|
+
actions.push(copyEntityAction);
|
|
37
|
+
if (deleteEnabled)
|
|
38
|
+
actions.push(deleteEntityAction);
|
|
39
|
+
if (customEntityActions)
|
|
40
|
+
return mergeEntityActions(actions, customEntityActions);
|
|
41
|
+
return actions;
|
|
42
|
+
}, [authController, collection, path]);
|
|
43
|
+
|
|
44
|
+
const formActions = showDefaultActions ? entityActions.filter(a => a.includeInForm === undefined || a.includeInForm) : [];
|
|
45
|
+
|
|
46
|
+
return layout === "bottom"
|
|
47
|
+
? buildBottomActions({
|
|
48
|
+
savingError,
|
|
49
|
+
entity,
|
|
50
|
+
formActions,
|
|
51
|
+
collection,
|
|
52
|
+
context,
|
|
53
|
+
sideEntityController,
|
|
54
|
+
isSubmitting: formex.isSubmitting,
|
|
55
|
+
disabled,
|
|
56
|
+
status,
|
|
57
|
+
sideDialogContext,
|
|
58
|
+
pluginActions,
|
|
59
|
+
openEntityMode,
|
|
60
|
+
})
|
|
61
|
+
: buildSideActions({
|
|
62
|
+
savingError,
|
|
63
|
+
entity,
|
|
64
|
+
formActions,
|
|
65
|
+
collection,
|
|
66
|
+
context,
|
|
67
|
+
sideEntityController,
|
|
68
|
+
isSubmitting: formex.isSubmitting,
|
|
69
|
+
sideDialogContext,
|
|
70
|
+
disabled,
|
|
71
|
+
status,
|
|
72
|
+
pluginActions,
|
|
73
|
+
openEntityMode,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
type ActionsViewProps<M extends object> = {
|
|
78
|
+
savingError: Error | undefined,
|
|
79
|
+
entity: Entity<M> | undefined,
|
|
80
|
+
formActions: EntityAction[],
|
|
81
|
+
collection: ResolvedEntityCollection,
|
|
82
|
+
context: FireCMSContext,
|
|
83
|
+
sideEntityController: SideEntityController,
|
|
84
|
+
isSubmitting: boolean,
|
|
85
|
+
disabled: boolean,
|
|
86
|
+
status: "new" | "existing" | "copy",
|
|
87
|
+
sideDialogContext: SideDialogController,
|
|
88
|
+
pluginActions?: React.ReactNode[],
|
|
89
|
+
openEntityMode: "side_panel" | "full_screen";
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
function buildBottomActions<M extends object>({
|
|
93
|
+
savingError,
|
|
94
|
+
entity,
|
|
95
|
+
formActions,
|
|
96
|
+
collection,
|
|
97
|
+
context,
|
|
98
|
+
sideEntityController,
|
|
99
|
+
isSubmitting,
|
|
100
|
+
disabled,
|
|
101
|
+
status,
|
|
102
|
+
sideDialogContext,
|
|
103
|
+
pluginActions,
|
|
104
|
+
openEntityMode,
|
|
105
|
+
}: ActionsViewProps<M>) {
|
|
106
|
+
|
|
107
|
+
const canClose = openEntityMode === "side_panel";
|
|
108
|
+
return <DialogActions position={"absolute"}>
|
|
109
|
+
{savingError &&
|
|
110
|
+
<div className="text-right">
|
|
111
|
+
<Typography color={"error"}>{savingError.message}</Typography>
|
|
112
|
+
</div>
|
|
113
|
+
}
|
|
114
|
+
{entity && formActions.length > 0 && <div className="flex-grow flex overflow-auto no-scrollbar">
|
|
115
|
+
{formActions.map(action => (
|
|
116
|
+
<IconButton
|
|
117
|
+
key={action.name}
|
|
118
|
+
color="primary"
|
|
119
|
+
onClick={(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
|
120
|
+
event.stopPropagation();
|
|
121
|
+
if (entity)
|
|
122
|
+
action.onClick({
|
|
123
|
+
entity,
|
|
124
|
+
fullPath: collection.path,
|
|
125
|
+
collection: collection,
|
|
126
|
+
context,
|
|
127
|
+
sideEntityController,
|
|
128
|
+
openEntityMode: openEntityMode
|
|
129
|
+
});
|
|
130
|
+
}}>
|
|
131
|
+
{action.icon}
|
|
132
|
+
</IconButton>
|
|
133
|
+
))}
|
|
134
|
+
</div>}
|
|
135
|
+
{pluginActions}
|
|
136
|
+
<Button variant="text" disabled={disabled || isSubmitting} type="reset">
|
|
137
|
+
{status === "existing" ? "Discard" : "Clear"}
|
|
138
|
+
</Button>
|
|
139
|
+
<Button variant={canClose ? "text" : "filled"} color="primary" type="submit"
|
|
140
|
+
disabled={disabled || isSubmitting}
|
|
141
|
+
onClick={() => {
|
|
142
|
+
sideDialogContext.setPendingClose(false);
|
|
143
|
+
}}>
|
|
144
|
+
{status === "existing" && "Save"}
|
|
145
|
+
{status === "copy" && "Create copy"}
|
|
146
|
+
{status === "new" && "Create"}
|
|
147
|
+
</Button>
|
|
148
|
+
{canClose && <LoadingButton variant="filled"
|
|
149
|
+
color="primary"
|
|
150
|
+
type="submit"
|
|
151
|
+
loading={isSubmitting}
|
|
152
|
+
disabled={disabled}
|
|
153
|
+
onClick={() => {
|
|
154
|
+
sideDialogContext.setPendingClose?.(true);
|
|
155
|
+
}}>
|
|
156
|
+
{status === "existing" && "Save and close"}
|
|
157
|
+
{status === "copy" && "Create copy and close"}
|
|
158
|
+
{status === "new" && "Create and close"}
|
|
159
|
+
</LoadingButton>}
|
|
160
|
+
</DialogActions>;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function buildSideActions<M extends object>({
|
|
164
|
+
savingError,
|
|
165
|
+
entity,
|
|
166
|
+
formActions,
|
|
167
|
+
collection,
|
|
168
|
+
context,
|
|
169
|
+
sideEntityController,
|
|
170
|
+
isSubmitting,
|
|
171
|
+
disabled,
|
|
172
|
+
status,
|
|
173
|
+
sideDialogContext,
|
|
174
|
+
pluginActions
|
|
175
|
+
}: ActionsViewProps<M>) {
|
|
176
|
+
|
|
177
|
+
return <div
|
|
178
|
+
className={cls("overflow-auto h-full flex flex-col gap-2 w-80 2xl:w-96 px-4 py-16 sticky top-0 border-l", defaultBorderMixin)}>
|
|
179
|
+
<LoadingButton fullWidth={true} variant="filled" color="primary" type="submit" size={"large"}
|
|
180
|
+
disabled={disabled || isSubmitting} onClick={() => {
|
|
181
|
+
sideDialogContext.setPendingClose?.(false);
|
|
182
|
+
}}>
|
|
183
|
+
{status === "existing" && "Save"}
|
|
184
|
+
{status === "copy" && "Create copy"}
|
|
185
|
+
{status === "new" && "Create"}
|
|
186
|
+
</LoadingButton>
|
|
187
|
+
<Button fullWidth={true} variant="text" disabled={disabled || isSubmitting} type="reset">
|
|
188
|
+
{status === "existing" ? "Discard" : "Clear"}
|
|
189
|
+
</Button>
|
|
190
|
+
|
|
191
|
+
{pluginActions}
|
|
192
|
+
|
|
193
|
+
{savingError &&
|
|
194
|
+
<div className="text-right">
|
|
195
|
+
<Typography color={"error"}>{savingError.message}</Typography>
|
|
196
|
+
</div>
|
|
197
|
+
}
|
|
198
|
+
</div>;
|
|
199
|
+
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useMemo } from "react";
|
|
2
2
|
|
|
3
3
|
import { EntitySidePanelProps } from "../types";
|
|
4
|
-
import { useNavigationController } from "../hooks";
|
|
4
|
+
import { useNavigationController, useSideEntityController } from "../hooks";
|
|
5
5
|
|
|
6
6
|
import { ErrorBoundary } from "../components";
|
|
7
|
-
import { EntityEditView } from "./EntityEditView";
|
|
7
|
+
import { EntityEditView, OnUpdateParams } from "./EntityEditView";
|
|
8
8
|
import { useSideDialogContext } from "./SideDialogs";
|
|
9
|
+
import { CloseIcon, IconButton, OpenInFullIcon } from "@firecms/ui";
|
|
10
|
+
import { useLocation, useNavigate } from "react-router-dom";
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
13
|
* This is the component in charge of rendering the side dialog used
|
|
@@ -20,30 +22,64 @@ export function EntitySidePanel(props: EntitySidePanelProps) {
|
|
|
20
22
|
const {
|
|
21
23
|
blocked,
|
|
22
24
|
setBlocked,
|
|
23
|
-
setBlockedNavigationMessage
|
|
25
|
+
setBlockedNavigationMessage,
|
|
26
|
+
close
|
|
24
27
|
} = useSideDialogContext();
|
|
25
28
|
|
|
29
|
+
const navigate = useNavigate();
|
|
30
|
+
const location = useLocation();
|
|
31
|
+
|
|
32
|
+
const sideEntityController = useSideEntityController();
|
|
26
33
|
const navigationController = useNavigationController();
|
|
34
|
+
const sideDialogsController = useSideDialogContext();
|
|
35
|
+
|
|
36
|
+
const onClose = () => {
|
|
37
|
+
if (props.onClose) {
|
|
38
|
+
props.onClose();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
setBlocked(false);
|
|
42
|
+
close(true);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const onUpdate = (params: OnUpdateParams) => {
|
|
46
|
+
if (props.onUpdate) {
|
|
47
|
+
props.onUpdate(params);
|
|
48
|
+
}
|
|
49
|
+
if (params.status !== "existing") {
|
|
50
|
+
sideEntityController.replace({
|
|
51
|
+
path: params.path,
|
|
52
|
+
entityId: params.entityId,
|
|
53
|
+
selectedTab: params.selectedTab,
|
|
54
|
+
updateUrl: true,
|
|
55
|
+
collection: params.collection,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (sideDialogsController.pendingClose) {
|
|
60
|
+
sideDialogsController.setPendingClose(false);
|
|
61
|
+
onClose();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
}
|
|
27
65
|
|
|
28
66
|
const parentCollectionIds = useMemo(() => {
|
|
29
67
|
return navigationController.getParentCollectionIds(props.path);
|
|
30
68
|
}, [navigationController, props.path]);
|
|
31
69
|
|
|
32
70
|
const collection = useMemo(() => {
|
|
33
|
-
if (
|
|
34
|
-
|
|
71
|
+
if (props.collection) {
|
|
72
|
+
return props.collection;
|
|
73
|
+
}
|
|
35
74
|
|
|
36
75
|
const registryCollection = navigationController.getCollection(props.path);
|
|
37
76
|
if (registryCollection) {
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
if (!usedCollection) {
|
|
41
|
-
console.error("ERROR: No collection found in path `", props.path, "`. Entity id: ", props.entityId);
|
|
42
|
-
throw Error("ERROR: No collection found in path `" + props.path + "`. Make sure you have defined a collection for this path in the root navigation.");
|
|
77
|
+
return registryCollection;
|
|
43
78
|
}
|
|
44
79
|
|
|
45
|
-
|
|
46
|
-
|
|
80
|
+
console.error("ERROR: No collection found in path `", props.path, "`. Entity id: ", props.entityId);
|
|
81
|
+
throw Error("ERROR: No collection found in path `" + props.path + "`. Make sure you have defined a collection for this path in the root navigation.");
|
|
82
|
+
}, [navigationController, props.collection]);
|
|
47
83
|
|
|
48
84
|
useEffect(() => {
|
|
49
85
|
function beforeunload(e: any) {
|
|
@@ -63,11 +99,11 @@ export function EntitySidePanel(props: EntitySidePanelProps) {
|
|
|
63
99
|
|
|
64
100
|
}, [blocked, collection]);
|
|
65
101
|
|
|
66
|
-
const
|
|
67
|
-
setBlocked(modified);
|
|
102
|
+
const onValuesModified = useCallback((modified: boolean) => {
|
|
68
103
|
setBlockedNavigationMessage(modified
|
|
69
104
|
? <> You have unsaved changes in this <b>{collection?.singularName ?? collection?.name}</b>.</>
|
|
70
105
|
: undefined)
|
|
106
|
+
setBlocked(modified);
|
|
71
107
|
}, [collection?.name, setBlocked, setBlockedNavigationMessage]);
|
|
72
108
|
|
|
73
109
|
if (!props || !collection) {
|
|
@@ -79,9 +115,43 @@ export function EntitySidePanel(props: EntitySidePanelProps) {
|
|
|
79
115
|
<ErrorBoundary>
|
|
80
116
|
<EntityEditView
|
|
81
117
|
{...props}
|
|
118
|
+
layout={"side_panel"}
|
|
82
119
|
collection={collection}
|
|
83
120
|
parentCollectionIds={parentCollectionIds}
|
|
84
|
-
|
|
121
|
+
onValuesModified={onValuesModified}
|
|
122
|
+
onSaved={onUpdate}
|
|
123
|
+
barActions={<>
|
|
124
|
+
<IconButton
|
|
125
|
+
className="self-center"
|
|
126
|
+
onClick={onClose}>
|
|
127
|
+
<CloseIcon size={"small"}/>
|
|
128
|
+
</IconButton>
|
|
129
|
+
<IconButton
|
|
130
|
+
className="self-center"
|
|
131
|
+
onClick={() => {
|
|
132
|
+
if (props.entityId)
|
|
133
|
+
navigate(location.pathname);
|
|
134
|
+
else
|
|
135
|
+
navigate(location.pathname + "#new");
|
|
136
|
+
}}>
|
|
137
|
+
<OpenInFullIcon size={"small"}/>
|
|
138
|
+
</IconButton>
|
|
139
|
+
</>}
|
|
140
|
+
onTabChange={({
|
|
141
|
+
path,
|
|
142
|
+
entityId,
|
|
143
|
+
selectedTab,
|
|
144
|
+
collection,
|
|
145
|
+
}) => {
|
|
146
|
+
sideEntityController.replace({
|
|
147
|
+
path,
|
|
148
|
+
entityId,
|
|
149
|
+
selectedTab,
|
|
150
|
+
updateUrl: true,
|
|
151
|
+
collection,
|
|
152
|
+
});
|
|
153
|
+
}}
|
|
154
|
+
formProps={props.formProps}
|
|
85
155
|
/>
|
|
86
156
|
</ErrorBoundary>
|
|
87
157
|
|
package/src/core/FireCMS.tsx
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import React, { useMemo } from "react";
|
|
4
4
|
import { CenteredView, Typography } from "@firecms/ui";
|
|
5
|
-
import { CustomizationController,
|
|
5
|
+
import { CustomizationController, FireCMSContext, FireCMSPlugin, FireCMSProps, User } from "../types";
|
|
6
6
|
import { AuthControllerContext } from "../contexts";
|
|
7
7
|
import { useBuildSideEntityController } from "../internal/useBuildSideEntityController";
|
|
8
8
|
import { useCustomizationController, useFireCMSContext } from "../hooks";
|
|
@@ -19,6 +19,7 @@ import { useBuildDataSource } from "../internal/useBuildDataSource";
|
|
|
19
19
|
import { CustomizationControllerContext } from "../contexts/CustomizationControllerContext";
|
|
20
20
|
import { AnalyticsContext } from "../contexts/AnalyticsContext";
|
|
21
21
|
import { useProjectLog } from "../hooks/useProjectLog";
|
|
22
|
+
import { BreadcrumbsProvider } from "../contexts/BreacrumbsContext";
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* If you are using independent components of the CMS
|
|
@@ -32,7 +33,7 @@ import { useProjectLog } from "../hooks/useProjectLog";
|
|
|
32
33
|
|
|
33
34
|
* @group Core
|
|
34
35
|
*/
|
|
35
|
-
export function FireCMS<USER extends User
|
|
36
|
+
export function FireCMS<USER extends User>(props: FireCMSProps<USER>) {
|
|
36
37
|
|
|
37
38
|
const {
|
|
38
39
|
children,
|
|
@@ -52,17 +53,8 @@ export function FireCMS<USER extends User, EC extends EntityCollection>(props: F
|
|
|
52
53
|
apiKey
|
|
53
54
|
} = props;
|
|
54
55
|
|
|
55
|
-
/**
|
|
56
|
-
* Controller in charge of fetching and persisting data
|
|
57
|
-
*/
|
|
58
|
-
const dataSource = useBuildDataSource({
|
|
59
|
-
delegate: dataSourceDelegate,
|
|
60
|
-
propertyConfigs,
|
|
61
|
-
navigationController
|
|
62
|
-
});
|
|
63
|
-
|
|
64
56
|
const sideDialogsController = useBuildSideDialogsController();
|
|
65
|
-
const sideEntityController = useBuildSideEntityController(navigationController, sideDialogsController);
|
|
57
|
+
const sideEntityController = useBuildSideEntityController(navigationController, sideDialogsController, authController);
|
|
66
58
|
|
|
67
59
|
const pluginsLoading = plugins?.some(p => p.loading) ?? false;
|
|
68
60
|
|
|
@@ -89,6 +81,16 @@ export function FireCMS<USER extends User, EC extends EntityCollection>(props: F
|
|
|
89
81
|
plugins
|
|
90
82
|
});
|
|
91
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Controller in charge of fetching and persisting data
|
|
86
|
+
*/
|
|
87
|
+
const dataSource = useBuildDataSource({
|
|
88
|
+
delegate: dataSourceDelegate,
|
|
89
|
+
propertyConfigs,
|
|
90
|
+
navigationController,
|
|
91
|
+
authController
|
|
92
|
+
});
|
|
93
|
+
|
|
92
94
|
if (accessResponse?.message) {
|
|
93
95
|
console.warn(accessResponse.message);
|
|
94
96
|
}
|
|
@@ -147,10 +149,12 @@ export function FireCMS<USER extends User, EC extends EntityCollection>(props: F
|
|
|
147
149
|
<NavigationContext.Provider
|
|
148
150
|
value={navigationController}>
|
|
149
151
|
<DialogsProvider>
|
|
150
|
-
<
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
152
|
+
<BreadcrumbsProvider>
|
|
153
|
+
<FireCMSInternal
|
|
154
|
+
loading={loading}>
|
|
155
|
+
{children}
|
|
156
|
+
</FireCMSInternal>
|
|
157
|
+
</BreadcrumbsProvider>
|
|
154
158
|
</DialogsProvider>
|
|
155
159
|
</NavigationContext.Provider>
|
|
156
160
|
</SideEntityControllerContext.Provider>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createBrowserRouter, RouterProvider } from "react-router-dom"
|
|
2
|
+
import { removeInitialAndTrailingSlashes } from "../util";
|
|
3
|
+
|
|
4
|
+
export function FireCMSRouter({
|
|
5
|
+
children,
|
|
6
|
+
basePath
|
|
7
|
+
}: {
|
|
8
|
+
children: React.ReactNode,
|
|
9
|
+
basePath?: string;
|
|
10
|
+
}) {
|
|
11
|
+
return <RouterProvider router={createBrowserRouter([
|
|
12
|
+
{
|
|
13
|
+
path: basePath ? `${removeInitialAndTrailingSlashes(basePath)}/*` : "/*",
|
|
14
|
+
element: children
|
|
15
|
+
}
|
|
16
|
+
])}/>;
|
|
17
|
+
}
|
|
@@ -2,9 +2,11 @@ import React from "react";
|
|
|
2
2
|
|
|
3
3
|
import { Route, Routes, useLocation } from "react-router-dom";
|
|
4
4
|
import { CMSView } from "../types";
|
|
5
|
-
import { DefaultHomePage,
|
|
5
|
+
import { DefaultHomePage, ErrorBoundary, NotFoundPage } from "../components";
|
|
6
6
|
import { useNavigationController } from "../hooks";
|
|
7
|
-
import {
|
|
7
|
+
import { FireCMSRoute } from "../routes/FireCMSRoute";
|
|
8
|
+
import { CustomCMSRoute } from "../routes/CustomCMSRoute";
|
|
9
|
+
import { HomePageRoute } from "../routes/HomePageRoute";
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
12
|
* @group Components
|
|
@@ -25,7 +27,6 @@ export type NavigationRoutesProps = {
|
|
|
25
27
|
* or the home route) related to a {@link NavigationController}.
|
|
26
28
|
* This component needs a parent {@link FireCMS}
|
|
27
29
|
*
|
|
28
|
-
|
|
29
30
|
* @group Components
|
|
30
31
|
*/
|
|
31
32
|
export const NavigationRoutes = React.memo<NavigationRoutesProps>(
|
|
@@ -67,43 +68,33 @@ export const NavigationRoutes = React.memo<NavigationRoutesProps>(
|
|
|
67
68
|
});
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
<ErrorBoundary>
|
|
81
|
-
<EntityCollectionView
|
|
82
|
-
key={`collection_view_${collection.id ?? collection.path}`}
|
|
83
|
-
isSubCollection={false}
|
|
84
|
-
parentCollectionIds={[]}
|
|
85
|
-
fullPath={collection.id ?? collection.path}
|
|
86
|
-
{...collection}
|
|
87
|
-
Actions={toArray(collection.Actions)}/>
|
|
88
|
-
</ErrorBoundary>
|
|
89
|
-
}/>;
|
|
90
|
-
}
|
|
91
|
-
);
|
|
71
|
+
const collectionUrlPath = navigation.buildUrlCollectionPath("");
|
|
72
|
+
const collectionRoute = (
|
|
73
|
+
<Route path={collectionUrlPath + "/*"}
|
|
74
|
+
key={`navigation_entity`}
|
|
75
|
+
element={
|
|
76
|
+
<ErrorBoundary>
|
|
77
|
+
<FireCMSRoute/>
|
|
78
|
+
</ErrorBoundary>
|
|
79
|
+
}/>
|
|
80
|
+
);
|
|
92
81
|
|
|
93
82
|
const homeRoute = (
|
|
94
83
|
<Route path={"/"}
|
|
95
|
-
element={homePage}/>
|
|
84
|
+
element={<HomePageRoute>{homePage}</HomePageRoute>}/>
|
|
96
85
|
);
|
|
97
86
|
|
|
98
|
-
const notFoundRoute =
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
87
|
+
const notFoundRoute = (
|
|
88
|
+
<Route path={"*"}
|
|
89
|
+
element={
|
|
90
|
+
<NotFoundPage/>
|
|
91
|
+
}/>
|
|
92
|
+
);
|
|
102
93
|
|
|
103
94
|
return (
|
|
104
95
|
<Routes location={baseLocation}>
|
|
105
96
|
|
|
106
|
-
{
|
|
97
|
+
{collectionRoute}
|
|
107
98
|
|
|
108
99
|
{cmsViews}
|
|
109
100
|
|
|
@@ -121,6 +112,6 @@ const buildCMSViewRoute = (path: string, cmsView: CMSView) => {
|
|
|
121
112
|
return <Route
|
|
122
113
|
key={"navigation_view_" + path}
|
|
123
114
|
path={path}
|
|
124
|
-
element={cmsView
|
|
115
|
+
element={<CustomCMSRoute cmsView={cmsView}/>}
|
|
125
116
|
/>;
|
|
126
117
|
};
|
package/src/core/SideDialogs.tsx
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
|
-
import React, { useContext, useEffect, useState } from "react";
|
|
1
|
+
import React, { useCallback, useContext, useEffect, useRef, useState } from "react";
|
|
2
2
|
import { useSideDialogsController } from "../hooks";
|
|
3
3
|
import { SideDialogPanelProps } from "../types";
|
|
4
4
|
import { Sheet } from "@firecms/ui";
|
|
5
|
-
import {
|
|
5
|
+
import { useNavigationUnsavedChangesDialog } from "../internal/useUnsavedChangesDialog";
|
|
6
6
|
import { ErrorBoundary } from "../components";
|
|
7
|
+
import { UnsavedChangesDialog } from "../components/UnsavedChangesDialog";
|
|
7
8
|
|
|
8
|
-
export type
|
|
9
|
+
export type SideDialogController = {
|
|
9
10
|
blocked: boolean,
|
|
10
11
|
setBlocked: (blocked: boolean) => void,
|
|
11
12
|
setBlockedNavigationMessage: (message?: React.ReactNode) => void,
|
|
12
13
|
width?: string,
|
|
13
|
-
close: (force?: boolean) => void
|
|
14
|
+
close: (force?: boolean) => void;
|
|
15
|
+
pendingClose: boolean,
|
|
16
|
+
setPendingClose: (pendingClose: boolean) => void
|
|
14
17
|
}
|
|
15
18
|
|
|
16
|
-
const SideDialogContext = React.createContext<
|
|
19
|
+
const SideDialogContext = React.createContext<SideDialogController>({
|
|
17
20
|
width: "",
|
|
18
21
|
blocked: false,
|
|
19
22
|
setBlocked: (blocked: boolean) => {
|
|
@@ -21,6 +24,10 @@ const SideDialogContext = React.createContext<SideDialogContextProps>({
|
|
|
21
24
|
setBlockedNavigationMessage: (message?: React.ReactNode) => {
|
|
22
25
|
},
|
|
23
26
|
close: () => {
|
|
27
|
+
},
|
|
28
|
+
pendingClose: false,
|
|
29
|
+
setPendingClose: () => {
|
|
30
|
+
|
|
24
31
|
}
|
|
25
32
|
});
|
|
26
33
|
|
|
@@ -29,7 +36,7 @@ const SideDialogContext = React.createContext<SideDialogContextProps>({
|
|
|
29
36
|
* in contrast with {@link useSideDialogsController} which handles the
|
|
30
37
|
* state of all the dialogs.
|
|
31
38
|
*/
|
|
32
|
-
export const useSideDialogContext = () => useContext<
|
|
39
|
+
export const useSideDialogContext = () => useContext<SideDialogController>(SideDialogContext);
|
|
33
40
|
|
|
34
41
|
/**
|
|
35
42
|
* This is the component in charge of rendering the side dialogs used
|
|
@@ -72,6 +79,8 @@ function SideDialogView({
|
|
|
72
79
|
const [blocked, setBlocked] = useState(false);
|
|
73
80
|
const [blockedNavigationMessage, setBlockedNavigationMessage] = useState<React.ReactNode | undefined>();
|
|
74
81
|
|
|
82
|
+
const [pendingClose, setPendingClose] = useState(false);
|
|
83
|
+
|
|
75
84
|
const widthRef = React.useRef<string | undefined>(panel?.width);
|
|
76
85
|
const width = widthRef.current;
|
|
77
86
|
|
|
@@ -111,8 +120,6 @@ function SideDialogView({
|
|
|
111
120
|
}
|
|
112
121
|
};
|
|
113
122
|
|
|
114
|
-
// const offsetClass = offsetPosition > 0 ? `transform translate-x-${offsetPosition}` : "";
|
|
115
|
-
|
|
116
123
|
return (
|
|
117
124
|
<SideDialogContext.Provider
|
|
118
125
|
value={{
|
|
@@ -120,7 +127,9 @@ function SideDialogView({
|
|
|
120
127
|
setBlocked,
|
|
121
128
|
setBlockedNavigationMessage,
|
|
122
129
|
width,
|
|
123
|
-
close: onCloseRequest
|
|
130
|
+
close: onCloseRequest,
|
|
131
|
+
pendingClose,
|
|
132
|
+
setPendingClose
|
|
124
133
|
}}>
|
|
125
134
|
|
|
126
135
|
<Sheet
|
|
@@ -133,7 +142,7 @@ function SideDialogView({
|
|
|
133
142
|
className={"transform max-w-[100vw] lg:max-w-[95vw] flex flex-col h-full transition-all duration-250 ease-in-out bg-white dark:bg-surface-900 "}
|
|
134
143
|
style={{
|
|
135
144
|
width: panel.width,
|
|
136
|
-
transform: `translateX(-${offsetPosition * 200}px)
|
|
145
|
+
transform: `translateX(-${offsetPosition * 200}px)`,
|
|
137
146
|
}}
|
|
138
147
|
>
|
|
139
148
|
<ErrorBoundary>
|
|
@@ -146,7 +155,7 @@ function SideDialogView({
|
|
|
146
155
|
</Sheet>
|
|
147
156
|
|
|
148
157
|
<UnsavedChangesDialog
|
|
149
|
-
open={
|
|
158
|
+
open={drawerCloseRequested}
|
|
150
159
|
handleOk={drawerCloseRequested ? handleDrawerCloseOk : handleNavigationOk}
|
|
151
160
|
handleCancel={drawerCloseRequested ? handleDrawerCloseCancel : handleNavigationCancel}
|
|
152
161
|
body={blockedNavigationMessage}/>
|
package/src/core/index.tsx
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
export * from "./FireCMS";
|
|
2
2
|
|
|
3
|
-
export * from "../app/Scaffold";
|
|
4
|
-
|
|
5
3
|
export * from "./DefaultDrawer";
|
|
6
4
|
export * from "./DrawerNavigationItem";
|
|
7
5
|
|
|
@@ -9,3 +7,7 @@ export * from "./field_configs";
|
|
|
9
7
|
|
|
10
8
|
export * from "./SideDialogs";
|
|
11
9
|
export * from "./NavigationRoutes";
|
|
10
|
+
|
|
11
|
+
export * from "./FireCMSRouter";
|
|
12
|
+
|
|
13
|
+
export * from "../app";
|