import { memo, useCallback, useMemo, useState, type MouseEvent } from "react"; import { useStyletron } from "baseui"; import { LabelSmall } from "baseui/typography"; import { Archive, ArrowUpFromLine, ChevronRight, FileCode, FilePlus, FileX, FolderOpen, GitPullRequest, PanelRight } from "lucide-react"; import { type ContextMenuItem, ContextMenuOverlay, PanelHeaderBar, SPanel, ScrollBody, useContextMenu } from "./ui"; import { type FileTreeNode, type Task, diffTabId } from "./view-model"; const FileTree = memo(function FileTree({ nodes, depth, onSelectFile, onFileContextMenu, changedPaths, }: { nodes: FileTreeNode[]; depth: number; onSelectFile: (path: string) => void; onFileContextMenu: (event: MouseEvent, path: string) => void; changedPaths: Set; }) { const [css, theme] = useStyletron(); const [collapsed, setCollapsed] = useState>(new Set()); return ( <> {nodes.map((node) => { const isCollapsed = collapsed.has(node.path); const isChanged = changedPaths.has(node.path); return (
{ if (node.isDir) { setCollapsed((current) => { const next = new Set(current); if (next.has(node.path)) { next.delete(node.path); } else { next.add(node.path); } return next; }); return; } onSelectFile(node.path); }} onContextMenu={node.isDir ? undefined : (event) => onFileContextMenu(event, node.path)} className={css({ display: "flex", alignItems: "center", gap: "4px", padding: "3px 10px", paddingLeft: `${10 + depth * 16}px`, cursor: "pointer", fontSize: "12px", fontFamily: '"IBM Plex Mono", monospace', color: isChanged ? theme.colors.contentPrimary : theme.colors.contentTertiary, ":hover": { backgroundColor: "rgba(255, 255, 255, 0.06)" }, })} > {node.isDir ? ( <> ) : ( )} {node.name}
{node.isDir && !isCollapsed && node.children ? ( ) : null}
); })} ); }); export const RightSidebar = memo(function RightSidebar({ task, activeTabId, onOpenDiff, onArchive, onRevertFile, onPublishPr, onToggleSidebar, }: { task: Task; activeTabId: string | null; onOpenDiff: (path: string) => void; onArchive: () => void; onRevertFile: (path: string) => void; onPublishPr: () => void; onToggleSidebar?: () => void; }) { const [css, theme] = useStyletron(); const [rightTab, setRightTab] = useState<"changes" | "files">("changes"); const contextMenu = useContextMenu(); const changedPaths = useMemo(() => new Set(task.fileChanges.map((file) => file.path)), [task.fileChanges]); const isTerminal = task.status === "archived"; const pullRequestUrl = task.pullRequest != null ? `https://github.com/${task.repoName}/pull/${task.pullRequest.number}` : null; const copyFilePath = useCallback(async (path: string) => { try { if (!window.navigator.clipboard) { throw new Error("Clipboard API unavailable in mock layout"); } await window.navigator.clipboard.writeText(path); } catch (error) { console.error("Failed to copy file path", error); } }, []); const openFileMenu = useCallback( (event: MouseEvent, path: string) => { const items: ContextMenuItem[] = []; if (changedPaths.has(path)) { items.push({ label: "Revert", onClick: () => onRevertFile(path) }); } items.push({ label: "Copy Path", onClick: () => void copyFilePath(path) }); contextMenu.open(event, items); }, [changedPaths, contextMenu, copyFilePath, onRevertFile], ); return (
{!isTerminal ? (
) : null} {onToggleSidebar ? (
{ if (event.key === "Enter" || event.key === " ") onToggleSidebar(); }} className={css({ width: "26px", height: "26px", borderRadius: "6px", color: "#71717a", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0, ":hover": { color: "#a1a1aa", backgroundColor: "rgba(255, 255, 255, 0.06)" }, })} >
) : null}
{rightTab === "changes" ? (
{task.fileChanges.length === 0 ? (
No changes yet
) : null} {task.fileChanges.map((file) => { const isActive = activeTabId === diffTabId(file.path); const TypeIcon = file.type === "A" ? FilePlus : file.type === "D" ? FileX : FileCode; const iconColor = file.type === "A" ? "#7ee787" : file.type === "D" ? "#ffa198" : theme.colors.contentTertiary; return (
onOpenDiff(file.path)} onContextMenu={(event) => openFileMenu(event, file.path)} className={css({ display: "flex", alignItems: "center", gap: "8px", padding: "6px 10px", borderRadius: "6px", backgroundColor: isActive ? "rgba(255, 255, 255, 0.06)" : "transparent", cursor: "pointer", ":hover": { backgroundColor: "rgba(255, 255, 255, 0.06)" }, })} >
{file.path}
+{file.added} -{file.removed} {file.type}
); })}
) : (
{task.fileTree.length > 0 ? ( ) : (
No files yet
)}
)}
{contextMenu.menu ? : null} ); });