import { useCallback, useEffect, useMemo, useState } from "react" import { FileDiff, Loader2 } from "lucide-react" import { DiffDialog, type DiffRequest } from "~/components/explorer/diff-dialog" import { Badge } from "~/components/ui/badge" import { Button } from "~/components/ui/button" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "~/components/ui/dialog" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "~/components/ui/select" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "~/components/ui/table" import { api } from "~/lib/api" import type { ConflictInfo, ConflictResolution } from "~/lib/api" // 每个冲突的取舍选择:未选择 / 采用服务器版本 / 保留本地版本。 type ChoiceValue = "none" | ConflictResolution /** * 统一冲突弹窗:Get Latest 与 Checkout 共用。 * 支持逐文件选择服务器版本或保留本地版本、批量设置、冲突文件 Diff、批量应用。 */ export function ConflictDialog({ serverPath, onClose, onResolved, }: { serverPath: string onClose: () => void onResolved: () => void }) { const [conflicts, setConflicts] = useState([]) const [choices, setChoices] = useState>({}) const [loading, setLoading] = useState(true) const [applying, setApplying] = useState(false) const [error, setError] = useState(null) // 应用结果摘要:conflictId -> 是否成功。 const [applied, setApplied] = useState>({}) const [diffRequest, setDiffRequest] = useState(null) // 是否发生过成功应用,关闭时据此通知上层刷新。 const [didResolve, setDidResolve] = useState(false) /** * 加载当前范围内的未解决冲突。 */ const loadConflicts = useCallback(async () => { setLoading(true) const result = await api.listConflicts({ serverPath, recursive: true }) setLoading(false) if (!result.ok) { setError(result.errorMessage ?? "冲突明细加载失败") return } setError(null) const list = (result.data?.conflicts ?? []).filter((item) => !item.resolved) setConflicts(list) setChoices({}) setApplied({}) }, [serverPath]) useEffect(() => { void loadConflicts() }, [loadConflicts]) const chosenCount = useMemo( () => Object.values(choices).filter((value) => value !== "none").length, [choices], ) /** * 批量把所有冲突设置为同一取舍。 */ const setAll = useCallback( (value: ConflictResolution) => { setChoices(() => { const next: Record = {} for (const conflict of conflicts) { next[conflict.conflictId] = value } return next }) }, [conflicts], ) /** * 应用所有已选择的取舍,逐个调用后端并记录结果。 */ const applyChoices = useCallback(async () => { setApplying(true) setError(null) const results: Record = {} let resolvedAny = false for (const conflict of conflicts) { const choice = choices[conflict.conflictId] if (!choice || choice === "none") { continue } const result = await api.applyConflict({ conflictId: conflict.conflictId, resolution: choice, }) const success = result.ok && result.data?.resolution?.resolved === true results[conflict.conflictId] = success if (success) { resolvedAny = true } } setApplying(false) setApplied(results) if (resolvedAny) { setDidResolve(true) } const failures = Object.values(results).filter((value) => !value).length if (failures > 0) { setError(`${failures} 个冲突应用失败,可重试`) await loadConflicts() return } // 全部成功:重新加载剩余冲突,没有剩余则关闭并通知刷新。 const remain = await api.listConflicts({ serverPath, recursive: true }) const remaining = (remain.data?.conflicts ?? []).filter((item) => !item.resolved) if (remaining.length === 0) { onResolved() return } setConflicts(remaining) setChoices({}) setApplied({}) }, [conflicts, choices, serverPath, loadConflicts, onResolved]) /** * 关闭弹窗:若期间有成功应用,通知上层刷新状态。 */ const handleClose = useCallback(() => { if (didResolve) { onResolved() } else { onClose() } }, [didResolve, onResolved, onClose]) return ( <> !open && handleClose()}> 处理冲突 {serverPath}
{conflicts.length} 个冲突 逐项选择,或批量设置后统一应用
{loading ? (
正在加载冲突…
) : conflicts.length === 0 ? (
当前范围内没有未解决的冲突
) : ( 文件 类型 取舍 操作 {conflicts.map((conflict) => { const choice = choices[conflict.conflictId] ?? "none" const appliedState = applied[conflict.conflictId] const canDiff = conflict.serverPath != null && conflict.localPath != null return ( {conflict.serverPath ?? conflict.localPath ?? `冲突 ${conflict.conflictId}`} {appliedState === false && ( 应用失败 )} {conflict.type} ) })}
)}
{error &&

{error}

}
{diffRequest && ( setDiffRequest(null)} /> )} ) }