@firecms/core 3.0.0-beta.11 → 3.0.0-beta.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/ArrayContainer.d.ts +7 -3
- package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +2 -1
- package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +2 -2
- package/dist/components/EntityCollectionTable/EntityCollectionTableProps.d.ts +15 -1
- package/dist/components/EntityCollectionTable/index.d.ts +1 -1
- package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +4 -0
- package/dist/components/PropertyIdCopyTooltip.d.ts +1 -1
- package/dist/components/SelectableTable/SelectableTable.d.ts +12 -2
- package/dist/components/SelectableTable/filters/ReferenceFilterField.d.ts +1 -1
- package/dist/components/UnsavedChangesDialog.d.ts +8 -0
- package/dist/components/VirtualTable/VirtualTableProps.d.ts +13 -0
- package/dist/components/common/default_entity_actions.d.ts +0 -2
- package/dist/components/common/index.d.ts +1 -1
- package/dist/components/common/useColumnsIds.d.ts +1 -0
- package/dist/components/common/{useDataSourceEntityCollectionTableController.d.ts → useDataSourceTableController.d.ts} +10 -2
- package/dist/components/common/useDebouncedCallback.d.ts +1 -0
- package/dist/components/common/useScrollRestoration.d.ts +14 -0
- package/dist/contexts/BreacrumbsContext.d.ts +8 -0
- package/dist/core/EntityEditView.d.ts +29 -21
- package/dist/core/EntityEditViewFormActions.d.ts +2 -0
- package/dist/core/FireCMSRouter.d.ts +4 -0
- package/dist/core/NavigationRoutes.d.ts +0 -1
- package/dist/core/SideDialogs.d.ts +4 -2
- package/dist/core/index.d.ts +2 -1
- package/dist/form/EntityForm.d.ts +45 -0
- package/dist/form/EntityFormActions.d.ts +17 -0
- package/dist/form/PropertyFieldBinding.d.ts +1 -1
- package/dist/form/components/FormEntry.d.ts +6 -0
- package/dist/form/components/FormLayout.d.ts +5 -0
- package/dist/form/components/index.d.ts +2 -0
- package/dist/form/field_bindings/ArrayCustomShapedFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/ArrayOfReferencesFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/BlockFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/MapFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/MarkdownEditorFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/RepeatFieldBinding.d.ts +1 -1
- package/dist/form/index.d.ts +16 -16
- package/dist/hooks/data/useEntityFetch.d.ts +2 -1
- package/dist/hooks/useBreadcrumbsController.d.ts +26 -0
- package/dist/hooks/useBuildNavigationController.d.ts +4 -1
- package/dist/hooks/useModeController.d.ts +1 -2
- package/dist/index.es.js +18213 -16819
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +18164 -16771
- package/dist/index.umd.js.map +1 -1
- package/dist/internal/useBuildSideEntityController.d.ts +2 -2
- package/dist/internal/useUnsavedChangesDialog.d.ts +7 -9
- package/dist/routes/CustomCMSRoute.d.ts +4 -0
- package/dist/routes/FireCMSRoute.d.ts +1 -0
- package/dist/routes/HomePageRoute.d.ts +3 -0
- package/dist/types/collections.d.ts +30 -10
- package/dist/types/datasource.d.ts +2 -2
- package/dist/types/dialogs_controller.d.ts +7 -3
- package/dist/types/entities.d.ts +1 -1
- package/dist/types/entity_actions.d.ts +10 -0
- package/dist/types/fields.d.ts +9 -2
- package/dist/types/navigation.d.ts +29 -12
- package/dist/types/plugins.d.ts +1 -0
- package/dist/types/properties.d.ts +18 -3
- package/dist/types/side_entity_controller.d.ts +6 -1
- package/dist/util/builders.d.ts +1 -1
- package/dist/util/entity_actions.d.ts +2 -0
- package/dist/util/entity_cache.d.ts +23 -0
- package/dist/util/index.d.ts +1 -0
- package/dist/util/navigation_from_path.d.ts +6 -1
- package/dist/util/navigation_utils.d.ts +12 -1
- package/dist/util/resolutions.d.ts +6 -1
- package/package.json +19 -21
- package/src/components/ArrayContainer.tsx +48 -29
- package/src/components/CircularProgressCenter.tsx +1 -1
- package/src/components/DeleteEntityDialog.tsx +1 -1
- package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +40 -19
- package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +10 -4
- package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +18 -1
- package/src/components/EntityCollectionTable/index.tsx +1 -1
- package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +3 -3
- package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +5 -11
- package/src/components/EntityCollectionView/EntityCollectionView.tsx +83 -41
- package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +4 -2
- package/src/components/NotFoundPage.tsx +2 -2
- package/src/components/PropertyIdCopyTooltip.tsx +2 -3
- package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +11 -6
- package/src/components/SelectableTable/SelectableTable.tsx +18 -3
- package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +7 -7
- package/src/components/UnsavedChangesDialog.tsx +42 -0
- package/src/components/VirtualTable/VirtualTable.tsx +19 -2
- package/src/components/VirtualTable/VirtualTableProps.tsx +15 -0
- package/src/components/VirtualTable/fields/VirtualTableInput.tsx +1 -1
- package/src/components/common/default_entity_actions.tsx +26 -37
- package/src/components/common/index.ts +1 -1
- package/src/components/common/useColumnsIds.tsx +1 -1
- package/src/components/common/useDataSourceTableController.tsx +420 -0
- package/src/components/common/useDebouncedCallback.tsx +20 -0
- package/src/components/common/useScrollRestoration.tsx +68 -0
- package/src/contexts/BreacrumbsContext.tsx +38 -0
- package/src/contexts/DialogsProvider.tsx +3 -2
- package/src/contexts/ModeController.tsx +1 -3
- package/src/core/DefaultAppBar.tsx +44 -14
- package/src/core/DefaultDrawer.tsx +1 -1
- package/src/core/EntityEditView.tsx +278 -1000
- package/src/core/EntityEditViewFormActions.tsx +199 -0
- package/src/core/EntitySidePanel.tsx +85 -15
- package/src/core/FireCMS.tsx +7 -4
- package/src/core/FireCMSRouter.tsx +17 -0
- package/src/core/NavigationRoutes.tsx +23 -32
- package/src/core/SideDialogs.tsx +20 -11
- package/src/core/index.tsx +4 -2
- package/src/form/EntityForm.tsx +774 -0
- package/src/form/EntityFormActions.tsx +169 -0
- package/src/form/PropertyFieldBinding.tsx +13 -11
- package/src/form/components/FormEntry.tsx +22 -0
- package/src/form/components/FormLayout.tsx +16 -0
- package/src/form/components/LabelWithIcon.tsx +6 -3
- package/src/form/components/StorageUploadProgress.tsx +1 -1
- package/src/form/components/index.tsx +2 -0
- package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +4 -4
- package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +4 -2
- package/src/form/field_bindings/BlockFieldBinding.tsx +4 -2
- package/src/form/field_bindings/DateTimeFieldBinding.tsx +1 -0
- package/src/form/field_bindings/MapFieldBinding.tsx +14 -16
- package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +9 -6
- package/src/form/field_bindings/MultiSelectFieldBinding.tsx +1 -1
- package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +2 -2
- package/src/form/field_bindings/ReferenceFieldBinding.tsx +1 -1
- package/src/form/field_bindings/RepeatFieldBinding.tsx +5 -2
- package/src/form/field_bindings/SelectFieldBinding.tsx +1 -1
- package/src/form/field_bindings/StorageUploadFieldBinding.tsx +6 -4
- package/src/form/field_bindings/SwitchFieldBinding.tsx +1 -0
- package/src/form/field_bindings/TextFieldBinding.tsx +1 -0
- package/src/form/index.tsx +16 -32
- package/src/form/validation.ts +12 -6
- package/src/hooks/data/save.ts +1 -1
- package/src/hooks/data/useCollectionFetch.tsx +1 -1
- package/src/hooks/data/useEntityFetch.tsx +5 -1
- package/src/hooks/useBreadcrumbsController.tsx +31 -0
- package/src/hooks/useBuildModeController.tsx +15 -28
- package/src/hooks/useBuildNavigationController.tsx +70 -24
- package/src/hooks/useLargeLayout.tsx +0 -35
- package/src/hooks/useModeController.tsx +1 -2
- package/src/hooks/useResolvedNavigationFrom.tsx +4 -6
- package/src/internal/useBuildSideDialogsController.tsx +3 -2
- package/src/internal/useBuildSideEntityController.tsx +124 -83
- package/src/internal/useUnsavedChangesDialog.tsx +126 -92
- package/src/preview/PropertyPreview.tsx +8 -8
- package/src/preview/components/UrlComponentPreview.tsx +17 -18
- package/src/preview/property_previews/MapPropertyPreview.tsx +3 -2
- package/src/routes/CustomCMSRoute.tsx +21 -0
- package/src/routes/FireCMSRoute.tsx +235 -0
- package/src/routes/HomePageRoute.tsx +17 -0
- package/src/types/collections.ts +38 -13
- package/src/types/datasource.ts +2 -1
- package/src/types/dialogs_controller.tsx +7 -3
- package/src/types/entities.ts +1 -1
- package/src/types/entity_actions.tsx +12 -0
- package/src/types/fields.tsx +13 -2
- package/src/types/navigation.ts +36 -16
- package/src/types/plugins.tsx +1 -0
- package/src/types/properties.ts +21 -3
- package/src/types/side_dialogs_controller.tsx +2 -0
- package/src/types/side_entity_controller.tsx +6 -1
- package/src/util/builders.ts +5 -3
- package/src/util/entity_actions.ts +28 -0
- package/src/util/entity_cache.ts +204 -0
- package/src/util/index.ts +1 -0
- package/src/util/join_collections.ts +3 -0
- package/src/util/navigation_from_path.ts +13 -6
- package/src/util/navigation_utils.ts +53 -1
- package/src/util/objects.ts +4 -1
- package/src/util/parent_references_from_path.ts +3 -3
- package/src/util/resolutions.ts +26 -5
- package/src/util/useStorageUploadController.tsx +39 -21
- package/src/components/common/useDataSourceEntityCollectionTableController.tsx +0 -236
- /package/src/util/{common.tsx → common.ts} +0 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { Blocker, useBlocker, useLocation } from "react-router";
|
|
2
|
+
import { EntityEditView } from "../core/EntityEditView";
|
|
3
|
+
import { useNavigationController } from "../hooks";
|
|
4
|
+
import { useEffect, useRef, useState } from "react";
|
|
5
|
+
import { useNavigate } from "react-router-dom";
|
|
6
|
+
import {
|
|
7
|
+
getNavigationEntriesFromPath,
|
|
8
|
+
NavigationViewCollectionInternal,
|
|
9
|
+
NavigationViewEntityCustomInternal,
|
|
10
|
+
NavigationViewInternal
|
|
11
|
+
} from "../util/navigation_from_path";
|
|
12
|
+
import { useBreadcrumbsController } from "../hooks/useBreadcrumbsController";
|
|
13
|
+
import { toArray } from "../util/arrays";
|
|
14
|
+
import { EntityCollectionView, NotFoundPage } from "../components";
|
|
15
|
+
import { UnsavedChangesDialog } from "../components/UnsavedChangesDialog";
|
|
16
|
+
|
|
17
|
+
export function FireCMSRoute() {
|
|
18
|
+
|
|
19
|
+
const location = useLocation();
|
|
20
|
+
const navigation = useNavigationController();
|
|
21
|
+
const breadcrumbs = useBreadcrumbsController();
|
|
22
|
+
|
|
23
|
+
const hash = location.hash;
|
|
24
|
+
const isSidePanel = hash.includes("#side");
|
|
25
|
+
const isNew = hash.includes("#new") || hash.includes("#new_side");
|
|
26
|
+
const isCopy = hash.includes("#copy");
|
|
27
|
+
|
|
28
|
+
const pathname = location.pathname;
|
|
29
|
+
const navigationPath = navigation.urlPathToDataPath(pathname);
|
|
30
|
+
|
|
31
|
+
const navigationEntries = getNavigationEntriesFromPath({
|
|
32
|
+
path: navigationPath,
|
|
33
|
+
collections: navigation.collections ?? []
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
breadcrumbs.set({
|
|
38
|
+
breadcrumbs: navigationEntries.map(entry => {
|
|
39
|
+
if (entry.type === "entity") {
|
|
40
|
+
return ({
|
|
41
|
+
title: entry.entityId,
|
|
42
|
+
url: navigation.buildUrlCollectionPath(entry.fullPath)
|
|
43
|
+
});
|
|
44
|
+
} else if (entry.type === "custom_view") {
|
|
45
|
+
return ({
|
|
46
|
+
title: entry.view.name,
|
|
47
|
+
url: navigation.buildUrlCollectionPath(entry.fullPath)
|
|
48
|
+
});
|
|
49
|
+
} else if (entry.type === "collection") {
|
|
50
|
+
return ({
|
|
51
|
+
title: entry.collection.name,
|
|
52
|
+
url: navigation.buildUrlCollectionPath(entry.fullPath)
|
|
53
|
+
});
|
|
54
|
+
} else {
|
|
55
|
+
throw new Error("Unexpected navigation entry type");
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
});
|
|
59
|
+
}, [navigationEntries.map(entry => entry.path).join(",")]);
|
|
60
|
+
|
|
61
|
+
if (isNew) {
|
|
62
|
+
return <EntityFullScreenRoute
|
|
63
|
+
pathname={pathname}
|
|
64
|
+
navigationEntries={navigationEntries}
|
|
65
|
+
isNew={true}
|
|
66
|
+
isCopy={false}
|
|
67
|
+
/>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (navigationEntries.length === 1 && navigationEntries[0].type === "collection") {
|
|
71
|
+
const collection = navigation.getCollection(navigationEntries[0].path);
|
|
72
|
+
if (!collection)
|
|
73
|
+
return null;
|
|
74
|
+
return <EntityCollectionView
|
|
75
|
+
key={`collection_view_${collection.id ?? collection.path}`}
|
|
76
|
+
isSubCollection={false}
|
|
77
|
+
parentCollectionIds={[]}
|
|
78
|
+
fullPath={collection.id}
|
|
79
|
+
updateUrl={true}
|
|
80
|
+
{...collection}
|
|
81
|
+
Actions={toArray(collection.Actions)}/>
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (isSidePanel) {
|
|
85
|
+
const lastCollectionEntry = navigationEntries.findLast((entry) => entry.type === "collection");
|
|
86
|
+
if (lastCollectionEntry) {
|
|
87
|
+
const collection = navigation.getCollection(lastCollectionEntry.path);
|
|
88
|
+
if (!collection)
|
|
89
|
+
return null;
|
|
90
|
+
return <EntityCollectionView
|
|
91
|
+
key={`collection_view_${collection.id ?? collection.path}`}
|
|
92
|
+
isSubCollection={false}
|
|
93
|
+
parentCollectionIds={[]}
|
|
94
|
+
fullPath={collection.id}
|
|
95
|
+
updateUrl={true}
|
|
96
|
+
{...collection}
|
|
97
|
+
Actions={toArray(collection.Actions)}/>;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return <EntityFullScreenRoute
|
|
102
|
+
pathname={pathname}
|
|
103
|
+
navigationEntries={navigationEntries}
|
|
104
|
+
isNew={isNew}
|
|
105
|
+
isCopy={isCopy}
|
|
106
|
+
/>;
|
|
107
|
+
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function getSelectedTabFromUrl(isNew: boolean, lastCustomView: NavigationViewCollectionInternal<any> | NavigationViewEntityCustomInternal<any> | undefined) {
|
|
111
|
+
if (isNew) {
|
|
112
|
+
return undefined;
|
|
113
|
+
} else if (lastCustomView) {
|
|
114
|
+
if (lastCustomView.type === "custom_view") {
|
|
115
|
+
return lastCustomView.view.key;
|
|
116
|
+
} else if (lastCustomView.type === "collection") {
|
|
117
|
+
return lastCustomView.id ?? lastCustomView.path;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function EntityFullScreenRoute({
|
|
124
|
+
pathname,
|
|
125
|
+
navigationEntries,
|
|
126
|
+
isNew,
|
|
127
|
+
isCopy
|
|
128
|
+
}: {
|
|
129
|
+
pathname: string;
|
|
130
|
+
navigationEntries: NavigationViewInternal[],
|
|
131
|
+
isNew: boolean,
|
|
132
|
+
isCopy: boolean
|
|
133
|
+
}) {
|
|
134
|
+
|
|
135
|
+
const navigation = useNavigationController();
|
|
136
|
+
const navigate = useNavigate();
|
|
137
|
+
|
|
138
|
+
const navigationPath = navigation.urlPathToDataPath(pathname);
|
|
139
|
+
|
|
140
|
+
// is navigating away blocked
|
|
141
|
+
const blocked = useRef(false);
|
|
142
|
+
|
|
143
|
+
const lastEntityEntry = navigationEntries.findLast((entry) => entry.type === "entity");
|
|
144
|
+
const navigationEntriesAfterEntity = lastEntityEntry ? navigationEntries.slice(navigationEntries.indexOf(lastEntityEntry) + 1) : [];
|
|
145
|
+
|
|
146
|
+
const lastCustomView = navigationEntriesAfterEntity.findLast(
|
|
147
|
+
(entry) => entry.type === "custom_view" || entry.type === "collection"
|
|
148
|
+
) as NavigationViewCollectionInternal<any> | NavigationViewEntityCustomInternal<any> | undefined;
|
|
149
|
+
|
|
150
|
+
const entityId = lastEntityEntry?.entityId;
|
|
151
|
+
|
|
152
|
+
const urlTab = getSelectedTabFromUrl(isNew, lastCustomView);
|
|
153
|
+
const [selectedTab, setSelectedTab] = useState<string | undefined>(urlTab);
|
|
154
|
+
|
|
155
|
+
const parentCollectionIds = navigation.getParentCollectionIds(navigationPath);
|
|
156
|
+
useEffect(() => {
|
|
157
|
+
if (urlTab !== selectedTab) {
|
|
158
|
+
setSelectedTab(urlTab);
|
|
159
|
+
}
|
|
160
|
+
}, [urlTab]);
|
|
161
|
+
|
|
162
|
+
const basePath = !entityId || isNew
|
|
163
|
+
? pathname
|
|
164
|
+
: pathname.substring(0, pathname.lastIndexOf(`/${entityId}`));
|
|
165
|
+
|
|
166
|
+
const entityPath = basePath + `/${entityId}`;
|
|
167
|
+
|
|
168
|
+
let blocker: Blocker | undefined = undefined;
|
|
169
|
+
try {
|
|
170
|
+
blocker = useBlocker(({
|
|
171
|
+
nextLocation
|
|
172
|
+
}) => {
|
|
173
|
+
if (nextLocation.pathname.startsWith(entityPath))
|
|
174
|
+
return false;
|
|
175
|
+
return blocked.current;
|
|
176
|
+
});
|
|
177
|
+
} catch (e) {
|
|
178
|
+
console.warn("Blocker not available, navigation will not be blocked");
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const lastCollectionEntry = navigationEntries.findLast((entry) => entry.type === "collection");
|
|
182
|
+
|
|
183
|
+
if (isNew && !lastCollectionEntry) {
|
|
184
|
+
throw new Error("INTERNAL: No collection found in the navigation");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (!isNew && !lastEntityEntry) {
|
|
188
|
+
return <NotFoundPage/>;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const collection = isNew ? lastCollectionEntry!.collection : lastEntityEntry!.parentCollection;
|
|
192
|
+
const collectionPath = navigation.resolveIdsFrom(isNew ? lastCollectionEntry!.path : lastEntityEntry!.path);
|
|
193
|
+
|
|
194
|
+
return <>
|
|
195
|
+
<EntityEditView
|
|
196
|
+
key={collection.id + "_" + (isNew ? "new" : (isCopy ? entityId + "_copy" : entityId))}
|
|
197
|
+
entityId={isNew ? undefined : entityId}
|
|
198
|
+
collection={collection}
|
|
199
|
+
layout={"full_screen"}
|
|
200
|
+
path={collectionPath}
|
|
201
|
+
copy={isCopy}
|
|
202
|
+
selectedTab={selectedTab ?? undefined}
|
|
203
|
+
onValuesModified={(modified) => blocked.current = modified}
|
|
204
|
+
onSaved={(params) => {
|
|
205
|
+
const newSelectedTab = params.selectedTab;
|
|
206
|
+
const newEntityId = params.entityId;
|
|
207
|
+
if (newSelectedTab) {
|
|
208
|
+
navigate(`${basePath}/${newEntityId}/${newSelectedTab}`, { replace: true });
|
|
209
|
+
} else {
|
|
210
|
+
navigate(`${basePath}/${newEntityId}`, { replace: true });
|
|
211
|
+
}
|
|
212
|
+
}}
|
|
213
|
+
onTabChange={(params) => {
|
|
214
|
+
setSelectedTab(params.selectedTab);
|
|
215
|
+
if (isNew) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
const newSelectedTab = params.selectedTab;
|
|
219
|
+
if (newSelectedTab) {
|
|
220
|
+
navigate(`${basePath}/${entityId}/${newSelectedTab}`, { replace: true });
|
|
221
|
+
} else {
|
|
222
|
+
navigate(`${basePath}/${entityId}`, { replace: true });
|
|
223
|
+
}
|
|
224
|
+
}}
|
|
225
|
+
parentCollectionIds={parentCollectionIds}
|
|
226
|
+
/>
|
|
227
|
+
|
|
228
|
+
<UnsavedChangesDialog
|
|
229
|
+
open={blocker?.state === "blocked"}
|
|
230
|
+
handleOk={() => blocker?.proceed?.()}
|
|
231
|
+
handleCancel={() => blocker?.reset?.()}
|
|
232
|
+
body={"You have unsaved changes in this entity."}/>
|
|
233
|
+
|
|
234
|
+
</>;
|
|
235
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { useBreadcrumbsController } from "../hooks/useBreadcrumbsController";
|
|
2
|
+
import { useEffect } from "react";
|
|
3
|
+
|
|
4
|
+
export function HomePageRoute({ children }: {
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
}) {
|
|
7
|
+
|
|
8
|
+
const breadcrumbs = useBreadcrumbsController();
|
|
9
|
+
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
breadcrumbs.set({
|
|
12
|
+
breadcrumbs: []
|
|
13
|
+
});
|
|
14
|
+
}, []);
|
|
15
|
+
|
|
16
|
+
return children;
|
|
17
|
+
}
|
package/src/types/collections.ts
CHANGED
|
@@ -84,6 +84,11 @@ export interface EntityCollection<M extends Record<string, any> = any, USER exte
|
|
|
84
84
|
*/
|
|
85
85
|
properties: PropertiesOrBuilders<M>;
|
|
86
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Default preview properties displayed when this collection is referenced to.
|
|
89
|
+
*/
|
|
90
|
+
previewProperties?: string[];
|
|
91
|
+
|
|
87
92
|
/**
|
|
88
93
|
* Title property of the entity. This is the property that will be used
|
|
89
94
|
* as the title in entity related views and references.
|
|
@@ -92,11 +97,10 @@ export interface EntityCollection<M extends Record<string, any> = any, USER exte
|
|
|
92
97
|
titleProperty?: keyof M;
|
|
93
98
|
|
|
94
99
|
/**
|
|
95
|
-
*
|
|
96
|
-
* Defaults to `
|
|
97
|
-
* Keep in mind that you can also set this prop to individual properties.
|
|
100
|
+
* When editing an entity, you can choose to open the entity in a side dialog
|
|
101
|
+
* or in a full screen dialog. Defaults to `full_screen`.
|
|
98
102
|
*/
|
|
99
|
-
|
|
103
|
+
openEntityMode?: "side_panel" | "full_screen";
|
|
100
104
|
|
|
101
105
|
/**
|
|
102
106
|
* Order in which the properties are displayed.
|
|
@@ -119,7 +123,7 @@ export interface EntityCollection<M extends Record<string, any> = any, USER exte
|
|
|
119
123
|
* `hidden` in the property definition,will be ignored.
|
|
120
124
|
* `propertiesOrder` has precedence over `hidden`.
|
|
121
125
|
*/
|
|
122
|
-
propertiesOrder?: (Extract<keyof M, string> | `subcollection:${string}`)[];
|
|
126
|
+
propertiesOrder?: (Extract<keyof M, string> | string | `subcollection:${string}` | "collectionGroupParent")[];
|
|
123
127
|
|
|
124
128
|
/**
|
|
125
129
|
* If enabled, content is loaded in batches. If `false` all entities in the
|
|
@@ -313,9 +317,19 @@ export interface EntityCollection<M extends Record<string, any> = any, USER exte
|
|
|
313
317
|
sideDialogWidth?: number | string;
|
|
314
318
|
|
|
315
319
|
/**
|
|
316
|
-
*
|
|
320
|
+
* Can this collection configuration be edited by the end user.
|
|
321
|
+
* Defaults to `true`.
|
|
322
|
+
* Keep in mind that you can also set this prop to individual properties.
|
|
323
|
+
* This prop has only effect if you are using the collection editor.
|
|
317
324
|
*/
|
|
318
|
-
|
|
325
|
+
editable?: boolean;
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* If set to true, the default values of the properties will be applied
|
|
329
|
+
* to the entity every time the entity is updated (not only when created).
|
|
330
|
+
* Defaults to false.
|
|
331
|
+
*/
|
|
332
|
+
alwaysApplyDefaultValues?: boolean;
|
|
319
333
|
}
|
|
320
334
|
|
|
321
335
|
/**
|
|
@@ -491,21 +505,26 @@ export type EntityCustomView<M extends Record<string, any> = any> =
|
|
|
491
505
|
/**
|
|
492
506
|
* Key of this custom view.
|
|
493
507
|
*/
|
|
494
|
-
key: string
|
|
508
|
+
key: string;
|
|
509
|
+
|
|
495
510
|
/**
|
|
496
511
|
* Name of this custom view.
|
|
497
512
|
*/
|
|
498
|
-
name: string
|
|
513
|
+
name: string;
|
|
514
|
+
|
|
499
515
|
/**
|
|
500
|
-
* If set to true, the actions of the entity will be
|
|
501
|
-
*
|
|
516
|
+
* If set to true, the actions of the entity (save, discard,delete) will be
|
|
517
|
+
* included in the view. By default the actions are located in the right or bottom,
|
|
518
|
+
* based on the screen size. You can force the actions to be located at the bottom
|
|
519
|
+
* by setting this prop to "bottom".
|
|
502
520
|
*/
|
|
503
|
-
includeActions?: boolean;
|
|
521
|
+
includeActions?: boolean | "bottom";
|
|
522
|
+
|
|
504
523
|
/**
|
|
505
524
|
* Builder for rendering the custom view
|
|
506
525
|
*/
|
|
507
526
|
Builder?: React.ComponentType<EntityCustomViewParams<M>>;
|
|
508
|
-
}
|
|
527
|
+
};
|
|
509
528
|
|
|
510
529
|
/**
|
|
511
530
|
* Parameters passed to the builder in charge of rendering a custom panel for
|
|
@@ -570,6 +589,12 @@ export type EntityTableController<M extends Record<string, any> = any> = {
|
|
|
570
589
|
clearFilter?: () => void;
|
|
571
590
|
itemCount?: number;
|
|
572
591
|
setItemCount?: (itemCount: number) => void;
|
|
592
|
+
initialScroll?: number;
|
|
593
|
+
onScroll?: (props: {
|
|
594
|
+
scrollDirection: "forward" | "backward",
|
|
595
|
+
scrollOffset: number,
|
|
596
|
+
scrollUpdateWasRequested: boolean
|
|
597
|
+
}) => void;
|
|
573
598
|
paginationEnabled?: boolean;
|
|
574
599
|
pageSize?: number;
|
|
575
600
|
checkFilterCombination?: (filterValues: FilterValues<any>,
|
package/src/types/datasource.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { FireCMSContext } from "./firecms_context";
|
|
|
9
9
|
export interface FetchEntityProps<M extends Record<string, any> = any> {
|
|
10
10
|
path: string;
|
|
11
11
|
entityId: string;
|
|
12
|
+
databaseId?: string;
|
|
12
13
|
collection?: EntityCollection<M, any>
|
|
13
14
|
}
|
|
14
15
|
|
|
@@ -18,7 +19,6 @@ export interface FetchEntityProps<M extends Record<string, any> = any> {
|
|
|
18
19
|
export type ListenEntityProps<M extends Record<string, any> = any> =
|
|
19
20
|
FetchEntityProps<M>
|
|
20
21
|
& {
|
|
21
|
-
databaseId?: string;
|
|
22
22
|
onUpdate: (entity: Entity<M>) => void,
|
|
23
23
|
onError?: (error: Error) => void,
|
|
24
24
|
}
|
|
@@ -139,6 +139,7 @@ export interface DataSource {
|
|
|
139
139
|
fetchEntity<M extends Record<string, any> = any>({
|
|
140
140
|
path,
|
|
141
141
|
entityId,
|
|
142
|
+
databaseId,
|
|
142
143
|
collection
|
|
143
144
|
}: FetchEntityProps<M>
|
|
144
145
|
): Promise<Entity<M> | undefined>;
|
|
@@ -15,19 +15,23 @@ export interface DialogsController {
|
|
|
15
15
|
* Open a dialog
|
|
16
16
|
* @param props
|
|
17
17
|
*/
|
|
18
|
-
open: (props: DialogControllerEntryProps) => { closeDialog: () => void };
|
|
18
|
+
open: <T extends object = object>(props: DialogControllerEntryProps<T>) => { closeDialog: () => void };
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Props used to open a side dialog
|
|
23
23
|
* @group Hooks and utilities
|
|
24
24
|
*/
|
|
25
|
-
export interface DialogControllerEntryProps {
|
|
25
|
+
export interface DialogControllerEntryProps<T extends object = object> {
|
|
26
26
|
|
|
27
27
|
key: string;
|
|
28
28
|
/**
|
|
29
29
|
* The component type that will be rendered
|
|
30
30
|
*/
|
|
31
|
-
Component: React.ComponentType<{ open: boolean, closeDialog: () => void }>;
|
|
31
|
+
Component: React.ComponentType<{ open: boolean, closeDialog: () => void } & T>;
|
|
32
|
+
/**
|
|
33
|
+
* Props to pass to the dialog component
|
|
34
|
+
*/
|
|
35
|
+
props?: T;
|
|
32
36
|
|
|
33
37
|
}
|
package/src/types/entities.ts
CHANGED
|
@@ -8,7 +8,7 @@ export type EntityStatus = "new" | "existing" | "copy";
|
|
|
8
8
|
* Representation of an entity fetched from the datasource
|
|
9
9
|
* @group Models
|
|
10
10
|
*/
|
|
11
|
-
export interface Entity<M extends object> {
|
|
11
|
+
export interface Entity<M extends object = any> {
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* ID of the entity
|
|
@@ -15,6 +15,16 @@ export type EntityAction<M extends object = any, USER extends User = User> = {
|
|
|
15
15
|
*/
|
|
16
16
|
name: string;
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Key of the action. You only need to provide this if you want to
|
|
20
|
+
* override the default actions.
|
|
21
|
+
* The default actions are:
|
|
22
|
+
* - edit
|
|
23
|
+
* - delete
|
|
24
|
+
* - copy
|
|
25
|
+
*/
|
|
26
|
+
key?: string;
|
|
27
|
+
|
|
18
28
|
/**
|
|
19
29
|
* Icon of the action
|
|
20
30
|
*/
|
|
@@ -53,4 +63,6 @@ export type EntityActionClickProps<M extends object, USER extends User = User> =
|
|
|
53
63
|
* If this actions is being called from a side dialog
|
|
54
64
|
*/
|
|
55
65
|
sideEntityController?: SideEntityController;
|
|
66
|
+
|
|
67
|
+
openEntityMode: "side_panel" | "full_screen";
|
|
56
68
|
};
|
package/src/types/fields.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CMSType, Property, PropertyOrBuilder } from "./properties";
|
|
2
2
|
import { ResolvedEntityCollection, ResolvedProperty } from "./resolved_entities";
|
|
3
3
|
import { FormexController } from "@firecms/formex";
|
|
4
|
+
import { Entity } from "./entities";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* When building a custom field you need to create a React component that takes
|
|
@@ -81,7 +82,8 @@ export interface FieldProps<T extends CMSType = any, CustomProps = any, M extend
|
|
|
81
82
|
partOfArray?: boolean;
|
|
82
83
|
|
|
83
84
|
/**
|
|
84
|
-
*
|
|
85
|
+
* Display the child properties directly, without being wrapped in an
|
|
86
|
+
* extendable panel. Note that this will also hide the title of this property.
|
|
85
87
|
*/
|
|
86
88
|
minimalistView?: boolean;
|
|
87
89
|
|
|
@@ -160,6 +162,14 @@ export interface FormContext<M extends Record<string, any> = any> {
|
|
|
160
162
|
*/
|
|
161
163
|
path?: string;
|
|
162
164
|
|
|
165
|
+
status: "new" | "existing" | "copy";
|
|
166
|
+
|
|
167
|
+
entity?: Entity<M>;
|
|
168
|
+
|
|
169
|
+
savingError?: Error;
|
|
170
|
+
|
|
171
|
+
openEntityMode: "side_panel" | "full_screen";
|
|
172
|
+
|
|
163
173
|
/**
|
|
164
174
|
* This is the underlying formex controller that powers the form
|
|
165
175
|
*/
|
|
@@ -207,7 +217,8 @@ export interface PropertyFieldBindingProps<T extends CMSType, M extends Record<s
|
|
|
207
217
|
partOfArray?: boolean;
|
|
208
218
|
|
|
209
219
|
/**
|
|
210
|
-
*
|
|
220
|
+
* Display the child properties directly, without being wrapped in an
|
|
221
|
+
* extendable panel. Note that this will also hide the title of this property.
|
|
211
222
|
*/
|
|
212
223
|
minimalistView?: boolean;
|
|
213
224
|
|
package/src/types/navigation.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { EntityReference } from "./entities";
|
|
|
5
5
|
/**
|
|
6
6
|
* Controller that includes the resolved navigation and utility methods and
|
|
7
7
|
* attributes.
|
|
8
|
+
* This controller holds the state of the navigation including the collections.
|
|
8
9
|
* @group Models
|
|
9
10
|
*/
|
|
10
11
|
export type NavigationController<EC extends EntityCollection = EntityCollection<any>> = {
|
|
@@ -56,8 +57,8 @@ export type NavigationController<EC extends EntityCollection = EntityCollection<
|
|
|
56
57
|
* Get the collection configuration for a given path.
|
|
57
58
|
* The collection is resolved from the given path or alias.
|
|
58
59
|
*/
|
|
59
|
-
getCollection: (pathOrId: string,
|
|
60
|
-
|
|
60
|
+
getCollection: (pathOrId: string, includeUserOverride?: boolean) => EC | undefined;
|
|
61
|
+
|
|
61
62
|
/**
|
|
62
63
|
* Get the collection configuration from its parent ids.
|
|
63
64
|
*/
|
|
@@ -69,12 +70,14 @@ export type NavigationController<EC extends EntityCollection = EntityCollection<
|
|
|
69
70
|
getCollectionFromPaths: (pathSegments: string[]) => EC | undefined;
|
|
70
71
|
|
|
71
72
|
/**
|
|
72
|
-
* Default path under the navigation routes of the CMS will be created
|
|
73
|
+
* Default path under the navigation routes of the CMS will be created.
|
|
74
|
+
* Defaults to '/'. You may want to change this `basepath` to 'admin' for example.
|
|
73
75
|
*/
|
|
74
76
|
basePath: string;
|
|
75
77
|
|
|
76
78
|
/**
|
|
77
|
-
* Default path under the collection routes of the CMS will be created
|
|
79
|
+
* Default path under the collection routes of the CMS will be created.
|
|
80
|
+
* It defaults to '/c'
|
|
78
81
|
*/
|
|
79
82
|
baseCollectionPath: string;
|
|
80
83
|
|
|
@@ -87,16 +90,6 @@ export type NavigationController<EC extends EntityCollection = EntityCollection<
|
|
|
87
90
|
*/
|
|
88
91
|
urlPathToDataPath: (cmsPath: string) => string;
|
|
89
92
|
|
|
90
|
-
/**
|
|
91
|
-
* Convert a collection or entity path to a URL path
|
|
92
|
-
* @param path
|
|
93
|
-
*/
|
|
94
|
-
buildCMSUrlPath: (path: string) => string;
|
|
95
|
-
|
|
96
|
-
buildUrlEditCollectionPath: (props: {
|
|
97
|
-
path: string
|
|
98
|
-
}) => string;
|
|
99
|
-
|
|
100
93
|
/**
|
|
101
94
|
* Base url path for the home screen
|
|
102
95
|
*/
|
|
@@ -117,10 +110,11 @@ export type NavigationController<EC extends EntityCollection = EntityCollection<
|
|
|
117
110
|
buildUrlCollectionPath: (path: string) => string;
|
|
118
111
|
|
|
119
112
|
/**
|
|
120
|
-
* Turn a path with
|
|
113
|
+
* Turn a path with collection ids into a resolved path.
|
|
114
|
+
* The ids (typically used in urls) will be replaced with relative paths (typically used in database paths)
|
|
121
115
|
* @param pathWithAliases
|
|
122
116
|
*/
|
|
123
|
-
|
|
117
|
+
resolveIdsFrom: (pathWithAliases: string) => string;
|
|
124
118
|
|
|
125
119
|
/**
|
|
126
120
|
* Call this method to recalculate the navigation
|
|
@@ -144,8 +138,34 @@ export type NavigationController<EC extends EntityCollection = EntityCollection<
|
|
|
144
138
|
* @param ids
|
|
145
139
|
*/
|
|
146
140
|
convertIdsToPaths: (ids: string[]) => string[];
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* A function to navigate to a specified route or URL.
|
|
144
|
+
*
|
|
145
|
+
* @param {string} to - The target route or URL to navigate to.
|
|
146
|
+
* @param {NavigateOptions} [options] - Optional configuration settings for navigation, such as replace behavior or state data.
|
|
147
|
+
*/
|
|
148
|
+
navigate: (to: string, options?: NavigateOptions) => void;
|
|
149
|
+
|
|
147
150
|
}
|
|
148
151
|
|
|
152
|
+
export interface NavigateOptions {
|
|
153
|
+
replace?: boolean;
|
|
154
|
+
state?: any;
|
|
155
|
+
preventScrollReset?: boolean;
|
|
156
|
+
relative?: "route" | "path";
|
|
157
|
+
flushSync?: boolean;
|
|
158
|
+
viewTransition?: boolean;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// currently not used, in favor of a single blocker in `FireCMSRoute`
|
|
162
|
+
export type NavigationBlocker = {
|
|
163
|
+
updateBlockListener: (path: string, block: boolean, basePath?: string) => () => void;
|
|
164
|
+
isBlocked: (path: string) => boolean;
|
|
165
|
+
proceed?: () => void;
|
|
166
|
+
reset?: () => void;
|
|
167
|
+
};
|
|
168
|
+
|
|
149
169
|
/**
|
|
150
170
|
* Custom additional views created by the developer, added to the main
|
|
151
171
|
* navigation.
|
package/src/types/plugins.tsx
CHANGED
|
@@ -195,6 +195,7 @@ export interface PluginFormActionProps<USER extends User = User, EC extends Enti
|
|
|
195
195
|
formContext?: FormContext<any>;
|
|
196
196
|
context: FireCMSContext<USER>;
|
|
197
197
|
currentEntityId?: string;
|
|
198
|
+
openEntityMode: "side_panel" | "full_screen";
|
|
198
199
|
}
|
|
199
200
|
|
|
200
201
|
export type PluginFieldBuilderParams<T extends CMSType = CMSType, M extends Record<string, any> = any, EC extends EntityCollection<M> = EntityCollection<M>> = {
|
package/src/types/properties.ts
CHANGED
|
@@ -156,6 +156,12 @@ export interface BaseProperty<T extends CMSType, CustomProps = any> {
|
|
|
156
156
|
* save the new config. The saved config will then become the source of truth.
|
|
157
157
|
*/
|
|
158
158
|
editable?: boolean;
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* A number between 0 and 100 that indicates the width of the field in the form view.
|
|
162
|
+
* It defaults to 100, but you can set it to 50 to have two fields in the same row.
|
|
163
|
+
*/
|
|
164
|
+
widthPercentage?: number;
|
|
159
165
|
}
|
|
160
166
|
|
|
161
167
|
/**
|
|
@@ -285,14 +291,14 @@ export type PropertyBuilder<T extends CMSType = any, M extends Record<string, an
|
|
|
285
291
|
/**
|
|
286
292
|
* @group Entity properties
|
|
287
293
|
*/
|
|
288
|
-
export type PropertyOrBuilder<T extends CMSType = CMSType, M extends Record<string, any> =
|
|
294
|
+
export type PropertyOrBuilder<T extends CMSType = CMSType, M extends Record<string, any> = any> =
|
|
289
295
|
Property<T>
|
|
290
296
|
| PropertyBuilder<T, M>;
|
|
291
297
|
|
|
292
298
|
/**
|
|
293
299
|
* @group Entity properties
|
|
294
300
|
*/
|
|
295
|
-
export type PropertiesOrBuilders<M extends Record<string, any> =
|
|
301
|
+
export type PropertiesOrBuilders<M extends Record<string, any> = any> =
|
|
296
302
|
{
|
|
297
303
|
[k in keyof M]: PropertyOrBuilder<M[k], M>;
|
|
298
304
|
};
|
|
@@ -371,7 +377,7 @@ export interface StringProperty extends BaseProperty<string> {
|
|
|
371
377
|
|
|
372
378
|
/**
|
|
373
379
|
* You can specify a `Storage` configuration. It is used to
|
|
374
|
-
* indicate that this string refers to a path in
|
|
380
|
+
* indicate that this string refers to a path in your storage provider.
|
|
375
381
|
*/
|
|
376
382
|
storage?: StorageConfig;
|
|
377
383
|
|
|
@@ -469,6 +475,12 @@ export interface ArrayProperty<T extends ArrayT[] = any[], ArrayT extends CMSTyp
|
|
|
469
475
|
*/
|
|
470
476
|
expanded?: boolean;
|
|
471
477
|
|
|
478
|
+
/**
|
|
479
|
+
* Display the child properties directly, without being wrapped in an
|
|
480
|
+
* extendable panel.
|
|
481
|
+
*/
|
|
482
|
+
minimalistView?: boolean;
|
|
483
|
+
|
|
472
484
|
}
|
|
473
485
|
|
|
474
486
|
/**
|
|
@@ -517,6 +529,12 @@ export interface MapProperty<T extends Record<string, CMSType> = Record<string,
|
|
|
517
529
|
*/
|
|
518
530
|
spreadChildren?: boolean;
|
|
519
531
|
|
|
532
|
+
/**
|
|
533
|
+
* Display the child properties directly, without being wrapped in an
|
|
534
|
+
* extendable panel. Note that this will also hide the title of this property.
|
|
535
|
+
*/
|
|
536
|
+
minimalistView?: boolean;
|
|
537
|
+
|
|
520
538
|
/**
|
|
521
539
|
* Should the field be initially expanded. Defaults to `true`
|
|
522
540
|
*/
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
+
import { EntityFormProps } from "../form";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Controller to open the side dialog
|
|
@@ -78,4 +79,5 @@ export interface SideDialogPanelProps {
|
|
|
78
79
|
* Use this prop to store additional data in the panel
|
|
79
80
|
*/
|
|
80
81
|
additional?: any;
|
|
82
|
+
|
|
81
83
|
}
|