import { useCallback, useMemo, useState } from "react"; import { StyleSheet } from "react-native-unistyles"; // The sheet-aware list: inside a bottom sheet the tree scrolls with the sheet // gesture, and outside one it is the ordinary React Native FlatList. import { FlatList } from "@/components/ui/scroll-view"; import type { WorkingDiffMode } from "@/git/diff-document"; import { DiffFolderRow } from "@/git/diff-folder-row"; import { buildDiffTree, collectDirPaths, compressSingleChildChains, flattenDiffTree, type DiffTreeRow, } from "@/git/diff-tree"; import { FileHeader } from "@/git/file-header"; import type { DiffFileHeader } from "@/git/diff-snapshot"; export interface ChangedFilesTreeProps { files: DiffFileHeader[]; mode: WorkingDiffMode; onSelectFile: (path: string) => void; collapsedFolderPaths: string[]; onCollapsedFolderPathsChange: (paths: string[]) => void; } /** The changed-files tree shared by the desktop Changes rail and the compact Jump to file sheet. */ export function ChangedFilesTree({ files, mode, onSelectFile, collapsedFolderPaths, onCollapsedFolderPathsChange, }: ChangedFilesTreeProps) { const [selectedPath, setSelectedPath] = useState(null); const compressedTree = useMemo(() => compressSingleChildChains(buildDiffTree(files)), [files]); const allFolderPaths = useMemo(() => collectDirPaths(compressedTree), [compressedTree]); const collapsedFolders = useMemo(() => new Set(collapsedFolderPaths), [collapsedFolderPaths]); const items = useMemo( () => flattenDiffTree(compressedTree, collapsedFolders), [collapsedFolders, compressedTree], ); const handleSelectPath = useCallback((path: string) => setSelectedPath(path), []); const handleSelectFile = useCallback( (path: string) => { setSelectedPath(path); onSelectFile(path); }, [onSelectFile], ); const handleToggleFolder = useCallback( (dirPath: string) => { const next = collapsedFolders.has(dirPath) ? Array.from(collapsedFolders).filter((path) => path !== dirPath) : [...collapsedFolders, dirPath]; onCollapsedFolderPathsChange(next); }, [collapsedFolders, onCollapsedFolderPathsChange], ); const handleCollapseFolder = useCallback( (dirPath: string) => { const prefix = `${dirPath}/`; onCollapsedFolderPathsChange([ ...new Set([ ...collapsedFolders, ...allFolderPaths.filter( (folderPath) => folderPath === dirPath || folderPath.startsWith(prefix), ), ]), ]); }, [allFolderPaths, collapsedFolders, onCollapsedFolderPathsChange], ); const renderItem = useCallback( ({ item }: { item: DiffTreeRow }) => { if (item.kind === "folder") { return ( ); } return ( ); }, [ handleCollapseFolder, handleSelectFile, handleSelectPath, handleToggleFolder, collapsedFolders, mode, selectedPath, ], ); const keyExtractor = useCallback( (item: DiffTreeRow) => item.kind === "folder" ? `folder-${item.dirPath}` : `file-${item.file.path}`, [], ); return ( ); } const styles = StyleSheet.create((theme) => ({ scrollView: { flex: 1, }, contentContainer: { paddingBottom: theme.spacing[8], }, }));