import { useEffect, useMemo, useState } from "react" import { FileText, FolderClosed, Loader2 } from "lucide-react" import { Button } from "~/components/ui/button" import { Checkbox } from "~/components/ui/checkbox" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "~/components/ui/dialog" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "~/components/ui/table" import { api } from "~/lib/api" import type { FolderDiffItem } from "~/lib/api" /** * 添加本地文件弹窗:对选中目录执行目录对比(递归), * 自动筛出本地新增(localOnly)的文件 / 目录,勾选后批量加入版本控制(pendingAdd)。 */ export function AddFilesDialog({ serverPath, onConfirm, onClose, }: { serverPath: string /** 确认添加所选本地路径,由编排层调用接口并刷新,返回是否成功。 */ onConfirm: (localPaths: string[]) => Promise onClose: () => void }) { const [items, setItems] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) // 勾选集合:以本地路径为键,默认全选。 const [selected, setSelected] = useState>(new Set()) const [busy, setBusy] = useState(false) useEffect(() => { let active = true void (async () => { setLoading(true) const result = await api.compareFolder({ serverPath, recursive: true }) if (!active) { return } setLoading(false) if (!result.ok) { setError(result.errorMessage ?? "目录扫描失败") return } setError(null) const localOnly = (result.data?.diffs ?? []).filter( (item) => item.status === "localOnly" && item.localPath, ) setItems(localOnly) setSelected(new Set(localOnly.map((item) => item.localPath))) })() return () => { active = false } }, [serverPath]) const allChecked = items.length > 0 && selected.size === items.length const selectedPaths = useMemo( () => items.filter((item) => selected.has(item.localPath)).map((item) => item.localPath), [items, selected], ) /** * 勾选 / 取消勾选单项。 */ const toggle = (localPath: string) => { setSelected((prev) => { const next = new Set(prev) if (next.has(localPath)) { next.delete(localPath) } else { next.add(localPath) } return next }) } return ( !open && !busy && onClose()}> 添加本地文件 {serverPath}
{loading ? (
正在扫描本地新增文件…
) : error ? (
{error}
) : items.length === 0 ? (
没有发现本地新增的文件,本地内容与服务器一致
) : ( setSelected( value === true ? new Set(items.map((item) => item.localPath)) : new Set(), ) } aria-label="全选 / 清空" /> 名称 本地路径 {items.map((item) => ( toggle(item.localPath)} > toggle(item.localPath)} aria-label={`选择 ${item.name}`} /> {item.folder ? ( ) : ( )} {item.name} {item.localPath} ))}
)}

添加后产生「挂起新增」,可在挂起更改面板审查、撤销,签入后进入服务器;目录会连同其内容一并添加。

) }