115 lines · 4.3 KB · TypeScript
RawDownload
import { createFileRoute, Navigate, notFound } from "@tanstack/react-router";import { useClientPlugins } from "@executor-js/sdk/client"; // ---------------------------------------------------------------------------// /plugins/<pluginId>/<rest>//// Mounts pages contributed by client plugins. The host's// `<ExecutorPluginsProvider>` (set up at the root) materialises the// list of `ClientPluginSpec` from `virtual:executor/plugins-client`,// and this route reads it via `useClientPlugins()` — so adding a// plugin to `executor.config.ts` is sufficient for its pages to mount// here, with no per-route imports.//// Plugin pages use the same lightweight route vocabulary as the rest of the// console route tree: static segments and `$param` segments. The host route// owns that matching so plugin detail views can be real URLs instead of// in-component state.// --------------------------------------------------------------------------- export const Route = createFileRoute("/{-$orgSlug}/plugins/$pluginId/$")({  component: PluginRouteComponent,}); function normalizePath(input: string): string {  if (!input || input === "/") return "/";  const withLeadingSlash = input.startsWith("/") ? input : `/${input}`;  return withLeadingSlash.length > 1 ? withLeadingSlash.replace(/\/+$/, "") : "/";} const pathSegments = (input: string): readonly string[] =>  normalizePath(input)    .split("/")    .filter((segment) => segment.length > 0); export const matchPluginPagePath = (  pattern: string,  target: string,): Readonly<Record<string, string>> | null => {  const patternSegments = pathSegments(pattern);  const targetSegments = pathSegments(target);  if (patternSegments.length !== targetSegments.length) return null;   const params: Record<string, string> = {};  for (let index = 0; index < patternSegments.length; index += 1) {    const patternSegment = patternSegments[index]!;    const targetSegment = targetSegments[index]!;    if (patternSegment.startsWith("$") && patternSegment.length > 1) {      params[patternSegment.slice(1)] = decodeURIComponent(targetSegment);      continue;    }    if (patternSegment !== targetSegment) return null;  }  return params;}; const matchScore = (pattern: string): number =>  pathSegments(pattern).reduce((score, segment) => score + (segment.startsWith("$") ? 1 : 2), 0); export const matchPluginPage = <TPage extends { readonly path: string }>(  pages: readonly TPage[] | undefined,  target: string,): { readonly page: TPage; readonly params: Readonly<Record<string, string>> } | null => {  const matches =    pages      ?.map((page, index) => ({        page,        index,        params: matchPluginPagePath(page.path, target),        score: matchScore(page.path),      }))      .filter(        (          candidate,        ): candidate is {          readonly page: TPage;          readonly index: number;          readonly params: Readonly<Record<string, string>>;          readonly score: number;        } => candidate.params !== null,      )      .sort((a, b) => b.score - a.score || a.index - b.index) ?? [];  const first = matches[0];  return first ? { page: first.page, params: first.params } : null;}; function PluginRouteComponent() {  const { orgSlug, pluginId, _splat: rest } = Route.useParams();  const plugins = useClientPlugins();  const target = normalizePath(rest ?? "/");  if (pluginId === "toolkits") {    const segments = pathSegments(target);    const toolkitSlug = segments[0];    if (toolkitSlug) {      return (        <Navigate          to="/{-$orgSlug}/toolkits/$toolkitSlug"          params={{ orgSlug, toolkitSlug }}          replace        />      );    }    return <Navigate to="/{-$orgSlug}/toolkits" params={{ orgSlug }} replace />;  }   const plugin = plugins.find((p) => p.id === pluginId);  // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: TanStack Router represents not-found from components by throwing notFound()  if (!plugin) throw notFound();   const match = matchPluginPage(plugin.pages, target);  // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: TanStack Router represents not-found from components by throwing notFound()  if (!match) throw notFound();   const Component = match.page.component;  return <Component params={match.params} path={target} pluginId={pluginId} />;}