@firecms/core 3.0.0-canary.167 → 3.0.0-canary.169
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/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/SelectableTable/SelectableTable.d.ts +12 -2
- package/dist/components/UnsavedChangesDialog.d.ts +8 -0
- package/dist/components/VirtualTable/VirtualTableProps.d.ts +13 -0
- package/dist/components/common/index.d.ts +1 -1
- 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 +25 -7
- package/dist/core/FireCMSRouter.d.ts +4 -0
- package/dist/core/NavigationRoutes.d.ts +0 -1
- package/dist/core/index.d.ts +2 -1
- package/dist/hooks/data/useEntityFetch.d.ts +2 -1
- package/dist/hooks/useBreadcrumbsController.d.ts +26 -0
- package/dist/hooks/useBuildNavigationController.d.ts +0 -1
- package/dist/index.es.js +2344 -1305
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +2344 -1306
- package/dist/index.umd.js.map +1 -1
- 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 +18 -6
- package/dist/types/datasource.d.ts +2 -2
- package/dist/types/dialogs_controller.d.ts +2 -6
- package/dist/types/entities.d.ts +1 -1
- package/dist/types/entity_actions.d.ts +1 -6
- package/dist/types/navigation.d.ts +28 -12
- package/dist/types/plugins.d.ts +1 -0
- package/dist/types/side_entity_controller.d.ts +1 -1
- package/dist/util/entity_cache.d.ts +23 -0
- package/dist/util/navigation_from_path.d.ts +5 -1
- package/dist/util/navigation_utils.d.ts +12 -1
- package/package.json +5 -5
- 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/EntityCollectionView/EntityCollectionView.tsx +79 -38
- package/src/components/NotFoundPage.tsx +2 -2
- package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +9 -5
- package/src/components/SelectableTable/SelectableTable.tsx +18 -3
- 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 +24 -10
- package/src/components/common/index.ts +1 -1
- package/src/components/common/{useDataSourceEntityCollectionTableController.tsx → useDataSourceTableController.tsx} +122 -13
- 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 +0 -1
- package/src/core/DefaultAppBar.tsx +28 -4
- package/src/core/EntityEditView.tsx +457 -296
- package/src/core/EntitySidePanel.tsx +72 -6
- package/src/core/FireCMS.tsx +7 -4
- package/src/core/FireCMSRouter.tsx +17 -0
- package/src/core/NavigationRoutes.tsx +15 -28
- package/src/core/SideDialogs.tsx +10 -5
- package/src/core/index.tsx +4 -2
- 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/useBuildNavigationController.tsx +59 -21
- package/src/hooks/useLargeLayout.tsx +0 -35
- package/src/hooks/useResolvedNavigationFrom.tsx +4 -6
- package/src/internal/useBuildSideDialogsController.tsx +4 -2
- package/src/internal/useBuildSideEntityController.tsx +157 -77
- package/src/internal/useUnsavedChangesDialog.tsx +126 -92
- package/src/routes/CustomCMSRoute.tsx +21 -0
- package/src/routes/FireCMSRoute.tsx +230 -0
- package/src/routes/HomePageRoute.tsx +17 -0
- package/src/types/collections.ts +19 -6
- package/src/types/datasource.ts +2 -1
- package/src/types/dialogs_controller.tsx +2 -6
- package/src/types/entities.ts +1 -1
- package/src/types/entity_actions.tsx +1 -7
- package/src/types/navigation.ts +35 -16
- package/src/types/plugins.tsx +1 -0
- package/src/types/side_entity_controller.tsx +1 -1
- package/src/util/entity_cache.ts +196 -0
- package/src/util/navigation_from_path.ts +10 -2
- package/src/util/navigation_utils.ts +53 -1
- package/src/util/parent_references_from_path.ts +3 -3
- /package/src/util/{common.tsx → common.ts} +0 -0
|
@@ -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,11 +22,41 @@ 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();
|
|
27
34
|
|
|
35
|
+
const onClose = () => {
|
|
36
|
+
if (props.onClose) {
|
|
37
|
+
props.onClose();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
setBlocked(false);
|
|
41
|
+
close(true);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const onUpdate = (params: OnUpdateParams) => {
|
|
45
|
+
if (props.onUpdate) {
|
|
46
|
+
props.onUpdate(params);
|
|
47
|
+
}
|
|
48
|
+
if (params.status !== "existing") {
|
|
49
|
+
sideEntityController.replace({
|
|
50
|
+
path: params.path,
|
|
51
|
+
entityId: params.entityId,
|
|
52
|
+
selectedTab: params.selectedTab,
|
|
53
|
+
updateUrl: true,
|
|
54
|
+
collection: params.collection,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
}
|
|
59
|
+
|
|
28
60
|
const parentCollectionIds = useMemo(() => {
|
|
29
61
|
return navigationController.getParentCollectionIds(props.path);
|
|
30
62
|
}, [navigationController, props.path]);
|
|
@@ -63,11 +95,11 @@ export function EntitySidePanel(props: EntitySidePanelProps) {
|
|
|
63
95
|
|
|
64
96
|
}, [blocked, collection]);
|
|
65
97
|
|
|
66
|
-
const
|
|
67
|
-
setBlocked(modified);
|
|
98
|
+
const onValuesModified = useCallback((modified: boolean) => {
|
|
68
99
|
setBlockedNavigationMessage(modified
|
|
69
100
|
? <> You have unsaved changes in this <b>{collection?.singularName ?? collection?.name}</b>.</>
|
|
70
101
|
: undefined)
|
|
102
|
+
setBlocked(modified);
|
|
71
103
|
}, [collection?.name, setBlocked, setBlockedNavigationMessage]);
|
|
72
104
|
|
|
73
105
|
if (!props || !collection) {
|
|
@@ -79,9 +111,43 @@ export function EntitySidePanel(props: EntitySidePanelProps) {
|
|
|
79
111
|
<ErrorBoundary>
|
|
80
112
|
<EntityEditView
|
|
81
113
|
{...props}
|
|
114
|
+
layout={"side_panel"}
|
|
82
115
|
collection={collection}
|
|
83
116
|
parentCollectionIds={parentCollectionIds}
|
|
84
|
-
|
|
117
|
+
onValuesModified={onValuesModified}
|
|
118
|
+
onClose={onClose}
|
|
119
|
+
onSaved={onUpdate}
|
|
120
|
+
barActions={<>
|
|
121
|
+
<IconButton
|
|
122
|
+
className="self-center"
|
|
123
|
+
onClick={onClose}>
|
|
124
|
+
<CloseIcon size={"small"}/>
|
|
125
|
+
</IconButton>
|
|
126
|
+
<IconButton
|
|
127
|
+
className="self-center"
|
|
128
|
+
onClick={() => {
|
|
129
|
+
if (props.entityId)
|
|
130
|
+
navigate(location.pathname);
|
|
131
|
+
else
|
|
132
|
+
navigate(location.pathname + "#new");
|
|
133
|
+
}}>
|
|
134
|
+
<OpenInFullIcon size={"small"}/>
|
|
135
|
+
</IconButton>
|
|
136
|
+
</>}
|
|
137
|
+
onTabChange={({
|
|
138
|
+
path,
|
|
139
|
+
entityId,
|
|
140
|
+
selectedTab,
|
|
141
|
+
collection
|
|
142
|
+
}) => {
|
|
143
|
+
sideEntityController.replace({
|
|
144
|
+
path,
|
|
145
|
+
entityId,
|
|
146
|
+
selectedTab,
|
|
147
|
+
updateUrl: true,
|
|
148
|
+
collection
|
|
149
|
+
});
|
|
150
|
+
}}
|
|
85
151
|
/>
|
|
86
152
|
</ErrorBoundary>
|
|
87
153
|
|
package/src/core/FireCMS.tsx
CHANGED
|
@@ -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
|
|
@@ -147,10 +148,12 @@ export function FireCMS<USER extends User, EC extends EntityCollection>(props: F
|
|
|
147
148
|
<NavigationContext.Provider
|
|
148
149
|
value={navigationController}>
|
|
149
150
|
<DialogsProvider>
|
|
150
|
-
<
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
151
|
+
<BreadcrumbsProvider>
|
|
152
|
+
<FireCMSInternal
|
|
153
|
+
loading={loading}>
|
|
154
|
+
{children}
|
|
155
|
+
</FireCMSInternal>
|
|
156
|
+
</BreadcrumbsProvider>
|
|
154
157
|
</DialogsProvider>
|
|
155
158
|
</NavigationContext.Provider>
|
|
156
159
|
</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,32 +68,18 @@ export const NavigationRoutes = React.memo<NavigationRoutesProps>(
|
|
|
67
68
|
});
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
key={`navigation_${collection.id ?? collection.path}`}
|
|
79
|
-
element={
|
|
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 urlPath = navigation.buildUrlCollectionPath("");
|
|
72
|
+
const collectionRoute = <Route path={urlPath + "/*"}
|
|
73
|
+
key={`navigation_entity`}
|
|
74
|
+
element={
|
|
75
|
+
<ErrorBoundary>
|
|
76
|
+
<FireCMSRoute/>
|
|
77
|
+
</ErrorBoundary>
|
|
78
|
+
}/>
|
|
92
79
|
|
|
93
80
|
const homeRoute = (
|
|
94
81
|
<Route path={"/"}
|
|
95
|
-
element={homePage}/>
|
|
82
|
+
element={<HomePageRoute>{homePage}</HomePageRoute>}/>
|
|
96
83
|
);
|
|
97
84
|
|
|
98
85
|
const notFoundRoute = <Route path={"*"}
|
|
@@ -103,7 +90,7 @@ export const NavigationRoutes = React.memo<NavigationRoutesProps>(
|
|
|
103
90
|
return (
|
|
104
91
|
<Routes location={baseLocation}>
|
|
105
92
|
|
|
106
|
-
{
|
|
93
|
+
{collectionRoute}
|
|
107
94
|
|
|
108
95
|
{cmsViews}
|
|
109
96
|
|
|
@@ -121,6 +108,6 @@ const buildCMSViewRoute = (path: string, cmsView: CMSView) => {
|
|
|
121
108
|
return <Route
|
|
122
109
|
key={"navigation_view_" + path}
|
|
123
110
|
path={path}
|
|
124
|
-
element={cmsView
|
|
111
|
+
element={<CustomCMSRoute cmsView={cmsView}/>}
|
|
125
112
|
/>;
|
|
126
113
|
};
|
package/src/core/SideDialogs.tsx
CHANGED
|
@@ -2,8 +2,9 @@ import React, { useContext, useEffect, 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
9
|
export type SideDialogContextProps = {
|
|
9
10
|
blocked: boolean,
|
|
@@ -111,8 +112,6 @@ function SideDialogView({
|
|
|
111
112
|
}
|
|
112
113
|
};
|
|
113
114
|
|
|
114
|
-
// const offsetClass = offsetPosition > 0 ? `transform translate-x-${offsetPosition}` : "";
|
|
115
|
-
|
|
116
115
|
return (
|
|
117
116
|
<SideDialogContext.Provider
|
|
118
117
|
value={{
|
|
@@ -127,13 +126,19 @@ function SideDialogView({
|
|
|
127
126
|
open={Boolean(panel)}
|
|
128
127
|
onOpenChange={(open) => !open && onCloseRequest()}
|
|
129
128
|
title={"Side dialog " + panel?.key}
|
|
129
|
+
// style={{
|
|
130
|
+
// zIndex: offsetPosition * 20 + 20,
|
|
131
|
+
// }}
|
|
132
|
+
// overlayStyle={{
|
|
133
|
+
// zIndex: offsetPosition * 20 + 10,
|
|
134
|
+
// }}
|
|
130
135
|
>
|
|
131
136
|
{panel &&
|
|
132
137
|
<div
|
|
133
138
|
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
139
|
style={{
|
|
135
140
|
width: panel.width,
|
|
136
|
-
transform: `translateX(-${offsetPosition * 200}px)
|
|
141
|
+
transform: `translateX(-${offsetPosition * 200}px)`,
|
|
137
142
|
}}
|
|
138
143
|
>
|
|
139
144
|
<ErrorBoundary>
|
|
@@ -146,7 +151,7 @@ function SideDialogView({
|
|
|
146
151
|
</Sheet>
|
|
147
152
|
|
|
148
153
|
<UnsavedChangesDialog
|
|
149
|
-
open={
|
|
154
|
+
open={drawerCloseRequested}
|
|
150
155
|
handleOk={drawerCloseRequested ? handleDrawerCloseOk : handleNavigationOk}
|
|
151
156
|
handleCancel={drawerCloseRequested ? handleDrawerCloseCancel : handleNavigationCancel}
|
|
152
157
|
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";
|
package/src/hooks/data/save.ts
CHANGED
|
@@ -75,7 +75,7 @@ export async function saveEntityWithCallbacks<M extends Record<string, any>, USE
|
|
|
75
75
|
|
|
76
76
|
const customizationController = context.customizationController;
|
|
77
77
|
|
|
78
|
-
const resolvedPath = context.navigation.
|
|
78
|
+
const resolvedPath = context.navigation.resolveIdsFrom(path);
|
|
79
79
|
|
|
80
80
|
const callbacks = collection.callbacks;
|
|
81
81
|
if (callbacks?.onPreSave) {
|
|
@@ -73,7 +73,7 @@ export function useCollectionFetch<M extends Record<string, any>, USER extends U
|
|
|
73
73
|
const dataSource = useDataSource(collection);
|
|
74
74
|
const navigationController = useNavigationController();
|
|
75
75
|
|
|
76
|
-
const path = navigationController.
|
|
76
|
+
const path = navigationController.resolveIdsFrom(inputPath);
|
|
77
77
|
|
|
78
78
|
const sortByProperty = sortBy ? sortBy[0] : undefined;
|
|
79
79
|
const currentSort = sortBy ? sortBy[1] : undefined;
|
|
@@ -10,6 +10,7 @@ import { useFireCMSContext } from "../useFireCMSContext";
|
|
|
10
10
|
export interface EntityFetchProps<M extends Record<string, any>, USER extends User = User> {
|
|
11
11
|
path: string;
|
|
12
12
|
entityId?: string;
|
|
13
|
+
databaseId?: string;
|
|
13
14
|
collection: EntityCollection<M, USER>;
|
|
14
15
|
useCache?: boolean;
|
|
15
16
|
}
|
|
@@ -40,13 +41,14 @@ export function useEntityFetch<M extends Record<string, any>, USER extends User>
|
|
|
40
41
|
path: inputPath,
|
|
41
42
|
entityId,
|
|
42
43
|
collection,
|
|
44
|
+
databaseId,
|
|
43
45
|
useCache = false
|
|
44
46
|
}: EntityFetchProps<M, USER>): EntityFetchResult<M> {
|
|
45
47
|
|
|
46
48
|
const dataSource = useDataSource(collection);
|
|
47
49
|
const navigationController = useNavigationController();
|
|
48
50
|
|
|
49
|
-
const path = navigationController.
|
|
51
|
+
const path = navigationController.resolveIdsFrom(inputPath);
|
|
50
52
|
|
|
51
53
|
const context: FireCMSContext<USER> = useFireCMSContext();
|
|
52
54
|
|
|
@@ -96,6 +98,7 @@ export function useEntityFetch<M extends Record<string, any>, USER extends User>
|
|
|
96
98
|
return dataSource.listenEntity<M>({
|
|
97
99
|
path,
|
|
98
100
|
entityId,
|
|
101
|
+
databaseId,
|
|
99
102
|
collection,
|
|
100
103
|
onUpdate: onEntityUpdate,
|
|
101
104
|
onError
|
|
@@ -104,6 +107,7 @@ export function useEntityFetch<M extends Record<string, any>, USER extends User>
|
|
|
104
107
|
dataSource.fetchEntity<M>({
|
|
105
108
|
path,
|
|
106
109
|
entityId,
|
|
110
|
+
databaseId,
|
|
107
111
|
collection
|
|
108
112
|
})
|
|
109
113
|
.then(onEntityUpdate)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useContext } from "react";
|
|
2
|
+
import { BreadcrumbContext } from "../contexts/BreacrumbsContext";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @group Hooks and utilities
|
|
6
|
+
*/
|
|
7
|
+
export interface BreadcrumbsController {
|
|
8
|
+
breadcrumbs: BreadcrumbEntry[];
|
|
9
|
+
set: (props: {
|
|
10
|
+
breadcrumbs: BreadcrumbEntry[];
|
|
11
|
+
}) => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @group Hooks and utilities
|
|
16
|
+
*/
|
|
17
|
+
export interface BreadcrumbEntry {
|
|
18
|
+
title: string;
|
|
19
|
+
url: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Hook to retrieve the BreadcrumbsController.
|
|
24
|
+
*
|
|
25
|
+
* Consider that in order to use this hook you need to have a parent
|
|
26
|
+
* `FireCMS`
|
|
27
|
+
*
|
|
28
|
+
* @see BreadcrumbsController
|
|
29
|
+
* @group Hooks and utilities
|
|
30
|
+
*/
|
|
31
|
+
export const useBreadcrumbsController = (): BreadcrumbsController => useContext(BreadcrumbContext);
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
EntityCollection,
|
|
10
10
|
EntityCollectionsBuilder,
|
|
11
11
|
EntityReference,
|
|
12
|
+
NavigationBlocker,
|
|
12
13
|
NavigationController,
|
|
13
14
|
PermissionsBuilder,
|
|
14
15
|
TopNavigationEntry,
|
|
@@ -26,6 +27,7 @@ import {
|
|
|
26
27
|
resolvePermissions
|
|
27
28
|
} from "../util";
|
|
28
29
|
import { getParentReferencesFromPath } from "../util/parent_references_from_path";
|
|
30
|
+
import { useBlocker, useNavigate } from "react-router-dom";
|
|
29
31
|
|
|
30
32
|
const DEFAULT_BASE_PATH = "/";
|
|
31
33
|
const DEFAULT_COLLECTION_PATH = "/c";
|
|
@@ -66,6 +68,8 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
|
|
|
66
68
|
injectCollections
|
|
67
69
|
} = props;
|
|
68
70
|
|
|
71
|
+
const navigate = useNavigate();
|
|
72
|
+
|
|
69
73
|
const collectionsRef = useRef<EntityCollection[] | undefined>();
|
|
70
74
|
const viewsRef = useRef<CMSView[] | undefined>();
|
|
71
75
|
const adminViewsRef = useRef<CMSView[] | undefined>();
|
|
@@ -332,16 +336,7 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
|
|
|
332
336
|
throw Error("Expected path starting with " + fullCollectionPath);
|
|
333
337
|
}, [fullCollectionPath]);
|
|
334
338
|
|
|
335
|
-
const
|
|
336
|
-
path
|
|
337
|
-
}: {
|
|
338
|
-
path: string
|
|
339
|
-
}): string => {
|
|
340
|
-
return `s/edit/${encodePath(path)}`;
|
|
341
|
-
},
|
|
342
|
-
[]);
|
|
343
|
-
|
|
344
|
-
const resolveAliasesFrom = useCallback((path: string): string => {
|
|
339
|
+
const resolveIdsFrom = useCallback((path: string): string => {
|
|
345
340
|
const collections = collectionsRef.current ?? [];
|
|
346
341
|
return resolveCollectionPathIds(path, collections);
|
|
347
342
|
}, []);
|
|
@@ -401,24 +396,16 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
|
|
|
401
396
|
isUrlCollectionPath,
|
|
402
397
|
urlPathToDataPath,
|
|
403
398
|
buildUrlCollectionPath,
|
|
404
|
-
|
|
405
|
-
buildCMSUrlPath,
|
|
406
|
-
resolveAliasesFrom,
|
|
399
|
+
resolveIdsFrom,
|
|
407
400
|
topLevelNavigation,
|
|
408
401
|
refreshNavigation,
|
|
409
402
|
getParentReferencesFromPath: getAllParentReferencesForPath,
|
|
410
403
|
getParentCollectionIds,
|
|
411
|
-
convertIdsToPaths
|
|
404
|
+
convertIdsToPaths,
|
|
405
|
+
navigate,
|
|
412
406
|
};
|
|
413
407
|
}
|
|
414
408
|
|
|
415
|
-
export function getSidePanelKey(path: string, entityId?: string) {
|
|
416
|
-
if (entityId)
|
|
417
|
-
return `${removeInitialAndTrailingSlashes(path)}/${removeInitialAndTrailingSlashes(entityId)}`;
|
|
418
|
-
else
|
|
419
|
-
return removeInitialAndTrailingSlashes(path);
|
|
420
|
-
}
|
|
421
|
-
|
|
422
409
|
function encodePath(input: string) {
|
|
423
410
|
return encodeURIComponent(removeInitialAndTrailingSlashes(input))
|
|
424
411
|
.replaceAll("%2F", "/")
|
|
@@ -513,3 +500,54 @@ function areCollectionsEqual(a: EntityCollection, b: EntityCollection) {
|
|
|
513
500
|
}
|
|
514
501
|
return equal(removeFunctions(restA), removeFunctions(restB));
|
|
515
502
|
}
|
|
503
|
+
|
|
504
|
+
function useCustomBlocker(): NavigationBlocker {
|
|
505
|
+
const [blockListeners, setBlockListeners] = useState<Record<string, {
|
|
506
|
+
block: boolean,
|
|
507
|
+
basePath?: string
|
|
508
|
+
}>>({});
|
|
509
|
+
|
|
510
|
+
const shouldBlock = Object.values(blockListeners).some(b => b.block);
|
|
511
|
+
|
|
512
|
+
let blocker: any;
|
|
513
|
+
try {
|
|
514
|
+
blocker = useBlocker(({
|
|
515
|
+
nextLocation
|
|
516
|
+
}) => {
|
|
517
|
+
const allBasePaths = Object.values(blockListeners).map(b => b.basePath).filter(Boolean) as string[];
|
|
518
|
+
if (allBasePaths && allBasePaths.some(path => nextLocation.pathname.startsWith(path)))
|
|
519
|
+
return false;
|
|
520
|
+
return shouldBlock;
|
|
521
|
+
});
|
|
522
|
+
} catch (e) {
|
|
523
|
+
console.warn("Blocker not available, navigation will not be blocked");
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
const updateBlockListener = (path: string, block: boolean, basePath?: string) => {
|
|
527
|
+
setBlockListeners(prev => ({
|
|
528
|
+
...prev,
|
|
529
|
+
[path]: {
|
|
530
|
+
block,
|
|
531
|
+
basePath
|
|
532
|
+
}
|
|
533
|
+
}));
|
|
534
|
+
return () => setBlockListeners(prev => {
|
|
535
|
+
const {
|
|
536
|
+
[path]: removed,
|
|
537
|
+
...rest
|
|
538
|
+
} = prev;
|
|
539
|
+
return rest;
|
|
540
|
+
})
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
const isBlocked = (path: string) => {
|
|
544
|
+
return (blockListeners[path]?.block ?? false) && blocker?.state === "blocked";
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
return {
|
|
548
|
+
updateBlockListener,
|
|
549
|
+
isBlocked,
|
|
550
|
+
proceed: blocker?.proceed,
|
|
551
|
+
reset: blocker?.reset
|
|
552
|
+
}
|
|
553
|
+
}
|
|
@@ -63,38 +63,3 @@ function checkLargeLayout(breakpoint: "xs" | "sm" | "md" | "lg" | "xl" | "2xl" |
|
|
|
63
63
|
return false;
|
|
64
64
|
return window.matchMedia(`(min-width: ${breakpoints[breakpoint] + 1}px)`).matches;
|
|
65
65
|
}
|
|
66
|
-
|
|
67
|
-
// import { useEffect, useState } from "react";
|
|
68
|
-
//
|
|
69
|
-
// const breakpoints = {
|
|
70
|
-
// xs: 0,
|
|
71
|
-
// sm: 640,
|
|
72
|
-
// md: 768,
|
|
73
|
-
// lg: 1024,
|
|
74
|
-
// xl: 1280,
|
|
75
|
-
// "2xl": 1536,
|
|
76
|
-
// "3xl": 1920
|
|
77
|
-
// }
|
|
78
|
-
// export const useLargeLayout = (breakpoint: "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl" = "lg"): boolean => {
|
|
79
|
-
// const [isLargeLayout, setIsLargeLayout] = useState<boolean>(false);
|
|
80
|
-
//
|
|
81
|
-
// useEffect(() => {
|
|
82
|
-
// const handleResize = () => {
|
|
83
|
-
// const matched = window.matchMedia(`(min-width: ${breakpoints[breakpoint] + 1}px)`).matches;
|
|
84
|
-
// setIsLargeLayout(matched);
|
|
85
|
-
// };
|
|
86
|
-
//
|
|
87
|
-
// // Set initial state
|
|
88
|
-
// handleResize();
|
|
89
|
-
//
|
|
90
|
-
// // Set up event listener for resize events
|
|
91
|
-
// window.addEventListener("resize", handleResize);
|
|
92
|
-
//
|
|
93
|
-
// // Clean up event listener when component unmounts
|
|
94
|
-
// return () => {
|
|
95
|
-
// window.removeEventListener("resize", handleResize);
|
|
96
|
-
// };
|
|
97
|
-
// }, []);
|
|
98
|
-
//
|
|
99
|
-
// return isLargeLayout;
|
|
100
|
-
// };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Entity, EntityCollection, EntityCustomView, FireCMSContext, User } from "../types";
|
|
2
2
|
import { useEffect, useState } from "react";
|
|
3
|
-
import {
|
|
3
|
+
import { getNavigationEntriesFromPath } from "../util/navigation_from_path";
|
|
4
4
|
import { useFireCMSContext } from "./useFireCMSContext";
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -67,10 +67,10 @@ export function resolveNavigationFrom<M extends Record<string, any>, USER extend
|
|
|
67
67
|
const navigation = context.navigation;
|
|
68
68
|
|
|
69
69
|
if (!navigation) {
|
|
70
|
-
throw Error("Calling
|
|
70
|
+
throw Error("Calling resolveNavigationFrom, but main navigation has not yet been initialised");
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
const navigationEntries =
|
|
73
|
+
const navigationEntries = getNavigationEntriesFromPath({
|
|
74
74
|
path,
|
|
75
75
|
collections: navigation.collections ?? []
|
|
76
76
|
});
|
|
@@ -143,9 +143,7 @@ export function useResolvedNavigationFrom<M extends Record<string, any>, USER ex
|
|
|
143
143
|
setDataLoading(true);
|
|
144
144
|
setDataLoadingError(undefined);
|
|
145
145
|
resolveNavigationFrom<M, USER>({ path, context })
|
|
146
|
-
.then(
|
|
147
|
-
setData(res);
|
|
148
|
-
})
|
|
146
|
+
.then(setData)
|
|
149
147
|
.catch((e) => setDataLoadingError(e))
|
|
150
148
|
.finally(() => setDataLoading(false));
|
|
151
149
|
}
|
|
@@ -25,8 +25,9 @@ export function useBuildSideDialogsController(): SideDialogsController {
|
|
|
25
25
|
const newPanels = panelKeys
|
|
26
26
|
.map(key => routesStore.current[key])
|
|
27
27
|
.filter(p => Boolean(p)) as SideDialogPanelProps[];
|
|
28
|
-
if (!equal(sidePanelsRef.current.map(p => p.key), newPanels.map(p => p.key)))
|
|
28
|
+
if (!equal(sidePanelsRef.current.map(p => p.key), newPanels.map(p => p.key))) {
|
|
29
29
|
updateSidePanels(newPanels);
|
|
30
|
+
}
|
|
30
31
|
}, [location]);
|
|
31
32
|
|
|
32
33
|
const close = useCallback(() => {
|
|
@@ -39,7 +40,7 @@ export function useBuildSideDialogsController(): SideDialogsController {
|
|
|
39
40
|
updateSidePanels(updatedPanels);
|
|
40
41
|
|
|
41
42
|
if (routesCount.current > 0) {
|
|
42
|
-
if (lastSidePanel.urlPath)
|
|
43
|
+
if (lastSidePanel.urlPath) // if it has a url path, we need to navigate back, don't remove this code
|
|
43
44
|
navigate(-1);
|
|
44
45
|
routesCount.current--;
|
|
45
46
|
} else if (lastSidePanel.parentUrlPath) {
|
|
@@ -89,6 +90,7 @@ export function useBuildSideDialogsController(): SideDialogsController {
|
|
|
89
90
|
|
|
90
91
|
const replace = useCallback((panelProps: SideDialogPanelProps | SideDialogPanelProps[]) => {
|
|
91
92
|
|
|
93
|
+
console.log("replace", panelProps);
|
|
92
94
|
const newPanels: SideDialogPanelProps[] = Array.isArray(panelProps) ? panelProps : [panelProps];
|
|
93
95
|
newPanels.forEach((panel) => {
|
|
94
96
|
routesStore.current[panel.key] = panel;
|