import { useState } from "react"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { open as openDialog } from "@tauri-apps/plugin-dialog"; import { githubListRepos, githubGetToken, githubGetAccount, gitClone, createProject, getProjects, getSpaces, type GithubRepo, } from "../../lib/commands"; import { syncProjectCatalog } from "../../lib/spacesSync"; import { useUIStore } from "../../store/ui"; interface Props { onClose: () => void; } export function ImportRepoModal({ onClose }: Props) { const queryClient = useQueryClient(); const showToast = useUIStore((s) => s.showToast); const [search, setSearch] = useState(""); const [selected, setSelected] = useState(null); const [localPath, setLocalPath] = useState(""); const [upstreamUrl, setUpstreamUrl] = useState(""); const [cloning, setCloning] = useState(false); const { data: repos = [], isLoading, error } = useQuery({ queryKey: ["github-repos"], queryFn: async () => { const token = await githubGetToken(); if (!token) throw new Error("未登录 GitHub"); return githubListRepos(token); }, }); const { data: spaces = [] } = useQuery({ queryKey: ["spaces"], queryFn: getSpaces, }); const currentSpace = spaces.find((s) => s.is_current); const { data: existingProjects = [] } = useQuery({ queryKey: ["projects", currentSpace?.id], queryFn: () => getProjects(undefined, currentSpace?.id), enabled: !!currentSpace, }); // 当前空间已有的 repo_url 集合(用于判重) const importedUrls = new Set( existingProjects.map((p) => p.repo_url).filter((u): u is string => !!u) ); const filtered = repos.filter( (r) => r.name.toLowerCase().includes(search.toLowerCase()) || r.full_name.toLowerCase().includes(search.toLowerCase()) ); async function handlePickDir() { const dir = await openDialog({ directory: true, title: "选择本地存放目录" }); if (dir && selected) { setLocalPath(`${dir}\\${selected.name}`.replace(/\\/g, "\\")); } else if (dir) { setLocalPath(dir as string); } } async function handleImport() { if (!selected || !localPath) return; setCloning(true); try { const wsId = crypto.randomUUID(); await gitClone(selected.clone_url, localPath, upstreamUrl || undefined); await createProject({ id: wsId, name: selected.name, description: selected.description ?? undefined, win_path: localPath, status: "active", priority: "mid", repo_url: selected.clone_url, upstream_url: upstreamUrl || undefined, default_branch: selected.default_branch, space_id: currentSpace?.id, }); queryClient.invalidateQueries({ queryKey: ["projects"] }); // 静默同步 catalog 到 .dev-spaces (async () => { try { const account = await githubGetAccount(); const token = await githubGetToken(); if (token && account && currentSpace) { await syncProjectCatalog(token, account.username, currentSpace.id, currentSpace.name, { id: wsId, name: selected.name, description: selected.description ?? undefined, repo_url: selected.clone_url, default_branch: selected.default_branch, platform: "windows", }); } } catch { /* 同步失败不影响导入 */ } })(); showToast(`${selected.name} 已导入`); onClose(); } catch (e) { showToast(`导入失败: ${e}`); } finally { setCloning(false); } } return (
{/* 标题 */}

从 GitHub 导入仓库

{isLoading && (
加载仓库列表...
)} {error && (
{String(error)}
)} {!isLoading && !error && ( <> {/* 搜索 */}
setSearch(e.target.value)} className="w-full bg-zinc-800 text-white text-sm rounded-lg px-3 py-2 outline-none focus:ring-1 focus:ring-blue-500 placeholder:text-zinc-500" />
{/* 仓库列表 */}
{filtered.map((repo) => { const alreadyImported = importedUrls.has(repo.clone_url); return ( ); })} {filtered.length === 0 && (
没有找到匹配的仓库
)}
{/* 选中后的配置 */} {selected && (
setLocalPath(e.target.value)} placeholder="选择本地存放目录..." className="flex-1 bg-zinc-800 text-white text-sm rounded-lg px-3 py-2 outline-none focus:ring-1 focus:ring-blue-500 placeholder:text-zinc-500" />
setUpstreamUrl(e.target.value)} placeholder="https://github.com/company/project.git" className="w-full bg-zinc-800 text-white text-sm rounded-lg px-3 py-2 outline-none focus:ring-1 focus:ring-blue-500 placeholder:text-zinc-500" />
)} )}
); }