简化版本控制里面git检测交互
This commit is contained in:
@@ -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 ---- */}
|
||||||
|
|||||||
Reference in New Issue
Block a user