import { fetchRepositories } from "./gitea-api.js"; import { getActiveServer, getState, setSettings, updateSettings, addRecentRepo } from "./state.js"; import { runGitBranch, runGitClone, runGitPull, runGitPush, runGitStatus, testGiteaConnection, } from "./tauri-api.js"; const appRoot = document.getElementById("app"); const mockRepos = [ { id: 1, full_name: "gitpub-desktop/client-ui", private: false, clone_url: "https://example.com/client-ui.git", updated_at: "today" }, { id: 2, full_name: "gitpub-desktop/git-core", private: true, clone_url: "https://example.com/git-core.git", updated_at: "yesterday" }, { id: 3, full_name: "gitpub-desktop/docs", private: false, clone_url: "https://example.com/docs.git", updated_at: "2 days ago" }, ]; let repositories = [...mockRepos]; let serverTestResult = ""; let settingsNotice = ""; let gitOutput = ""; function uid() { return `${Date.now()}-${Math.random().toString(16).slice(2)}`; } function escapeHtml(value = "") { return value .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """); } function serverFormTemplate(server = null) { // Reused in first-launch setup and in Settings server management. const defaults = { id: "", displayName: "", serverUrl: "", authMethod: "token", token: "", username: "", password: "", }; const config = { ...defaults, ...(server || {}) }; return `

${server ? "Edit Server" : "Add Server"}

Display name
Gitea server URL
Auth method
Access token
Username
Password
${escapeHtml(serverTestResult || settingsNotice)}
`; } function welcomeView() { appRoot.innerHTML = `

Welcome to Gitpub Desktop

Connect your first Gitea backend. Gitpub Desktop works with any compatible Gitea server.

${serverFormTemplate(null)}
`; bindServerFormEvents(); } function filteredRepositories() { const searchTerm = getState().repoSearch.trim().toLowerCase(); if (!searchTerm) return repositories; return repositories.filter((repo) => repo.full_name.toLowerCase().includes(searchTerm)); } function dashboardView() { const state = getState(); const activeServer = getActiveServer(); const visibleRepos = filteredRepositories(); appRoot.innerHTML = `

Repository Dashboard

Browse, clone, and open repositories across multiple Gitea backends.

Repositories

${visibleRepos.length} shown
${visibleRepos .map( (repo) => `
${escapeHtml(repo.full_name)}
${repo.private ? "Private" : "Public"} • Updated ${escapeHtml(repo.updated_at || "recently")}
` ) .join("")}

Repository View

Current repository: ${escapeHtml(state.selectedRepoName || "None selected")}
Commit message (MVP placeholder)
${escapeHtml(gitOutput || "Run a git action to see output.")}
`; bindDashboardEvents(); } function bindServerFormEvents(existingServer = null) { const testButton = document.getElementById("test-server-btn"); const saveButton = document.getElementById("save-server-btn"); const formCard = document.getElementById("server-form-card"); if (!testButton || !saveButton || !formCard) return; const collectPayload = () => { const get = (name) => formCard.querySelector(`[name="${name}"]`)?.value?.trim() || ""; return { id: get("id"), displayName: get("displayName"), serverUrl: get("serverUrl"), authMethod: get("authMethod") || "token", token: get("token"), username: get("username"), password: get("password"), }; }; testButton.addEventListener("click", async () => { const payload = collectPayload(); try { const result = await testGiteaConnection(payload); serverTestResult = `${result.ok ? "Connected" : "Failed"}: ${result.message} (${result.apiBaseUrl})`; render(); if (existingServer) { openServerForm(existingServer.id); } } catch (error) { serverTestResult = `Connection test failed: ${error.message}`; render(); if (existingServer) { openServerForm(existingServer.id); } } }); saveButton.addEventListener("click", () => { const payload = collectPayload(); if (!payload.displayName || !payload.serverUrl) { settingsNotice = "Display name and server URL are required."; render(); if (existingServer) openServerForm(existingServer.id); return; } const state = getState(); let nextServers = [...state.settings.servers]; const nextId = payload.id || uid(); const serverData = { ...payload, id: nextId }; const existingIndex = nextServers.findIndex((item) => item.id === nextId); if (existingIndex >= 0) { nextServers[existingIndex] = serverData; settingsNotice = `Updated server ${payload.displayName}.`; } else { nextServers.push(serverData); settingsNotice = `Added server ${payload.displayName}.`; } const activeServerId = state.settings.activeServerId || nextId; setSettings({ ...state.settings, servers: nextServers, activeServerId }); serverTestResult = ""; render(); }); } function openServerForm(serverId = null) { const slot = document.getElementById("server-form-slot"); if (!slot) return; const server = getState().settings.servers.find((item) => item.id === serverId) || null; slot.innerHTML = serverFormTemplate(server); bindServerFormEvents(server); } async function loadRepositories() { const activeServer = getActiveServer(); if (!activeServer) { // Empty setup defaults to mock cards so UI can be tested immediately. repositories = [...mockRepos]; return; } try { repositories = await fetchRepositories(activeServer); } catch (error) { settingsNotice = `Using mock repositories. API fetch failed: ${error.message}`; repositories = [...mockRepos]; } } async function runRepoCommand(actionName, runner) { const state = getState(); if (!state.selectedRepoPath) { gitOutput = "Select or enter a local repository path first."; render(); return; } try { const result = await runner(); gitOutput = `${actionName}: ${result.command}\n\n${result.stdout || "(no stdout)"}\n${result.stderr ? `\n${result.stderr}` : ""}`; } catch (error) { gitOutput = `${actionName} failed: ${error.message}`; } render(); } function bindDashboardEvents() { const state = getState(); document.getElementById("repo-search-input")?.addEventListener("input", (event) => { state.repoSearch = event.target.value; render(); }); document.getElementById("refresh-repos-btn")?.addEventListener("click", async () => { await loadRepositories(); render(); }); document.getElementById("open-settings-btn")?.addEventListener("click", () => { const panel = document.getElementById("server-form-slot"); if (panel) panel.scrollIntoView({ behavior: "smooth", block: "start" }); }); document.querySelectorAll(".open-repo-btn").forEach((button) => { button.addEventListener("click", () => { state.selectedRepoName = button.dataset.repoName || "Repository"; render(); }); }); document.querySelectorAll(".clone-repo-btn").forEach((button) => { button.addEventListener("click", () => { state.cloneUrlInput = button.dataset.cloneUrl || ""; render(); }); }); document.getElementById("open-local-repo-btn")?.addEventListener("click", () => { // MVP local detection is user-driven path entry; folder picker can be added next. const value = document.getElementById("local-repo-path-input")?.value?.trim() || ""; state.localRepoPathInput = value; state.selectedRepoPath = value; state.selectedRepoName = value.split("/").filter(Boolean).pop() || value; addRecentRepo(value); render(); }); document.getElementById("clone-btn")?.addEventListener("click", async () => { state.cloneUrlInput = document.getElementById("clone-url-input")?.value?.trim() || ""; state.cloneDestinationInput = document.getElementById("clone-destination-input")?.value?.trim() || ""; if (!state.cloneUrlInput || !state.cloneDestinationInput) { gitOutput = "Clone URL and destination path are required."; render(); return; } try { const result = await runGitClone( state.cloneUrlInput, state.cloneDestinationInput, state.settings.gitExecutablePath ); gitOutput = `${result.command}\n\n${result.stdout || "Clone completed."}\n${result.stderr || ""}`; addRecentRepo(state.cloneDestinationInput); } catch (error) { gitOutput = `Clone failed: ${error.message}`; } render(); }); document.getElementById("git-status-btn")?.addEventListener("click", () => { runRepoCommand("Status", () => runGitStatus(state.selectedRepoPath, state.settings.gitExecutablePath)); }); document.getElementById("git-branch-btn")?.addEventListener("click", () => { runRepoCommand("Branch", () => runGitBranch(state.selectedRepoPath, state.settings.gitExecutablePath)); }); document.getElementById("git-pull-btn")?.addEventListener("click", () => { runRepoCommand("Pull", () => runGitPull(state.selectedRepoPath, state.settings.gitExecutablePath)); }); document.getElementById("git-push-btn")?.addEventListener("click", () => { runRepoCommand("Push", () => runGitPush(state.selectedRepoPath, state.settings.gitExecutablePath)); }); document.getElementById("save-basic-settings-btn")?.addEventListener("click", () => { updateSettings({ theme: document.getElementById("theme-select")?.value || "dark", gitExecutablePath: document.getElementById("git-path-input")?.value?.trim() || "", defaultCloneDirectory: document.getElementById("default-clone-dir-input")?.value?.trim() || "", }); settingsNotice = "Saved basic settings."; render(); }); document.getElementById("add-server-btn")?.addEventListener("click", () => openServerForm()); document.querySelectorAll(".set-default-server-btn").forEach((button) => { button.addEventListener("click", async () => { updateSettings({ activeServerId: button.dataset.id }); settingsNotice = "Default server updated."; await loadRepositories(); render(); }); }); document.querySelectorAll(".delete-server-btn").forEach((button) => { button.addEventListener("click", () => { const nextServers = getState().settings.servers.filter((server) => server.id !== button.dataset.id); const nextActive = getState().settings.activeServerId === button.dataset.id ? nextServers[0]?.id || null : getState().settings.activeServerId; setSettings({ ...getState().settings, servers: nextServers, activeServerId: nextActive }); settingsNotice = "Server removed."; render(); }); }); document.querySelectorAll(".edit-server-btn").forEach((button) => { button.addEventListener("click", () => openServerForm(button.dataset.id)); }); } function render() { if (!getState().settings.servers.length) { // First launch always lands in setup until at least one backend is saved. welcomeView(); return; } dashboardView(); } window.addEventListener("DOMContentLoaded", async () => { await loadRepositories(); render(); });