mirror of
https://github.com/zhigang1992/firecms.git
synced 2026-06-14 09:38:59 +08:00
Refactor of Routes to use React Router 6
This commit is contained in:
@@ -2,7 +2,7 @@ import React from "react";
|
||||
|
||||
import { FirebaseApp } from "firebase/app";
|
||||
import { GoogleAuthProvider } from "firebase/auth";
|
||||
import { ThemeProvider, CssBaseline } from "@mui/material";
|
||||
import { CssBaseline, ThemeProvider } from "@mui/material";
|
||||
import { BrowserRouter as Router } from "react-router-dom";
|
||||
|
||||
import "typeface-rubik";
|
||||
@@ -15,16 +15,17 @@ import {
|
||||
buildSchema,
|
||||
CircularProgressCenter,
|
||||
CMSAppProvider,
|
||||
CMSMainView,
|
||||
CMSRoutes,
|
||||
CMSScaffold,
|
||||
createCMSDefaultTheme,
|
||||
EntityLinkBuilder,
|
||||
FirebaseLoginView,
|
||||
initialiseFirebase,
|
||||
NavigationBuilder,
|
||||
NavigationBuilderProps,
|
||||
useFirebaseAuthHandler,
|
||||
useFirebaseStorageSource,
|
||||
useFirestoreDataSource
|
||||
useFirestoreDataSource,
|
||||
useInitialiseFirebase
|
||||
} from "@camberi/firecms";
|
||||
|
||||
import { firebaseConfig } from "./firebase_config";
|
||||
@@ -95,7 +96,7 @@ export function SimpleAppWithProvider() {
|
||||
firebaseConfigLoading,
|
||||
configError,
|
||||
firebaseConfigError
|
||||
} = initialiseFirebase({ firebaseConfig });
|
||||
} = useInitialiseFirebase({ firebaseConfig });
|
||||
|
||||
if (configError) {
|
||||
return <div> {configError} </div>;
|
||||
@@ -139,7 +140,7 @@ export function SimpleAppWithProvider() {
|
||||
storageSource={storageSource}>
|
||||
{({ context }) => {
|
||||
|
||||
if (context.authController.authLoading) {
|
||||
if (context.authController.authLoading || !context.navigation) {
|
||||
return <CircularProgressCenter/>;
|
||||
}
|
||||
|
||||
@@ -152,11 +153,14 @@ export function SimpleAppWithProvider() {
|
||||
);
|
||||
}
|
||||
|
||||
return <CMSMainView name={"My Online Shop"}/>;
|
||||
return (
|
||||
<CMSScaffold name={"My Online Shop"}>
|
||||
<CMSRoutes navigation={context.navigation}/>
|
||||
</CMSScaffold>);
|
||||
|
||||
}}
|
||||
</CMSAppProvider>
|
||||
</Router>
|
||||
</ThemeProvider>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
18
package.json
18
package.json
@@ -35,15 +35,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@date-io/date-fns": "^1.3.11",
|
||||
"@types/deep-equal": "^1.0.1",
|
||||
"@types/react-measure": "^2.0.6",
|
||||
"@uiw/react-md-editor": "^3.4.10",
|
||||
"date-fns": "^2.21.3",
|
||||
"deep-equal": "^2.0.5",
|
||||
"firebaseui": "~0.600",
|
||||
"formik": "^2.2.6",
|
||||
"history": "^5.0.0",
|
||||
"material-ui-popup-state": "^1.8.3",
|
||||
"object-hash": "^2.1.1",
|
||||
"react-base-table": "^1.12.0",
|
||||
"react-csv": "^2.0.3",
|
||||
@@ -51,8 +47,8 @@
|
||||
"react-dnd-html5-backend": "^14.0.0",
|
||||
"react-dropzone": "^11.3.2",
|
||||
"react-measure": "^2.5.2",
|
||||
"react-router": "^6.0.0-beta.0",
|
||||
"react-router-dom": "^6.0.0-beta.0",
|
||||
"react-router": "^6.0.0-beta.4",
|
||||
"react-router-dom": "^6.0.0-beta.4",
|
||||
"react-transition-group": "^4.4.1",
|
||||
"typeface-roboto": "^1.1.13",
|
||||
"typeface-rubik": "^1.1.13",
|
||||
@@ -63,10 +59,10 @@
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "latest",
|
||||
"@emotion/styled": "latest",
|
||||
"@mui/material": "next",
|
||||
"@mui/styles": "next",
|
||||
"@mui/icons-material": "next",
|
||||
"@mui/lab": "next",
|
||||
"@mui/material": "next",
|
||||
"@mui/styles": "next",
|
||||
"algoliasearch": "^4.9.1",
|
||||
"firebase": "^9.0.0",
|
||||
"react": "^17.0.2",
|
||||
@@ -82,11 +78,13 @@
|
||||
"devDependencies": {
|
||||
"@emotion/react": "latest",
|
||||
"@emotion/styled": "latest",
|
||||
"@mui/material": "next",
|
||||
"@mui/styles": "next",
|
||||
"@mui/icons-material": "next",
|
||||
"@mui/lab": "next",
|
||||
"@mui/material": "next",
|
||||
"@mui/styles": "next",
|
||||
"@rollup/plugin-typescript": "^8.2.1",
|
||||
"@types/deep-equal": "^1.0.1",
|
||||
"@types/react-measure": "^2.0.6",
|
||||
"@types/jest": "^26.0.23",
|
||||
"@types/node": "^15.0.2",
|
||||
"@types/object-hash": "^2.1.0",
|
||||
|
||||
@@ -95,7 +95,7 @@ export const SideEntityProvider: React.FC<SideEntityProviderProps> = ({
|
||||
|
||||
const schemasRegistry = useSchemasRegistry();
|
||||
|
||||
const basePathname = location.state && location.state["base_pathname"] ? location.state["base_pathname"] : location.pathname;
|
||||
const baseLocation = location.state && location.state["base_location"] ? location.state["base_location"] : location;
|
||||
|
||||
useEffect(() => {
|
||||
if (schemasRegistry.initialised) {
|
||||
@@ -194,7 +194,7 @@ export const SideEntityProvider: React.FC<SideEntityProviderProps> = ({
|
||||
{
|
||||
replace: true,
|
||||
state: {
|
||||
base_pathname: basePathname,
|
||||
base_location: baseLocation,
|
||||
panels: [...sidePanels.slice(0, -1), updatedPanel]
|
||||
}
|
||||
}
|
||||
@@ -212,7 +212,7 @@ export const SideEntityProvider: React.FC<SideEntityProviderProps> = ({
|
||||
newPath,
|
||||
{
|
||||
state: {
|
||||
base_pathname: basePathname,
|
||||
base_location: baseLocation,
|
||||
panels: [...sidePanels, newPanel]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { Navigation } from "../models";
|
||||
import { addInitialSlash, buildCollectionUrlPath } from "./navigation";
|
||||
|
||||
import CollectionRoute from "./internal/CollectionRoute";
|
||||
import CMSViewRoute from "./internal/CMSViewRoute";
|
||||
import HomeRoute from "./internal/HomeRoute";
|
||||
|
||||
export function CMSRouterSwitch({ navigation }: {
|
||||
navigation: Navigation
|
||||
}) {
|
||||
|
||||
const location = useLocation();
|
||||
const basePathname = location.state && location.state["base_pathname"] ? location.state["base_pathname"] : location.pathname;
|
||||
|
||||
if (addInitialSlash(basePathname) === "/")
|
||||
return <HomeRoute navigation={navigation}/>;
|
||||
|
||||
|
||||
const matchedCollection = [...navigation.collections]
|
||||
// we reorder collections so that nested paths are included first
|
||||
.sort((a, b) => b.relativePath.length - a.relativePath.length)
|
||||
.find(entityCollection => {
|
||||
return addInitialSlash(buildCollectionUrlPath(entityCollection)) === addInitialSlash(basePathname);
|
||||
});
|
||||
|
||||
if (matchedCollection) {
|
||||
return <CollectionRoute
|
||||
key={`col_${matchedCollection.relativePath}`}
|
||||
path={matchedCollection.relativePath}
|
||||
collectionConfig={matchedCollection}
|
||||
/>;
|
||||
}
|
||||
if (navigation.views) {
|
||||
const matchedView = [...navigation.views].find(view => {
|
||||
if (Array.isArray(view.path)) {
|
||||
const matchedPath = view.path.find((p) => addInitialSlash(p) === addInitialSlash(basePathname));
|
||||
return matchedPath !== undefined;
|
||||
} else {
|
||||
return addInitialSlash(view.path) === addInitialSlash(basePathname);
|
||||
}
|
||||
});
|
||||
if (matchedView) {
|
||||
return <CMSViewRoute cmsView={matchedView}/>;
|
||||
}
|
||||
}
|
||||
return <></>;
|
||||
|
||||
// TODO: the following implementation relies on being able to override the location
|
||||
// TODO: in react-router
|
||||
// const buildCMSViewRoute = (path: string, cmsView: CMSView) => {
|
||||
// return <Route
|
||||
// key={"navigation_view_" + path}
|
||||
// path={addInitialSlash(path)}
|
||||
// >
|
||||
// <CMSViewRoute cmsView={cmsView}/>
|
||||
// </Route>;
|
||||
// };
|
||||
//
|
||||
// let customRoutes: JSX.Element[] = [];
|
||||
// if (navigation.views) {
|
||||
// navigation.views.forEach((cmsView) => {
|
||||
// if (Array.isArray(cmsView.path))
|
||||
// customRoutes.push(...cmsView.path.map(path => buildCMSViewRoute(path, cmsView)));
|
||||
// else
|
||||
// customRoutes.push(buildCMSViewRoute(cmsView.path, cmsView));
|
||||
// });
|
||||
// }
|
||||
// const collectionRoutes = [...collections]
|
||||
// // we reorder collections so that nested paths are included first
|
||||
// .sort((a, b) => b.relativePath.length - a.relativePath.length)
|
||||
// .map(entityCollection => (
|
||||
// <Route
|
||||
// path={buildCollectionUrlPath(entityCollection)}
|
||||
// key={`navigation_${entityCollection.relativePath}`}>
|
||||
// <CollectionRoute
|
||||
// path={entityCollection.relativePath}
|
||||
// collectionConfig={entityCollection}
|
||||
// />
|
||||
// </Route>
|
||||
// )
|
||||
// );
|
||||
// return (
|
||||
// <Routes>
|
||||
//
|
||||
// {collectionRoutes}
|
||||
//
|
||||
// {customRoutes}
|
||||
//
|
||||
// <Route
|
||||
// key={`navigation_home`}>
|
||||
// <HomeRoute
|
||||
// collections={collections}
|
||||
// cmsViews={views}
|
||||
// />
|
||||
// </Route>
|
||||
//
|
||||
// </Routes>
|
||||
// );
|
||||
}
|
||||
97
src/core/CMSRoutes.tsx
Normal file
97
src/core/CMSRoutes.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
import React from "react";
|
||||
|
||||
import { Route, Routes, useLocation } from "react-router-dom";
|
||||
import { CMSView, Navigation } from "../models";
|
||||
import { addInitialSlash, buildCollectionUrlPath } from "./navigation";
|
||||
import { EntityCollectionTable } from "./components/EntityCollectionTable";
|
||||
import BreadcrumbUpdater from "./components/BreadcrumbUpdater";
|
||||
import CMSHome from "./components/CMSHome";
|
||||
|
||||
/**
|
||||
* This component is in charge of taking a {@link Navigation} and rendering
|
||||
* all the related routes (entity collection root views, custom views
|
||||
* or the home route).
|
||||
*
|
||||
* @param navigation
|
||||
* @constructor
|
||||
*/
|
||||
export function CMSRoutes({ navigation }: {
|
||||
navigation: Navigation
|
||||
}) {
|
||||
|
||||
const location = useLocation();
|
||||
/**
|
||||
* The location can be overridden if `base_location` is set in the
|
||||
* state field of the current location. This can happen if you open
|
||||
* a side entity, like `products`, from a different one, like `users`
|
||||
*/
|
||||
const baseLocation = location.state && location.state["base_location"] ? location.state["base_location"] : location;
|
||||
|
||||
const customRoutes: JSX.Element[] = [];
|
||||
if (navigation.views) {
|
||||
const buildCMSViewRoute = (path: string, cmsView: CMSView) => {
|
||||
return <Route
|
||||
key={"navigation_view_" + path}
|
||||
path={addInitialSlash(path)}
|
||||
element={
|
||||
<BreadcrumbUpdater
|
||||
path={addInitialSlash(path)}
|
||||
key={`navigation_${path}`}
|
||||
title={cmsView.name}>
|
||||
{cmsView.view}
|
||||
</BreadcrumbUpdater>}
|
||||
/>;
|
||||
};
|
||||
|
||||
navigation.views.forEach((cmsView) => {
|
||||
if (Array.isArray(cmsView.path))
|
||||
customRoutes.push(...cmsView.path.map(path => buildCMSViewRoute(path, cmsView)));
|
||||
else
|
||||
customRoutes.push(buildCMSViewRoute(cmsView.path, cmsView));
|
||||
});
|
||||
}
|
||||
|
||||
const collectionRoutes = [...navigation.collections]
|
||||
// we reorder collections so that nested paths are included first
|
||||
.sort((a, b) => b.relativePath.length - a.relativePath.length)
|
||||
.map(entityCollection => {
|
||||
const urlPath = buildCollectionUrlPath(entityCollection);
|
||||
return (
|
||||
<Route path={urlPath}
|
||||
element={
|
||||
<BreadcrumbUpdater
|
||||
path={urlPath}
|
||||
key={`navigation_${entityCollection.relativePath}`}
|
||||
title={entityCollection.name}>
|
||||
<EntityCollectionTable
|
||||
path={entityCollection.relativePath}
|
||||
collectionConfig={entityCollection}/>
|
||||
</BreadcrumbUpdater>
|
||||
}/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
const homeRoute =
|
||||
<Route path={"/"}
|
||||
element={
|
||||
<BreadcrumbUpdater
|
||||
path={"/"}
|
||||
key={`navigation_home`}
|
||||
title={"Home"}>
|
||||
<CMSHome navigation={navigation}/>
|
||||
</BreadcrumbUpdater>
|
||||
}/>;
|
||||
|
||||
return (
|
||||
<Routes location={baseLocation}>
|
||||
|
||||
{collectionRoutes}
|
||||
|
||||
{customRoutes}
|
||||
|
||||
{homeRoute}
|
||||
|
||||
</Routes>
|
||||
);
|
||||
}
|
||||
@@ -20,7 +20,7 @@ import * as locales from "date-fns/locale";
|
||||
/**
|
||||
* @category Core
|
||||
*/
|
||||
export interface CMSMainViewProps {
|
||||
export interface CMSScaffoldProps {
|
||||
|
||||
/**
|
||||
* Name of the app, displayed as the main title and in the tab title
|
||||
@@ -32,12 +32,6 @@ export interface CMSMainViewProps {
|
||||
*/
|
||||
logo?: string;
|
||||
|
||||
/**
|
||||
* If authentication is enabled, allow the user to access the content
|
||||
* without login.
|
||||
*/
|
||||
allowSkipLogin?: boolean;
|
||||
|
||||
/**
|
||||
* A component that gets rendered on the upper side of the main toolbar
|
||||
*/
|
||||
@@ -92,7 +86,7 @@ const useStyles = makeStyles((theme: Theme) =>
|
||||
* @constructor
|
||||
* @category Core
|
||||
*/
|
||||
export function CMSMainView(props: PropsWithChildren<CMSMainViewProps>) {
|
||||
export function CMSScaffold(props: PropsWithChildren<CMSScaffoldProps>) {
|
||||
|
||||
const {
|
||||
children,
|
||||
37
src/core/components/BreadcrumbUpdater.tsx
Normal file
37
src/core/components/BreadcrumbUpdater.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import React, { PropsWithChildren } from "react";
|
||||
import { useBreadcrumbsContext } from "../../contexts";
|
||||
import { EntityCollectionTable } from "./EntityCollectionTable";
|
||||
|
||||
export type BreadcrumbRouteProps = {
|
||||
title: string;
|
||||
path: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* This component updates the breadcrumb in the app bar when rendered
|
||||
* @param children
|
||||
* @param title
|
||||
* @param path
|
||||
* @constructor
|
||||
*/
|
||||
function BreadcrumbUpdater({
|
||||
children,
|
||||
title,
|
||||
path
|
||||
}
|
||||
: PropsWithChildren<BreadcrumbRouteProps>) {
|
||||
|
||||
const breadcrumbsContext = useBreadcrumbsContext();
|
||||
React.useEffect(() => {
|
||||
breadcrumbsContext.set({
|
||||
breadcrumbs: [{
|
||||
title: title,
|
||||
url: path
|
||||
}]
|
||||
});
|
||||
}, [path]);
|
||||
|
||||
return <> {children}</>;
|
||||
}
|
||||
|
||||
export default BreadcrumbUpdater;
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect } from "react";
|
||||
import React from "react";
|
||||
import {
|
||||
Box,
|
||||
Card,
|
||||
@@ -16,14 +16,9 @@ import createStyles from "@mui/styles/createStyles";
|
||||
import makeStyles from "@mui/styles/makeStyles";
|
||||
import ArrowForwardIcon from "@mui/icons-material/ArrowForward";
|
||||
import PlaylistPlayIcon from "@mui/icons-material/PlaylistPlay";
|
||||
import { Link as ReactLink, useLocation } from "react-router-dom";
|
||||
import { Link as ReactLink } from "react-router-dom";
|
||||
|
||||
import {
|
||||
BreadcrumbEntry,
|
||||
computeNavigation,
|
||||
NavigationEntry
|
||||
} from "../navigation";
|
||||
import { useBreadcrumbsContext } from "../../contexts";
|
||||
import { computeNavigation, NavigationEntry } from "../navigation";
|
||||
import { Navigation } from "../../models";
|
||||
import { Markdown } from "../../preview";
|
||||
|
||||
@@ -41,30 +36,21 @@ export const useStyles = makeStyles((theme: Theme) =>
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
interface HomeRouteProps {
|
||||
export interface CMSHomeProps {
|
||||
navigation: Navigation;
|
||||
}
|
||||
|
||||
function HomeRoute({
|
||||
/**
|
||||
* Default main view and entry point for the CMS
|
||||
* This components takes navigation as an input and renders
|
||||
* @param navigation
|
||||
* @constructor
|
||||
*/
|
||||
function CMSHome({
|
||||
navigation
|
||||
}: HomeRouteProps) {
|
||||
}: CMSHomeProps) {
|
||||
|
||||
const classes = useStyles();
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const breadcrumb: BreadcrumbEntry = {
|
||||
title: "Home",
|
||||
url: pathname
|
||||
};
|
||||
|
||||
const breadcrumbsContext = useBreadcrumbsContext();
|
||||
|
||||
useEffect(() => {
|
||||
breadcrumbsContext.set({
|
||||
breadcrumbs: [breadcrumb]
|
||||
});
|
||||
}, [pathname]);
|
||||
|
||||
const {
|
||||
navigationEntries,
|
||||
@@ -114,7 +100,6 @@ function HomeRoute({
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<Container>
|
||||
{allGroups.map((group, index) => (
|
||||
@@ -145,4 +130,4 @@ function HomeRoute({
|
||||
);
|
||||
}
|
||||
|
||||
export default HomeRoute;
|
||||
export default CMSHome;
|
||||
@@ -1,3 +1,5 @@
|
||||
import { CMSHomeProps } from "./CMSHome";
|
||||
|
||||
export type { ErrorViewProps } from "./ErrorView";
|
||||
export {
|
||||
default as ErrorView
|
||||
@@ -22,8 +24,12 @@ export {
|
||||
default as CircularProgressCenter
|
||||
} from "./CircularProgressCenter";
|
||||
|
||||
export type { FirebaseLoginViewProps } from "./FirebaseLoginView";
|
||||
export type { CMSHomeProps } from "./CMSHome";
|
||||
export {
|
||||
default as FirebaseLoginView
|
||||
} from "./FirebaseLoginView";
|
||||
default as CMSHome
|
||||
} from "./CMSHome";
|
||||
export {
|
||||
default as BreadcrumbUpdater
|
||||
} from "./BreadcrumbUpdater";
|
||||
|
||||
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
|
||||
|
||||
export { CMSAppProvider } from "./CMSAppProvider";
|
||||
export type {
|
||||
CMSAppProviderProps
|
||||
} from "./CMSAppProvider";
|
||||
|
||||
export { CMSMainView } from "./CMSMainView";
|
||||
export { CMSRoutes } from "./CMSRoutes";
|
||||
|
||||
export { CMSScaffold } from "./CMSScaffold";
|
||||
export type {
|
||||
CMSMainViewProps
|
||||
} from "./CMSMainView";
|
||||
CMSScaffoldProps
|
||||
} from "./CMSScaffold";
|
||||
|
||||
export * from "./components";
|
||||
|
||||
export { initialiseFirebase } from "./initialiseFirebase";
|
||||
|
||||
export { createCMSDefaultTheme } from "./theme";
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { BreadcrumbEntry } from "../navigation";
|
||||
import { useBreadcrumbsContext } from "../../contexts";
|
||||
import { CMSView } from "../../models";
|
||||
|
||||
|
||||
interface CMSViewRouteProps {
|
||||
cmsView: CMSView;
|
||||
}
|
||||
|
||||
function CMSViewRoute({
|
||||
cmsView
|
||||
}: CMSViewRouteProps) {
|
||||
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const breadcrumb: BreadcrumbEntry = {
|
||||
title: cmsView.name,
|
||||
url: pathname
|
||||
};
|
||||
|
||||
const breadcrumbsContext = useBreadcrumbsContext();
|
||||
|
||||
useEffect(() => {
|
||||
breadcrumbsContext.set({
|
||||
breadcrumbs: [breadcrumb]
|
||||
});
|
||||
}, [pathname]);
|
||||
|
||||
return <React.Fragment>
|
||||
{cmsView.view}
|
||||
</React.Fragment>;
|
||||
}
|
||||
|
||||
export default CMSViewRoute;
|
||||
@@ -1,54 +0,0 @@
|
||||
import React from "react";
|
||||
import { EntityCollection } from "../../models";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import makeStyles from "@mui/styles/makeStyles";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { useBreadcrumbsContext } from "../../contexts";
|
||||
import { EntityCollectionTable } from "../components/EntityCollectionTable";
|
||||
|
||||
export const useStyles = makeStyles(() =>
|
||||
createStyles({
|
||||
root: {
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column"
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
interface CollectionRouteProps<M extends { [Key: string]: any }> {
|
||||
collectionConfig: EntityCollection<M>;
|
||||
path: string
|
||||
}
|
||||
|
||||
function CollectionRoute<M extends { [Key: string]: any }>({
|
||||
collectionConfig,
|
||||
path
|
||||
}
|
||||
: CollectionRouteProps<M>) {
|
||||
|
||||
const { pathname } = useLocation();
|
||||
const breadcrumbsContext = useBreadcrumbsContext();
|
||||
React.useEffect(() => {
|
||||
breadcrumbsContext.set({
|
||||
breadcrumbs: [{
|
||||
title: collectionConfig.name,
|
||||
url: pathname
|
||||
}]
|
||||
});
|
||||
}, [pathname]);
|
||||
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
|
||||
<EntityCollectionTable
|
||||
path={path}
|
||||
collectionConfig={collectionConfig}/>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default CollectionRoute;
|
||||
@@ -38,10 +38,6 @@ export function buildCollectionUrlPath(view: EntityCollection) {
|
||||
return `${DATA_PATH}/${removeInitialAndTrailingSlashes(view.relativePath)}`;
|
||||
}
|
||||
|
||||
export function buildCollectionPath(view: EntityCollection) {
|
||||
return `${DATA_PATH}/${removeInitialAndTrailingSlashes(view.relativePath)}`;
|
||||
}
|
||||
|
||||
export function removeInitialAndTrailingSlashes(s: string) {
|
||||
return removeInitialSlash(removeTrailingSlash(s));
|
||||
}
|
||||
@@ -243,6 +239,7 @@ export function computeNavigation(navigation:Navigation, includeHiddenViews: boo
|
||||
...navigation.collections.map(collection => ({
|
||||
url: buildCollectionUrlPath(collection),
|
||||
name: collection.name,
|
||||
description: collection.description,
|
||||
group: collection.group
|
||||
})),
|
||||
...(navigation.views ?? []).map(view =>
|
||||
|
||||
@@ -2,28 +2,29 @@ import React from "react";
|
||||
|
||||
import { GoogleAuthProvider } from "firebase/auth";
|
||||
import { CssBaseline, ThemeProvider } from "@mui/material";
|
||||
import { BrowserRouter as Router } from "react-router-dom";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
|
||||
import {
|
||||
CircularProgressCenter,
|
||||
CMSAppProvider,
|
||||
CMSScaffold,
|
||||
CMSRoutes,
|
||||
createCMSDefaultTheme
|
||||
} from "../core";
|
||||
import { AuthController } from "../contexts";
|
||||
import { EntityLinkBuilder } from "../models";
|
||||
|
||||
import { CMSAppProps } from "./CMSAppProps";
|
||||
import {
|
||||
CMSAppProvider,
|
||||
CMSMainView,
|
||||
createCMSDefaultTheme,
|
||||
initialiseFirebase
|
||||
} from "../core";
|
||||
import { CMSRouterSwitch } from "../core/CMSRouterSwitch";
|
||||
import { CircularProgressCenter, FirebaseLoginView } from "../core/components";
|
||||
import { AuthController } from "../contexts";
|
||||
import { useFirebaseAuthHandler } from "./useFirebaseAuthHandler";
|
||||
import { EntityLinkBuilder } from "../models";
|
||||
import { useFirestoreDataSource } from "./useFirestoreDataSource";
|
||||
import { useFirebaseStorageSource } from "./useFirebaseStorageSource";
|
||||
import { useInitialiseFirebase } from "./useInitialiseFirebase";
|
||||
import FirebaseLoginView from "./FirebaseLoginView";
|
||||
|
||||
const DEFAULT_SIGN_IN_OPTIONS = [
|
||||
GoogleAuthProvider.PROVIDER_ID
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Main entry point for FireCMS. You can use this component as a full app,
|
||||
* by specifying collections and entity schemas.
|
||||
@@ -32,7 +33,7 @@ const DEFAULT_SIGN_IN_OPTIONS = [
|
||||
* configuration object.
|
||||
*
|
||||
* If you are building a larger app and need finer control, you can use
|
||||
* {@link CMSAppProvider} and {@link CMSMainView} instead.
|
||||
* {@link CMSAppProvider} and {@link CMSScaffold} instead.
|
||||
*
|
||||
* @param props
|
||||
* @constructor
|
||||
@@ -62,7 +63,7 @@ export function CMSApp({
|
||||
firebaseConfigLoading,
|
||||
configError,
|
||||
firebaseConfigError
|
||||
} = initialiseFirebase({ onFirebaseInit, firebaseConfig });
|
||||
} = useInitialiseFirebase({ onFirebaseInit, firebaseConfig });
|
||||
|
||||
const authController: AuthController = useFirebaseAuthHandler({
|
||||
firebaseApp,
|
||||
@@ -86,7 +87,10 @@ export function CMSApp({
|
||||
return <CircularProgressCenter/>;
|
||||
}
|
||||
|
||||
const dataSource = useFirestoreDataSource({ firebaseApp: firebaseApp! , textSearchDelegateResolver});
|
||||
const dataSource = useFirestoreDataSource({
|
||||
firebaseApp: firebaseApp!,
|
||||
textSearchDelegateResolver
|
||||
});
|
||||
const storageSource = useFirebaseStorageSource({ firebaseApp: firebaseApp! });
|
||||
|
||||
const mode: "light" | "dark" = "light";
|
||||
@@ -100,9 +104,11 @@ export function CMSApp({
|
||||
const entityLinkBuilder: EntityLinkBuilder = ({ entity }) => `https://console.firebase.google.com/project/${firebaseApp.options.projectId}/firestore/data/${entity.path}/${entity.id}`;
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline/>
|
||||
<Router>
|
||||
<BrowserRouter>
|
||||
<ThemeProvider theme={theme}>
|
||||
|
||||
<CssBaseline/>
|
||||
|
||||
<CMSAppProvider navigation={navigation}
|
||||
authController={authController}
|
||||
schemaResolver={schemaResolver}
|
||||
@@ -127,14 +133,16 @@ export function CMSApp({
|
||||
);
|
||||
}
|
||||
|
||||
return <CMSMainView name={name}
|
||||
logo={logo}
|
||||
toolbarExtraWidget={toolbarExtraWidget}>
|
||||
<CMSRouterSwitch navigation={context.navigation}/>
|
||||
</CMSMainView>;
|
||||
return (
|
||||
<CMSScaffold name={name}
|
||||
logo={logo}
|
||||
toolbarExtraWidget={toolbarExtraWidget}>
|
||||
<CMSRoutes navigation={context.navigation}/>
|
||||
</CMSScaffold>
|
||||
);
|
||||
}}
|
||||
</CMSAppProvider>
|
||||
</Router>
|
||||
</ThemeProvider>
|
||||
);
|
||||
</ThemeProvider>
|
||||
</BrowserRouter>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
NavigationBuilder,
|
||||
SchemaResolver
|
||||
} from "../models";
|
||||
import { TextSearchDelegateResolver } from "./text_search_delegate";
|
||||
import { TextSearchDelegateResolver } from "./text_search";
|
||||
|
||||
/**
|
||||
* Main entry point that defines the CMS configuration
|
||||
|
||||
@@ -6,7 +6,7 @@ import makeStyles from "@mui/styles/makeStyles";
|
||||
|
||||
import firebase from "firebase/compat/app";
|
||||
|
||||
import { useAuthController } from "../../contexts";
|
||||
import { useAuthController } from "../contexts";
|
||||
|
||||
import * as firebaseui from "firebaseui";
|
||||
import "firebaseui/dist/firebaseui.css";
|
||||
@@ -1,3 +1,4 @@
|
||||
import { InitialiseFirebaseResult } from "./useInitialiseFirebase";
|
||||
|
||||
export { CMSApp } from "./CMSApp";
|
||||
export type {
|
||||
@@ -20,3 +21,11 @@ export {
|
||||
export type { TextSearchDelegateResolver } from "./text_search";
|
||||
export { performAlgoliaTextSearch } from "./text_search";
|
||||
|
||||
export type { FirebaseLoginViewProps } from "./FirebaseLoginView";
|
||||
export {
|
||||
default as FirebaseLoginView
|
||||
} from "./FirebaseLoginView";
|
||||
|
||||
export type { InitialiseFirebaseResult } from "./useInitialiseFirebase";
|
||||
export { useInitialiseFirebase } from "./useInitialiseFirebase";
|
||||
|
||||
|
||||
@@ -45,7 +45,8 @@ import {
|
||||
where as whereClause
|
||||
} from "firebase/firestore";
|
||||
import { FirebaseApp } from "firebase/app";
|
||||
import { TextSearchDelegateResolver } from "./text_search_delegate";
|
||||
import { TextSearchDelegateResolver } from "./text_search";
|
||||
import firebase from "firebase/compat/app";
|
||||
|
||||
export type FirestoreDataSourceProps = {
|
||||
firebaseApp: FirebaseApp,
|
||||
@@ -365,15 +366,8 @@ export function useFirestoreDataSource({
|
||||
}: SaveEntityProps<M>): Promise<void> {
|
||||
|
||||
const properties: Properties<M> = computeSchemaProperties(schema, path, entityId);
|
||||
let updatedValues: EntityValues<M> = updateAutoValues(
|
||||
{
|
||||
inputValues: values,
|
||||
properties,
|
||||
status,
|
||||
timestampNowValue: serverTimestamp(),
|
||||
referenceConverter: (value: EntityReference) => doc(db, value.path, value.id),
|
||||
geopointConverter: (value: GeoPoint) => new GeoPoint(value.latitude, value.longitude)
|
||||
});
|
||||
|
||||
let updatedValues: EntityValues<M>;
|
||||
|
||||
if (schema.onPreSave) {
|
||||
try {
|
||||
@@ -381,7 +375,7 @@ export function useFirestoreDataSource({
|
||||
schema,
|
||||
path,
|
||||
entityId,
|
||||
values: updatedValues,
|
||||
values,
|
||||
status,
|
||||
context
|
||||
});
|
||||
@@ -393,6 +387,17 @@ export function useFirestoreDataSource({
|
||||
}
|
||||
}
|
||||
|
||||
updatedValues = updateAutoValues(
|
||||
{
|
||||
inputValues: values,
|
||||
properties,
|
||||
status,
|
||||
timestampNowValue: serverTimestamp(),
|
||||
referenceConverter: (value: EntityReference) => doc(db, value.path, value.id),
|
||||
geopointConverter: (value: GeoPoint) => new firebase.firestore.GeoPoint(value.latitude, value.longitude)
|
||||
});
|
||||
|
||||
|
||||
console.debug("Saving entity", path, entityId, updatedValues);
|
||||
|
||||
let documentReference: DocumentReference;
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useEffect } from "react";
|
||||
|
||||
import { FirebaseApp, initializeApp } from "firebase/app";
|
||||
|
||||
export interface CMSFirebaseInitResult {
|
||||
export interface InitialiseFirebaseResult {
|
||||
firebaseConfigLoading: boolean,
|
||||
firebaseApp?: FirebaseApp;
|
||||
configError?: string,
|
||||
@@ -24,10 +24,10 @@ export interface CMSFirebaseInitResult {
|
||||
* @param firebaseConfig
|
||||
* @category Hooks and utilities Functions
|
||||
*/
|
||||
export function initialiseFirebase({ firebaseConfig, onFirebaseInit }: {
|
||||
export function useInitialiseFirebase({ firebaseConfig, onFirebaseInit }: {
|
||||
onFirebaseInit?: ((config: object) => void) | undefined,
|
||||
firebaseConfig: Object | undefined
|
||||
}): CMSFirebaseInitResult {
|
||||
}): InitialiseFirebaseResult {
|
||||
|
||||
const [firebaseApp, setFirebaseApp] = React.useState<FirebaseApp | undefined>();
|
||||
const [firebaseConfigLoading, setFirebaseConfigLoading] = React.useState<boolean>(false);
|
||||
@@ -210,7 +210,7 @@ export interface EntityOnSaveProps<M extends { [Key: string]: any }> {
|
||||
/**
|
||||
* Values being saved
|
||||
*/
|
||||
values: EntityValues<M>;
|
||||
values: Partial<EntityValues<M>>;
|
||||
|
||||
/**
|
||||
* New or existing entity
|
||||
|
||||
@@ -6,10 +6,8 @@ it("Test Algolia search", async () => {
|
||||
const client = algoliasearch(
|
||||
"Y6FR1MDSVW",
|
||||
"f084e6dcc154c04295c8124dbb797ff1");
|
||||
const usersIndex = client.initIndex("users");
|
||||
|
||||
await performAlgoliaTextSearch(
|
||||
client,
|
||||
"users",
|
||||
"john").then(console.log);
|
||||
await performAlgoliaTextSearch(usersIndex, "john").then(console.log);
|
||||
console.log("done");
|
||||
});
|
||||
|
||||
47
yarn.lock
47
yarn.lock
@@ -1991,7 +1991,7 @@
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/runtime@^7.13.10", "@babel/runtime@^7.14.8", "@babel/runtime@^7.7.6":
|
||||
"@babel/runtime@^7.13.10", "@babel/runtime@^7.14.8":
|
||||
version "7.15.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.3.tgz#2e1c2880ca118e5b2f9988322bd8a7656a32502b"
|
||||
integrity sha512-OvwMLqNXkCXSz1kSm58sEsNuhqOx/fKpnUnKnFB5v8uDda5bLNEHNgKPvhDN6IU0LDcnHQ90LlJ0Q6jnyBSIBA==
|
||||
@@ -2893,11 +2893,6 @@
|
||||
refractor "^3.3.1"
|
||||
unist-util-visit "^2.0.3"
|
||||
|
||||
"@material-ui/types@^6.0.1":
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@material-ui/types/-/types-6.0.1.tgz#bfcdcd3bb5091e5feac2b191db516543d84e26af"
|
||||
integrity sha512-t53C2BZE59e8ao38EDIZdM2smPDSEo5Xx9XxQ/MNM9Ph63Mu4vj5pmECiXkYp0y2OrvFiiZhcqRWV34SBOA18g==
|
||||
|
||||
"@mui/core@5.0.0-alpha.46":
|
||||
version "5.0.0-alpha.46"
|
||||
resolved "https://registry.yarnpkg.com/@mui/core/-/core-5.0.0-alpha.46.tgz#f4c0e5b2ad346e31e74bb96b684f6734b55cc9e6"
|
||||
@@ -5383,7 +5378,7 @@ class-utils@^0.3.5:
|
||||
isobject "^3.0.0"
|
||||
static-extend "^0.1.1"
|
||||
|
||||
classnames@^2.2.5, classnames@^2.2.6:
|
||||
classnames@^2.2.5:
|
||||
version "2.2.6"
|
||||
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
|
||||
integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==
|
||||
@@ -8320,13 +8315,6 @@ hex-color-regex@^1.1.0:
|
||||
resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
|
||||
integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==
|
||||
|
||||
history@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/history/-/history-5.0.0.tgz#0cabbb6c4bbf835addb874f8259f6d25101efd08"
|
||||
integrity sha512-3NyRMKIiFSJmIPdq7FxkNMJkQ7ZEtVblOQ38VtKaA0zZMW1Eo6Q6W8oDKEflr1kNNTItSnk4JMCO1deeSgbLLg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.7.6"
|
||||
|
||||
hmac-drbg@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
|
||||
@@ -10370,16 +10358,6 @@ material-design-lite@^1.2.0:
|
||||
resolved "https://registry.yarnpkg.com/material-design-lite/-/material-design-lite-1.3.0.tgz#d004ce3fee99a1eeb74a78b8a325134a5f1171d3"
|
||||
integrity sha1-0ATOP+6Zoe63Sni4oyUTSl8RcdM=
|
||||
|
||||
material-ui-popup-state@^1.8.3:
|
||||
version "1.9.0"
|
||||
resolved "https://registry.yarnpkg.com/material-ui-popup-state/-/material-ui-popup-state-1.9.0.tgz#569d173c941c41ba32791ea759ff21b1a84d70fd"
|
||||
integrity sha512-a0OqcXgicrYYKMoBXuajjT94uARgl7ozt1gs735Xb+UmEtanBxjavtoOVo+C4pfVRCHOVamJg+1H48zhOrGd/w==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.12.5"
|
||||
"@material-ui/types" "^6.0.1"
|
||||
classnames "^2.2.6"
|
||||
prop-types "^15.7.2"
|
||||
|
||||
maxmin@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/maxmin/-/maxmin-2.1.0.tgz#4d3b220903d95eee7eb7ac7fa864e72dc09a3166"
|
||||
@@ -12982,20 +12960,17 @@ react-refresh@^0.8.3:
|
||||
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f"
|
||||
integrity sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg==
|
||||
|
||||
react-router-dom@^6.0.0-beta.0:
|
||||
version "6.0.0-beta.0"
|
||||
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.0.0-beta.0.tgz#9dcc8555365f22f7fbd09f26b6b82543f3eb97d6"
|
||||
integrity sha512-36yNNGMT8RB9FRPL9nKJi6HKDkgOakU+o/2hHpSzR6e37gN70MpOU6QQlmif4oAWWBwjyGc3ZNOMFCsFuHUY5w==
|
||||
react-router-dom@^6.0.0-beta.4:
|
||||
version "6.0.0-beta.4"
|
||||
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.0.0-beta.4.tgz#9cc9fb57bca3d5548b297ae8101efcdcc68dd903"
|
||||
integrity sha512-yppoRR8S7FxNMG6OGFn9DPJBPLsjeIuQa7GYnkRlgOSTBSPhAQpD6z7rvRzZnesQ9r6W+ibD9RvnqTcna4w/Pg==
|
||||
dependencies:
|
||||
prop-types "^15.7.2"
|
||||
react-router "6.0.0-beta.0"
|
||||
react-router "6.0.0-beta.4"
|
||||
|
||||
react-router@6.0.0-beta.0, react-router@^6.0.0-beta.0:
|
||||
version "6.0.0-beta.0"
|
||||
resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.0.0-beta.0.tgz#3e11f39b6ded4412c2fed9e4f989dd4c8156724d"
|
||||
integrity sha512-VgMdfpVcmFQki/LZuLh8E/MNACekDetz4xqft+a6fBZvvJnVqKbLqebF7hyoawGrV1HcO5tVaUang2Og4W2j1Q==
|
||||
dependencies:
|
||||
prop-types "^15.7.2"
|
||||
react-router@6.0.0-beta.4, react-router@^6.0.0-beta.4:
|
||||
version "6.0.0-beta.4"
|
||||
resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.0.0-beta.4.tgz#e35a440b36fd07a2ac854e1f1f176b56a777793a"
|
||||
integrity sha512-48JRRZJOwmfOKM12Tv3WjmNkVb2NpSEgDTWC4+MEJaud0+eyLJxqy62CKzOydMlk7hrfRFpC/elUYSZAuyx3qA==
|
||||
|
||||
react-scripts@^4.0.3:
|
||||
version "4.0.3"
|
||||
|
||||
Reference in New Issue
Block a user