import { type TurnId } from "@t3tools/contracts"; import { memo, useCallback, useMemo, useState } from "react"; import { type TurnDiffFileChange } from "../../types"; import { buildTurnDiffTree, type TurnDiffTreeNode } from "../../lib/turnDiffTree"; import { ChevronRightIcon, FolderIcon, FolderClosedIcon } from "lucide-react"; import { cn } from "~/lib/utils"; import { DiffStatLabel, hasNonZeroStat } from "./DiffStatLabel"; import { VscodeEntryIcon } from "./VscodeEntryIcon"; const EMPTY_DIRECTORY_OVERRIDES: Record = {}; export const ChangedFilesTree = memo(function ChangedFilesTree(props: { turnId: TurnId; files: ReadonlyArray; allDirectoriesExpanded: boolean; resolvedTheme: "light" | "dark"; onOpenTurnDiff: (turnId: TurnId, filePath?: string) => void; }) { const { files, allDirectoriesExpanded, onOpenTurnDiff, resolvedTheme, turnId } = props; const treeNodes = useMemo(() => buildTurnDiffTree(files), [files]); const directoryPathsKey = useMemo( () => collectDirectoryPaths(treeNodes).join("\u0000"), [treeNodes], ); const expansionStateKey = `${allDirectoriesExpanded ? "expanded" : "collapsed"}\u0000${directoryPathsKey}`; const [directoryExpansionState, setDirectoryExpansionState] = useState<{ key: string; overrides: Record; }>(() => ({ key: expansionStateKey, overrides: {}, })); const expandedDirectories = directoryExpansionState.key === expansionStateKey ? directoryExpansionState.overrides : EMPTY_DIRECTORY_OVERRIDES; const toggleDirectory = useCallback( (pathValue: string) => { setDirectoryExpansionState((current) => { const nextOverrides = current.key === expansionStateKey ? current.overrides : {}; return { key: expansionStateKey, overrides: { ...nextOverrides, [pathValue]: !(nextOverrides[pathValue] ?? allDirectoriesExpanded), }, }; }); }, [allDirectoriesExpanded, expansionStateKey], ); const renderTreeNode = (node: TurnDiffTreeNode, depth: number) => { const leftPadding = 8 + depth * 14; if (node.kind === "directory") { const isExpanded = expandedDirectories[node.path] ?? allDirectoriesExpanded; return (
{isExpanded && (
{node.children.map((childNode) => renderTreeNode(childNode, depth + 1))}
)}
); } return ( ); }; return
{treeNodes.map((node) => renderTreeNode(node, 0))}
; }); function collectDirectoryPaths(nodes: ReadonlyArray): string[] { const paths: string[] = []; for (const node of nodes) { if (node.kind !== "directory") continue; paths.push(node.path); paths.push(...collectDirectoryPaths(node.children)); } return paths; }