diff --git a/.changeset/cold-snakes-turn.md b/.changeset/cold-snakes-turn.md new file mode 100644 index 000000000..c4abc004c --- /dev/null +++ b/.changeset/cold-snakes-turn.md @@ -0,0 +1,5 @@ +--- +"nostrudel": patch +--- + +Improve drawer navigation diff --git a/src/providers/drawer-sub-view-provider.tsx b/src/providers/drawer-sub-view-provider.tsx index 408242c96..bec4382ec 100644 --- a/src/providers/drawer-sub-view-provider.tsx +++ b/src/providers/drawer-sub-view-provider.tsx @@ -1,3 +1,4 @@ +import { PropsWithChildren, createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react"; import { ButtonGroup, Drawer, @@ -9,13 +10,13 @@ import { DrawerProps, IconButton, } from "@chakra-ui/react"; -import { PropsWithChildren, createContext, useCallback, useContext, useMemo, useState } from "react"; -import { RouteObject, RouterProvider, To, createMemoryRouter, useNavigate } from "react-router-dom"; +import { Location, RouteObject, RouterProvider, To, createMemoryRouter, useNavigate } from "react-router-dom"; import { ErrorBoundary } from "../components/error-boundary"; import NoteView from "../views/note"; import { ChevronLeftIcon, ChevronRightIcon, ExternalLinkIcon } from "../components/icons"; import { PageProviders } from "."; +import { logger } from "../helpers/debug"; type Router = ReturnType; @@ -82,40 +83,85 @@ export function useNavigateInDrawer() { return isInDrawer ? navigate : openDrawer; } +const log = logger.extend("DrawerRouter"); + export default function DrawerSubViewProvider({ children, parentRouter, }: PropsWithChildren & { parentRouter: Router }) { const [router, setRouter] = useState(null); - const openInParent = useCallback( + const openInParent = useCallback((to: To) => parentRouter.navigate(to), [parentRouter]); + + const direction = useRef<"up" | "down">(); + const marker = useRef(0); + + useEffect(() => { + return parentRouter.subscribe((event) => { + const location = event.location as Location<{ subRouterPath?: To | null } | null>; + const subRoute = location.state?.subRouterPath; + + if (event.historyAction === "PUSH") marker.current++; + else if (event.historyAction === "POP") marker.current--; + + if (subRoute) { + if (router) { + if (router.state.location.pathname !== subRoute && direction.current !== "up") { + log("Updating router from parent state"); + direction.current = "down"; + router.navigate(subRoute); + direction.current = undefined; + } + } else { + log("Create Router"); + + const newRouter = createMemoryRouter(routes, { initialEntries: [subRoute] }); + newRouter.subscribe((e) => { + if (e.errors && e.errors[0].status === 404 && e.errors[0].internal) { + openInParent(e.location); + } else if (direction.current !== "down") { + log("Updating parent state from Router"); + direction.current = "up"; + parentRouter.navigate(".", { + state: { ...parentRouter.state.location.state, subRouterPath: e.location.pathname }, + }); + } + direction.current = undefined; + }); + + // use the parent routers createHref method so that users can open links in new tabs + newRouter.createHref = parentRouter.createHref; + + setRouter(newRouter); + } + } else if (router) { + log("Destroy Router"); + setRouter(null); + } + }); + }, [parentRouter, router, setRouter]); + + const openDrawer = useCallback( (to: To) => { - parentRouter.navigate(to); - setRouter(null); + marker.current = 0; + parentRouter.navigate(".", { state: { ...parentRouter.state.location.state, subRouterPath: to } }); }, [parentRouter], ); - const openDrawer = useCallback( - (to: To) => { - const newRouter = createMemoryRouter(routes, { initialEntries: [to] }); - newRouter.subscribe((e) => { - if (e.errors && e.errors[0].status === 404 && e.errors[0].internal) { - openInParent(e.location); - } - }); - - // use the parent routers createHref method so that users can open links in new tabs - newRouter.createHref = parentRouter.createHref; - - setRouter(newRouter); - }, - [setRouter, openInParent], - ); - const closeDrawer = useCallback(() => { - setRouter(null); - }, [setRouter]); + const i = marker.current; + if (i > 0) { + log(`Navigating back ${i} entries to the point the drawer was opened`); + parentRouter.navigate(-i); + } else { + log(`Failed to navigate back, clearing state`); + parentRouter.navigate(".", { state: { ...parentRouter.state.location.state, subRouterPath: undefined } }); + } + + // reset marker + marker.current = 0; + }, [parentRouter]); const context = useMemo( () => ({