Skip to content

Commit

Permalink
Add compare versions drawer, fix route meta
Browse files Browse the repository at this point in the history
- route meta previously was only derived from
the deepest matched route, even if that route
did not define a route handle but an ancestor
did
- it will now use the most deeply nested
matched route's metadata
  • Loading branch information
solomonhawk committed Nov 14, 2024
1 parent b483e3f commit d3ae725
Show file tree
Hide file tree
Showing 19 changed files with 715 additions and 352 deletions.
3 changes: 2 additions & 1 deletion apps/web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
<html lang="en" class="dark">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<!-- @TODO: favicon -->
<!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" /> -->
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Manifold</title>
<meta id="meta-description" name="description" content="Manifold" />
Expand Down
5 changes: 5 additions & 0 deletions apps/web/src/features/dialog-manager/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { FindDependencyDialog } from "~features/editor/components/find-dependenc
import { PreviewDependencyDialog } from "~features/editor/components/preview-dependency-dialog";
import { TableDeleteDialog } from "~features/table/components/table-delete-dialog";
import { TablePublishDialog } from "~features/table/components/table-publish-dialog";
import { CompareVersionsDialog } from "~features/table-version/components/compare-versions-dialog";

/**
* @NOTE: Workaround for TypeScript not being able to infer the correct type of
Expand Down Expand Up @@ -51,6 +52,10 @@ export const DIALOGS = {
ID: "PREVIEW_DEPENDENCY",
COMPONENT: NiceModal.create(PreviewDependencyDialog),
},
COMPARE_VERSIONS: {
ID: "COMPARE_VERSIONS",
COMPONENT: NiceModal.create(CompareVersionsDialog),
},
} as const;

for (const dialog of Object.values(DIALOGS)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ import {
DrawerHeader,
DrawerTitle,
} from "@manifold/ui/components/ui/drawer";
import { useCallback } from "react";
import { GoX } from "react-icons/go";
import { useBlocker } from "react-router-dom";

import { useRouteChange } from "~features/routing/hooks/use-route-change";

type Props = {
dependency: RouterOutput["table"]["findDependencies"][number];
Expand All @@ -27,6 +31,16 @@ export function PreviewDependencyDialog({
}: Props) {
const modal = useModal();

/**
* Prevent navigation when the modal is open
*/
useBlocker(modal.visible);

/**
* Close the modal when a route change would occur
*/
useRouteChange(useCallback(() => modal.hide(), [modal]));

return (
<Drawer
open={modal.visible}
Expand All @@ -36,7 +50,7 @@ export function PreviewDependencyDialog({
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus
>
<DrawerContent>
<DrawerContent className="top-0">
<div className="w-full flex-1 overflow-y-auto">
<div className="mx-auto w-full max-w-xl">
<DrawerHeader>
Expand Down
44 changes: 36 additions & 8 deletions apps/web/src/features/routing/components/route-meta.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { isError } from "@tanstack/react-query";
import { useEffect } from "react";
import {
type Params,
type UIMatch,
useMatches,
useParams,
Expand All @@ -18,6 +19,37 @@ const DEFAULT_TITLE = "Manifold | Embrace the chaos";
const DEFAULT_DESCRIPTION =
"A tool for curating your collection of random tables.";

type RouteMetaData = { title: string; description: string };

function getRouteMetaData(
matchedRoute: UIMatch<unknown, Handle<unknown>>,
params: Readonly<Params<string>>,
) {
const { handle, data } = matchedRoute;

if (handle && data) {
return {
title: handle.title?.({ params, data }),
description: handle.description?.({ params, data }),
};
}

return { title: undefined, description: undefined };
}

function getMostSpecificRouteMetaData(
metadata: Partial<RouteMetaData>[],
): RouteMetaData {
return metadata.reduce<RouteMetaData>(
(acc, current) => {
return {
title: current.title || acc.title,
description: current.description || acc.description,
};
},
{ title: DEFAULT_TITLE, description: DEFAULT_DESCRIPTION },
);
}
/**
* When the route changes, updates the document title.
*
Expand All @@ -37,18 +69,14 @@ export function RouteMeta() {
const params = useParams();
const matches = useMatches() as UIMatch<unknown, Handle<unknown>>[];
const error = useRouteError();

const { handle, data } = matches[matches.length - 1];

let title = DEFAULT_TITLE;
let description = DEFAULT_DESCRIPTION;
const allMatchesMeta = matches.map((match) =>
getRouteMetaData(match, params),
);
let { title, description } = getMostSpecificRouteMetaData(allMatchesMeta);

if (error) {
title = isError(error) ? error.message : "Something went wrong";
description = "We're not sure what happened, but we're looking into it.";
} else if (handle && data) {
title = handle.title?.({ params, data }) ?? DEFAULT_TITLE;
description = handle.description?.({ params, data }) ?? DEFAULT_DESCRIPTION;
}

useEffect(() => {
Expand Down
15 changes: 15 additions & 0 deletions apps/web/src/features/routing/hooks/use-route-change.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { useAtomValue } from "jotai";
import { useEffect } from "react";

import { routerAtom } from "~features/routing/state";

/**
* Calls the provided callback when the route changes
*/
export function useRouteChange(callback: () => void) {
const router = useAtomValue(routerAtom);

useEffect(() => {
return router?.subscribe(() => callback());
}, [router, callback]);
}
11 changes: 10 additions & 1 deletion apps/web/src/features/routing/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ import {
protectedLoaderBuilder,
routerBuilder,
} from "~features/routing/router";
import { routesAtom } from "~features/routing/state";
import { routerAtom, routesAtom } from "~features/routing/state";
import { trpc } from "~utils/trpc";

export function Router() {
const session = useAuth();
const trpcUtils = trpc.useUtils();
const setRoutes = useSetAtom(routesAtom);
const setRouter = useSetAtom(routerAtom);

const guestLoader = useMemo(() => guestLoaderBuilder(session), [session]);
const protectedLoader = useMemo(
Expand All @@ -39,6 +40,14 @@ export function Router() {
setRoutes(routes);
}, [routes, setRoutes]);

/**
* Make router instance available for descendents (e.g. Drawers/Dialogs) to
* listen to route changes
*/
useEffect(() => {
setRouter(routerInstance);
}, [routerInstance, setRouter]);

return (
<>
<NavigationProgress router={routerInstance} />
Expand Down
12 changes: 12 additions & 0 deletions apps/web/src/features/routing/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,20 @@ export function buildAppRoutes({
lazy: loadTableDetailRoute(trpcUtils),
},
{
id: "table-version-detail",
path: "v/:version",
lazy: loadTableVersionDetailRoute(trpcUtils),
children: [
{
index: true,
lazy: () =>
import("~features/table-version/pages/detail").then(
(mod) => ({
Component: mod.TableVersionDetail,
}),
),
},
],
},
{
path: "edit",
Expand Down
5 changes: 4 additions & 1 deletion apps/web/src/features/routing/state.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { atom } from "jotai";
import { RouteObject } from "react-router-dom";
import { createBrowserRouter, RouteObject } from "react-router-dom";

export const routesAtom = atom<RouteObject[]>([]);
export const routerAtom = atom<ReturnType<typeof createBrowserRouter> | null>(
null,
);
Loading

0 comments on commit d3ae725

Please sign in to comment.