import { Link, Outlet, useLocation } from "@tanstack/react-router"; import { useEffect, useRef, useState } from "react"; import { useAtomRefresh, useAtomValue } from "@effect/atom-react"; import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; import type { Integration } from "@executor-js/sdk/shared"; import { integrationsAtom, integrationsOptimisticAtom, toolsAllAtom, } from "@executor-js/react/api/atoms"; import { Button } from "@executor-js/react/components/button"; import { integrationPresetIconUrl } from "@executor-js/react/components/integration-favicon"; import { IntegrationIconWithAccount } from "@executor-js/react/components/integration-icon-with-account"; import { CommandPalette } from "@executor-js/react/components/command-palette"; import { useClientPlugins, useIntegrationPlugins } from "@executor-js/sdk/client"; import { SidebarUpdateCard } from "@executor-js/react/components/update-card"; import { Wordmark } from "@executor-js/react/components/wordmark"; import { ServerConnectionMenu } from "./server-connection-menu"; // ── Env ───────────────────────────────────────────────────────────────── type AppMetaEnv = { readonly VITE_APP_VERSION: string; readonly VITE_GITHUB_URL: string; }; const { VITE_APP_VERSION, VITE_GITHUB_URL } = ( import.meta as ImportMeta & { readonly env: AppMetaEnv; } ).env; // ── NavItem ────────────────────────────────────────────────────────────── function NavItem(props: { to: string; label: string; active: boolean; onNavigate?: () => void }) { return ( {props.label} ); } // ── PluginNav ──────────────────────────────────────────────────────────── // // Renders one nav link per plugin page that opted in via // `pages[].nav.label`. The catch-all `/plugins/$pluginId/$` route is the // mount point; the splat is the page's relative path with the leading // slash stripped. function PluginNav(props: { pathname: string; onNavigate?: () => void }) { const plugins = useClientPlugins(); const entries = plugins.flatMap((plugin) => (plugin.pages ?? []) .filter((page) => page.nav) .map((page) => { const splat = page.path.replace(/^\//, ""); const href = `/plugins/${plugin.id}${splat ? `/${splat}` : "/"}`; return { key: `${plugin.id}:${page.path}`, pluginId: plugin.id, splat, href, label: page.nav!.label, }; }), ); if (entries.length === 0) return null; return ( <> {entries.map((entry) => ( {entry.label} ))} ); } // ── IntegrationList ─────────────────────────────────────────────────────────── function IntegrationList(props: { pathname: string; onNavigate?: () => void }) { const integrations = useAtomValue(integrationsOptimisticAtom); const integrationPlugins = useIntegrationPlugins(); return AsyncResult.match(integrations, { onInitial: () =>
Loading…
, onFailure: () => (
No integrations yet
), onSuccess: ({ value }: { readonly value: readonly Integration[] }) => value.length === 0 ? (
No integrations yet
) : (
{value.map((integration: Integration) => { const slug = String(integration.slug); const name = integration.name || slug; const detailPath = `/integrations/${slug}`; const active = props.pathname === detailPath || props.pathname.startsWith(`${detailPath}/`); return ( {name} ); })}
), }); } // ── SidebarContent ─────────────────────────────────────────────────────── function SidebarContent(props: { pathname: string; onNavigate?: () => void; showBrand?: boolean }) { const isHome = props.pathname === "/"; const isSecrets = props.pathname === "/secrets"; const isPolicies = props.pathname === "/policies"; const isToolkits = props.pathname === "/toolkits" || props.pathname.startsWith("/toolkits/"); return ( <> {props.showBrand !== false && (
)} {/* Footer */}
Docs Feedback / bug? Star on GitHub v{VITE_APP_VERSION}
); } // ── Shell ───────────────────────────────────────────────────────────────── export function Shell() { const location = useLocation(); const pathname = location.pathname; const refreshSources = useAtomRefresh(integrationsAtom); const refreshTools = useAtomRefresh(toolsAllAtom); const lastPathname = useRef(pathname); const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false); if (lastPathname.current !== pathname) { lastPathname.current = pathname; if (mobileSidebarOpen) setMobileSidebarOpen(false); } // Lock scroll when mobile sidebar open useEffect(() => { if (!mobileSidebarOpen) return; const prev = document.body.style.overflow; document.body.style.overflow = "hidden"; return () => { document.body.style.overflow = prev; }; }, [mobileSidebarOpen]); useEffect(() => { if (!import.meta.hot) { return; } const refreshBackendData = () => { refreshSources(); refreshTools(); }; import.meta.hot.on("executor:backend-updated", refreshBackendData); return () => { import.meta.hot?.off("executor:backend-updated", refreshBackendData); }; }, [refreshSources, refreshTools]); return (
{/* Desktop sidebar */} {/* Mobile sidebar overlay */} {mobileSidebarOpen && (
{/* oxlint-disable-next-line react/forbid-elements */}
setMobileSidebarOpen(false)} showBrand={false} />
)} {/* Main content */}
{/* Desktop (macOS frameless) draggable title-bar strip. Gives the main area the same native window drag + double-click-to-zoom as the sidebar header; hidden everywhere else via CSS. Overlays the top of the main area (behind page content) so page headers stay flush with the top and their borders line up with the sidebar header. */}
{/* Mobile top bar. The desktop-macos-titlebar offset keeps the far-left hamburger clear of the native traffic lights when the macOS window is forced below the md breakpoint (issue #1125). */}
); }