import { type TurnId } from "@t3tools/contracts"; import { memo, useCallback, useEffect, 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"; 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 allDirectoryExpansionState = useMemo( () => buildDirectoryExpansionState( directoryPathsKey ? directoryPathsKey.split("\u0000") : [], allDirectoriesExpanded, ), [allDirectoriesExpanded, directoryPathsKey], ); const [expandedDirectories, setExpandedDirectories] = useState>(() => buildDirectoryExpansionState(directoryPathsKey ? directoryPathsKey.split("\u0000") : [], true), ); useEffect(() => { setExpandedDirectories(allDirectoryExpansionState); }, [allDirectoryExpansionState]); const toggleDirectory = useCallback((pathValue: string, fallbackExpanded: boolean) => { setExpandedDirectories((current) => ({ ...current, [pathValue]: !(current[pathValue] ?? fallbackExpanded), })); }, []); const renderTreeNode = (node: TurnDiffTreeNode, depth: number) => { const leftPadding = 8 + depth * 14; if (node.kind === "directory") { const isExpanded = expandedDirectories[node.path] ?? depth === 0; 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; } function buildDirectoryExpansionState( directoryPaths: ReadonlyArray, expanded: boolean, ): Record { const expandedState: Record = {}; for (const directoryPath of directoryPaths) { expandedState[directoryPath] = expanded; } return expandedState; }