diff --git a/example/src/SimpleAppWithProvider.tsx b/example/src/SimpleAppWithProvider.tsx index dadd8ff..b8ead03 100644 --- a/example/src/SimpleAppWithProvider.tsx +++ b/example/src/SimpleAppWithProvider.tsx @@ -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
{configError}
; @@ -139,7 +140,7 @@ export function SimpleAppWithProvider() { storageSource={storageSource}> {({ context }) => { - if (context.authController.authLoading) { + if (context.authController.authLoading || !context.navigation) { return ; } @@ -152,11 +153,14 @@ export function SimpleAppWithProvider() { ); } - return ; + return ( + + + ); }} - ); +); } diff --git a/package.json b/package.json index 9dc87c6..29f28d6 100644 --- a/package.json +++ b/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", diff --git a/src/contexts/SideEntityController.tsx b/src/contexts/SideEntityController.tsx index 6d1c4f1..4190de7 100644 --- a/src/contexts/SideEntityController.tsx +++ b/src/contexts/SideEntityController.tsx @@ -95,7 +95,7 @@ export const SideEntityProvider: React.FC = ({ 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 = ({ { replace: true, state: { - base_pathname: basePathname, + base_location: baseLocation, panels: [...sidePanels.slice(0, -1), updatedPanel] } } @@ -212,7 +212,7 @@ export const SideEntityProvider: React.FC = ({ newPath, { state: { - base_pathname: basePathname, + base_location: baseLocation, panels: [...sidePanels, newPanel] } } diff --git a/src/core/CMSRouterSwitch.tsx b/src/core/CMSRouterSwitch.tsx deleted file mode 100644 index 3e85240..0000000 --- a/src/core/CMSRouterSwitch.tsx +++ /dev/null @@ -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 ; - - - 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 ; - } - 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 ; - } - } - return <>; - - // TODO: the following implementation relies on being able to override the location - // TODO: in react-router - // const buildCMSViewRoute = (path: string, cmsView: CMSView) => { - // return - // - // ; - // }; - // - // 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 => ( - // - // - // - // ) - // ); - // return ( - // - // - // {collectionRoutes} - // - // {customRoutes} - // - // - // - // - // - // - // ); -} diff --git a/src/core/CMSRoutes.tsx b/src/core/CMSRoutes.tsx new file mode 100644 index 0000000..d4e1489 --- /dev/null +++ b/src/core/CMSRoutes.tsx @@ -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 + {cmsView.view} + } + />; + }; + + 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 ( + + + + }/> + ); + } + ); + + const homeRoute = + + + + }/>; + + return ( + + + {collectionRoutes} + + {customRoutes} + + {homeRoute} + + + ); +} diff --git a/src/core/CMSMainView.tsx b/src/core/CMSScaffold.tsx similarity index 93% rename from src/core/CMSMainView.tsx rename to src/core/CMSScaffold.tsx index 5444aae..d77ba27 100644 --- a/src/core/CMSMainView.tsx +++ b/src/core/CMSScaffold.tsx @@ -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) { +export function CMSScaffold(props: PropsWithChildren) { const { children, diff --git a/src/core/components/BreadcrumbUpdater.tsx b/src/core/components/BreadcrumbUpdater.tsx new file mode 100644 index 0000000..e52a5ca --- /dev/null +++ b/src/core/components/BreadcrumbUpdater.tsx @@ -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) { + + const breadcrumbsContext = useBreadcrumbsContext(); + React.useEffect(() => { + breadcrumbsContext.set({ + breadcrumbs: [{ + title: title, + url: path + }] + }); + }, [path]); + + return <> {children}; +} + +export default BreadcrumbUpdater; diff --git a/src/core/internal/HomeRoute.tsx b/src/core/components/CMSHome.tsx similarity index 84% rename from src/core/internal/HomeRoute.tsx rename to src/core/components/CMSHome.tsx index 5785520..dd9fb12 100644 --- a/src/core/internal/HomeRoute.tsx +++ b/src/core/components/CMSHome.tsx @@ -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 ( {allGroups.map((group, index) => ( @@ -145,4 +130,4 @@ function HomeRoute({ ); } -export default HomeRoute; +export default CMSHome; diff --git a/src/core/components/index.tsx b/src/core/components/index.tsx index f57ed26..fbc0b78 100644 --- a/src/core/components/index.tsx +++ b/src/core/components/index.tsx @@ -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"; + diff --git a/src/core/index.tsx b/src/core/index.tsx index f6865a5..bd09dcc 100644 --- a/src/core/index.tsx +++ b/src/core/index.tsx @@ -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"; diff --git a/src/core/internal/CMSViewRoute.tsx b/src/core/internal/CMSViewRoute.tsx deleted file mode 100644 index fbc7dd2..0000000 --- a/src/core/internal/CMSViewRoute.tsx +++ /dev/null @@ -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 - {cmsView.view} - ; -} - -export default CMSViewRoute; diff --git a/src/core/internal/CollectionRoute.tsx b/src/core/internal/CollectionRoute.tsx deleted file mode 100644 index 553ab9b..0000000 --- a/src/core/internal/CollectionRoute.tsx +++ /dev/null @@ -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 { - collectionConfig: EntityCollection; - path: string -} - -function CollectionRoute({ - collectionConfig, - path - } - : CollectionRouteProps) { - - const { pathname } = useLocation(); - const breadcrumbsContext = useBreadcrumbsContext(); - React.useEffect(() => { - breadcrumbsContext.set({ - breadcrumbs: [{ - title: collectionConfig.name, - url: pathname - }] - }); - }, [pathname]); - - const classes = useStyles(); - - return ( -
- - - -
- ); -} - -export default CollectionRoute; diff --git a/src/core/navigation.ts b/src/core/navigation.ts index d7930b4..a184467 100644 --- a/src/core/navigation.ts +++ b/src/core/navigation.ts @@ -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 => diff --git a/src/firebase_app/CMSApp.tsx b/src/firebase_app/CMSApp.tsx index 4687784..99ad40b 100644 --- a/src/firebase_app/CMSApp.tsx +++ b/src/firebase_app/CMSApp.tsx @@ -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 ; } - 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 ( - - - + + + + + - -
; + return ( + + + + ); }} - - -); + + + ); } diff --git a/src/firebase_app/CMSAppProps.tsx b/src/firebase_app/CMSAppProps.tsx index 72904bf..88215a2 100644 --- a/src/firebase_app/CMSAppProps.tsx +++ b/src/firebase_app/CMSAppProps.tsx @@ -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 diff --git a/src/core/components/FirebaseLoginView.tsx b/src/firebase_app/FirebaseLoginView.tsx similarity index 98% rename from src/core/components/FirebaseLoginView.tsx rename to src/firebase_app/FirebaseLoginView.tsx index 1714790..efd01f6 100644 --- a/src/core/components/FirebaseLoginView.tsx +++ b/src/firebase_app/FirebaseLoginView.tsx @@ -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"; diff --git a/src/firebase_app/index.ts b/src/firebase_app/index.ts index 9377a17..cbd6054 100644 --- a/src/firebase_app/index.ts +++ b/src/firebase_app/index.ts @@ -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"; + diff --git a/src/firebase_app/useFirestoreDataSource.ts b/src/firebase_app/useFirestoreDataSource.ts index 674b93f..61d4a18 100644 --- a/src/firebase_app/useFirestoreDataSource.ts +++ b/src/firebase_app/useFirestoreDataSource.ts @@ -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): Promise { const properties: Properties = computeSchemaProperties(schema, path, entityId); - let updatedValues: EntityValues = 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; 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; diff --git a/src/core/initialiseFirebase.ts b/src/firebase_app/useInitialiseFirebase.ts similarity index 95% rename from src/core/initialiseFirebase.ts rename to src/firebase_app/useInitialiseFirebase.ts index e9ae501..0014f7f 100644 --- a/src/core/initialiseFirebase.ts +++ b/src/firebase_app/useInitialiseFirebase.ts @@ -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(); const [firebaseConfigLoading, setFirebaseConfigLoading] = React.useState(false); diff --git a/src/models/entities.ts b/src/models/entities.ts index d0b6bc8..8532ddf 100644 --- a/src/models/entities.ts +++ b/src/models/entities.ts @@ -210,7 +210,7 @@ export interface EntityOnSaveProps { /** * Values being saved */ - values: EntityValues; + values: Partial>; /** * New or existing entity diff --git a/src/test/algolia.test.ts b/src/test/algolia.test.ts index b3daf27..a335c98 100644 --- a/src/test/algolia.test.ts +++ b/src/test/algolia.test.ts @@ -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"); }); diff --git a/yarn.lock b/yarn.lock index f89bf7c..de7da5a 100644 --- a/yarn.lock +++ b/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"