139 lines · 4.5 KB · TypeScript
RawDownload
import { type ResolvedKeybindingsConfig } from "@t3tools/contracts";import { useQuery } from "@tanstack/react-query";import { Outlet, createFileRoute, useNavigate } from "@tanstack/react-router";import { useEffect } from "react"; import ThreadSidebar from "../components/Sidebar";import { useHandleNewThread } from "../hooks/useHandleNewThread";import { isTerminalFocused } from "../lib/terminalFocus";import { serverConfigQueryOptions } from "../lib/serverReactQuery";import { resolveShortcutCommand } from "../keybindings";import { selectThreadTerminalState, useTerminalStateStore } from "../terminalStateStore";import { useThreadSelectionStore } from "../threadSelectionStore";import { resolveSidebarNewThreadEnvMode } from "~/components/Sidebar.logic";import { useAppSettings } from "~/appSettings";import { Sidebar, SidebarProvider, SidebarRail } from "~/components/ui/sidebar"; const EMPTY_KEYBINDINGS: ResolvedKeybindingsConfig = [];const THREAD_SIDEBAR_WIDTH_STORAGE_KEY = "chat_thread_sidebar_width";const THREAD_SIDEBAR_MIN_WIDTH = 13 * 16;const THREAD_MAIN_CONTENT_MIN_WIDTH = 40 * 16; function ChatRouteGlobalShortcuts() {  const clearSelection = useThreadSelectionStore((state) => state.clearSelection);  const selectedThreadIdsSize = useThreadSelectionStore((state) => state.selectedThreadIds.size);  const { activeDraftThread, activeThread, handleNewThread, projects, routeThreadId } =    useHandleNewThread();  const serverConfigQuery = useQuery(serverConfigQueryOptions());  const keybindings = serverConfigQuery.data?.keybindings ?? EMPTY_KEYBINDINGS;  const terminalOpen = useTerminalStateStore((state) =>    routeThreadId      ? selectThreadTerminalState(state.terminalStateByThreadId, routeThreadId).terminalOpen      : false,  );  const { settings: appSettings } = useAppSettings();   useEffect(() => {    const onWindowKeyDown = (event: KeyboardEvent) => {      if (event.defaultPrevented) return;       if (event.key === "Escape" && selectedThreadIdsSize > 0) {        event.preventDefault();        clearSelection();        return;      }       const projectId = activeThread?.projectId ?? activeDraftThread?.projectId ?? projects[0]?.id;      if (!projectId) return;       const command = resolveShortcutCommand(event, keybindings, {        context: {          terminalFocus: isTerminalFocused(),          terminalOpen,        },      });       if (command === "chat.newLocal") {        event.preventDefault();        event.stopPropagation();        void handleNewThread(projectId, {          envMode: resolveSidebarNewThreadEnvMode({            defaultEnvMode: appSettings.defaultThreadEnvMode,          }),        });        return;      }       if (command !== "chat.new") return;      event.preventDefault();      event.stopPropagation();      void handleNewThread(projectId, {        branch: activeThread?.branch ?? activeDraftThread?.branch ?? null,        worktreePath: activeThread?.worktreePath ?? activeDraftThread?.worktreePath ?? null,        envMode: activeDraftThread?.envMode ?? (activeThread?.worktreePath ? "worktree" : "local"),      });    };     window.addEventListener("keydown", onWindowKeyDown);    return () => {      window.removeEventListener("keydown", onWindowKeyDown);    };  }, [    activeDraftThread,    activeThread,    clearSelection,    handleNewThread,    keybindings,    projects,    selectedThreadIdsSize,    terminalOpen,    appSettings.defaultThreadEnvMode,  ]);   return null;} function ChatRouteLayout() {  const navigate = useNavigate();   useEffect(() => {    const onMenuAction = window.desktopBridge?.onMenuAction;    if (typeof onMenuAction !== "function") {      return;    }     const unsubscribe = onMenuAction((action) => {      if (action !== "open-settings") return;      void navigate({ to: "/settings" });    });     return () => {      unsubscribe?.();    };  }, [navigate]);   return (    <SidebarProvider defaultOpen>      <ChatRouteGlobalShortcuts />      <Sidebar        side="left"        collapsible="offcanvas"        className="border-r border-border bg-card text-foreground"        resizable={{          minWidth: THREAD_SIDEBAR_MIN_WIDTH,          shouldAcceptWidth: ({ nextWidth, wrapper }) =>            wrapper.clientWidth - nextWidth >= THREAD_MAIN_CONTENT_MIN_WIDTH,          storageKey: THREAD_SIDEBAR_WIDTH_STORAGE_KEY,        }}      >        <ThreadSidebar />        <SidebarRail />      </Sidebar>      <Outlet />    </SidebarProvider>  );} export const Route = createFileRoute("/_chat")({  component: ChatRouteLayout,});