简化版本控制里面git检测交互

This commit is contained in:
xintaofei
2026-03-21 15:07:22 +08:00
parent 3b6da2b477
commit 1a7d112c2c

View File

@@ -7,7 +7,6 @@ import {
Github, Github,
Globe, Globe,
Loader2, Loader2,
Save,
Trash2, Trash2,
XCircle, XCircle,
} from "lucide-react" } from "lucide-react"
@@ -152,9 +151,8 @@ export function VersionControlSettings() {
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [gitInfo, setGitInfo] = useState<GitDetectResult | null>(null) const [gitInfo, setGitInfo] = useState<GitDetectResult | null>(null)
const [customPath, setCustomPath] = useState("") const [customPath, setCustomPath] = useState("")
const [editingPath, setEditingPath] = useState(false)
const [savingGit, setSavingGit] = useState(false) const [savingGit, setSavingGit] = useState(false)
const [testingGit, setTestingGit] = useState(false)
const [testResult, setTestResult] = useState<GitDetectResult | null>(null)
const [accounts, setAccounts] = useState<GitHubAccountsSettings>({ const [accounts, setAccounts] = useState<GitHubAccountsSettings>({
accounts: [], accounts: [],
@@ -197,34 +195,24 @@ export function VersionControlSettings() {
loadData().catch(console.error) loadData().catch(console.error)
}, [loadData]) }, [loadData])
// --- Git detection handlers --- // --- Git path handlers ---
const handleTestGit = useCallback(async () => {
const pathToTest = customPath.trim() || "git"
setTestingGit(true)
setTestResult(null)
try {
const result = await testGitPath(pathToTest)
setTestResult(result)
if (result.installed) {
toast.success(t("testSuccess"))
} else {
toast.error(t("testFailed", { message: "not a valid git executable" }))
}
} catch (err) {
const message = err instanceof Error ? err.message : String(err)
toast.error(t("testFailed", { message }))
} finally {
setTestingGit(false)
}
}, [customPath, t])
const handleSaveGit = useCallback(async () => { const handleSaveGit = useCallback(async () => {
const trimmed = customPath.trim()
setSavingGit(true) setSavingGit(true)
try { try {
await updateGitSettings({ custom_path: customPath.trim() || null }) // Test first if a custom path is provided
if (trimmed) {
const result = await testGitPath(trimmed)
if (!result.installed) {
toast.error(t("testFailed", { message: "not a valid git executable" }))
return
}
}
await updateGitSettings({ custom_path: trimmed || null })
const git = await detectGit() const git = await detectGit()
setGitInfo(git) setGitInfo(git)
setEditingPath(false)
toast.success(t("saveSuccess")) toast.success(t("saveSuccess"))
} catch (err) { } catch (err) {
const message = err instanceof Error ? err.message : String(err) const message = err instanceof Error ? err.message : String(err)
@@ -234,6 +222,14 @@ export function VersionControlSettings() {
} }
}, [customPath, t]) }, [customPath, t])
const handleCancelEdit = useCallback(() => {
setEditingPath(false)
// Restore to the saved value
getGitSettings()
.then((s) => setCustomPath(s.custom_path ?? ""))
.catch(() => {})
}, [])
// --- Shared account handlers --- // --- Shared account handlers ---
const handleAccountAdded = useCallback( const handleAccountAdded = useCallback(
@@ -355,33 +351,42 @@ export function VersionControlSettings() {
<GitBranch className="h-4 w-4 text-muted-foreground" /> <GitBranch className="h-4 w-4 text-muted-foreground" />
<h2 className="text-sm font-semibold">{t("gitTitle")}</h2> <h2 className="text-sm font-semibold">{t("gitTitle")}</h2>
</div> </div>
<p className="text-xs text-muted-foreground leading-5">
{t("gitDescription")}
</p>
<div className="rounded-md border bg-muted/20 px-3 py-3 text-xs space-y-2"> <div className="rounded-md border bg-muted/20 px-3 py-3 text-xs space-y-2">
<div className="flex items-center gap-2"> <div className="flex items-center justify-between">
{gitInfo?.installed ? ( <div className="flex items-center gap-2">
<> {gitInfo?.installed ? (
<CheckCircle2 className="h-3.5 w-3.5 text-green-500" /> <>
<span className="text-green-600 dark:text-green-400 font-medium"> <CheckCircle2 className="h-3.5 w-3.5 text-green-500" />
{t("gitDetected")} <span className="text-green-600 dark:text-green-400 font-medium">
{t("gitDetected")}
</span>
</>
) : (
<>
<XCircle className="h-3.5 w-3.5 text-red-500" />
<span className="text-red-600 dark:text-red-400 font-medium">
{t("gitNotFound")}
</span>
</>
)}
{gitInfo?.version && (
<span className="text-muted-foreground">
{gitInfo.version}
</span> </span>
</> )}
) : ( </div>
<> {!editingPath && (
<XCircle className="h-3.5 w-3.5 text-red-500" /> <Button
<span className="text-red-600 dark:text-red-400 font-medium"> size="xs"
{t("gitNotFound")} variant="ghost"
</span> className="text-xs"
</> onClick={() => setEditingPath(true)}
>
{t("customGitPath")}
</Button>
)} )}
</div> </div>
{gitInfo?.version && (
<p className="text-muted-foreground">
{t("gitVersion")}: {gitInfo.version}
</p>
)}
{gitInfo?.path && ( {gitInfo?.path && (
<p className="text-muted-foreground"> <p className="text-muted-foreground">
{t("gitPath")}: {gitInfo.path} {t("gitPath")}: {gitInfo.path}
@@ -389,74 +394,44 @@ export function VersionControlSettings() {
)} )}
</div> </div>
<div className="space-y-2"> {editingPath && (
<label className="text-xs font-medium text-muted-foreground"> <div className="space-y-2">
{t("customGitPath")} <label className="text-xs font-medium text-muted-foreground">
</label> {t("customGitPath")}
<div className="flex gap-2"> </label>
<Input <div className="flex gap-2">
value={customPath} <Input
onChange={(e) => { value={customPath}
setCustomPath(e.target.value) onChange={(e) => setCustomPath(e.target.value)}
setTestResult(null) placeholder={t("customGitPathPlaceholder")}
}} className="flex-1"
placeholder={t("customGitPathPlaceholder")} autoFocus
className="flex-1" />
/> <Button
<Button size="sm"
size="sm" variant="outline"
variant="outline" onClick={handleCancelEdit}
onClick={handleTestGit} disabled={savingGit}
disabled={testingGit} >
> {t("removeCancel")}
{testingGit ? ( </Button>
<> <Button
size="sm"
onClick={handleSaveGit}
disabled={savingGit}
>
{savingGit ? (
<Loader2 className="h-3.5 w-3.5 animate-spin" /> <Loader2 className="h-3.5 w-3.5 animate-spin" />
{t("testing")} ) : (
</> t("save")
) : ( )}
t("test") </Button>
)}
</Button>
</div>
<p className="text-[11px] text-muted-foreground">
{t("customGitPathHint")}
</p>
{testResult && (
<div
className={`flex items-center gap-1.5 text-xs ${
testResult.installed
? "text-green-600 dark:text-green-400"
: "text-red-600 dark:text-red-400"
}`}
>
{testResult.installed ? (
<CheckCircle2 className="h-3 w-3" />
) : (
<XCircle className="h-3 w-3" />
)}
{testResult.installed
? `${t("testSuccess")} (${testResult.version})`
: t("testFailed", { message: "invalid" })}
</div> </div>
)} <p className="text-[11px] text-muted-foreground">
</div> {t("customGitPathHint")}
</p>
<div className="flex justify-end"> </div>
<Button size="sm" onClick={handleSaveGit} disabled={savingGit}> )}
{savingGit ? (
<>
<Loader2 className="h-3.5 w-3.5 animate-spin" />
{t("saving")}
</>
) : (
<>
<Save className="h-3.5 w-3.5" />
{t("save")}
</>
)}
</Button>
</div>
</section> </section>
{/* ---- GitHub Accounts ---- */} {/* ---- GitHub Accounts ---- */}