mirror of
https://github.com/tale/headplane.git
synced 2026-07-26 07:48:14 +00:00
25dc09e025
Previously we would use naive revalidators which would invalidate EVERY SINGLE action loader every 3 seconds, resulting in several fetches. It would also bubble fetches across the layout actions into the individual pages. This new approach selectively has live stores of resources which then poll for changes on the server side and then dispatches updates to the client via a new /events/live SSE endpoint.
64 lines
2.0 KiB
TypeScript
64 lines
2.0 KiB
TypeScript
import type { LinksFunction, MetaFunction } from "react-router";
|
|
import { Links, Meta, Outlet, Scripts, ScrollRestoration } from "react-router";
|
|
import "@fontsource-variable/inter";
|
|
import { ExternalScripts } from "remix-utils/external-scripts";
|
|
|
|
import ToastProvider from "~/components/ToastProvider";
|
|
import { LiveDataProvider } from "~/utils/live-data";
|
|
import { useToastQueue } from "~/utils/toast";
|
|
|
|
import type { Route } from "./+types/root";
|
|
import { ErrorBanner } from "./components/error-banner";
|
|
|
|
import stylesheet from "~/tailwind.css?url";
|
|
|
|
export const meta: MetaFunction = () => [
|
|
{ title: "Headplane" },
|
|
{
|
|
name: "description",
|
|
content: "A frontend for the headscale coordination server",
|
|
},
|
|
];
|
|
|
|
export const links: LinksFunction = () => [{ rel: "stylesheet", href: stylesheet }];
|
|
|
|
export function Layout({ children }: { readonly children: React.ReactNode }) {
|
|
const toastQueue = useToastQueue();
|
|
|
|
// LiveDataProvider is wrapped at the top level since dialogs and things
|
|
// that control its state are usually open in portal containers which
|
|
// are not a part of the normal React tree.
|
|
return (
|
|
<LiveDataProvider>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charSet="utf-8" />
|
|
<meta content="width=device-width, initial-scale=1" name="viewport" />
|
|
<Meta />
|
|
<Links />
|
|
<link href={`${__PREFIX__}/favicon.ico`} rel="icon" />
|
|
</head>
|
|
<body className="overflow-x-hidden overscroll-none dark:bg-mist-900 dark:text-mist-50">
|
|
{children}
|
|
<ToastProvider queue={toastQueue} />
|
|
<ScrollRestoration />
|
|
<Scripts />
|
|
<ExternalScripts />
|
|
</body>
|
|
</html>
|
|
</LiveDataProvider>
|
|
);
|
|
}
|
|
|
|
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
|
|
return (
|
|
<div className="flex h-screen w-screen items-center justify-center p-4">
|
|
<ErrorBanner className="max-w-2xl" error={error} />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default function App() {
|
|
return <Outlet />;
|
|
}
|