import { fetchCurrentUser, 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: "alice/portfolio", private: false, clone_url: "https://example.com/portfolio.git", updated_at: "today", owner: { login: "alice", type: "User" } }, { id: 2, full_name: "alice/dotfiles", private: true, clone_url: "https://example.com/dotfiles.git", updated_at: "yesterday", owner: { login: "alice", type: "User" } }, { id: 3, full_name: "acme-corp/client-ui", private: false, clone_url: "https://example.com/client-ui.git", updated_at: "today", owner: { login: "acme-corp", type: "Organization" } }, { id: 4, full_name: "acme-corp/api-server", private: true, clone_url: "https://example.com/api-server.git", updated_at: "3 days ago", owner: { login: "acme-corp", type: "Organization" } }, { id: 5, full_name: "oss-collective/toolkit", private: false, clone_url: "https://example.com/toolkit.git", updated_at: "last week", owner: { login: "oss-collective", type: "Organization" } }, ]; let repositories = [...mockRepos]; let currentUserLogin = ""; // authenticated user's login name let serverTestResult = ""; let settingsNotice = ""; let gitOutput = ""; let activeRightTab = "clone"; // "clone" | "settings" | "servers" let activeMainTab = "repos"; // "repos" | "local" let repoOwnerFilter = "all"; // "all" | "personal" | "orgs" 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 isOrgRepo(repo) { const ownerLogin = repo.owner?.login || repo.full_name.split("/")[0]; // When we know the authenticated user, compare against owner login. // Fall back to owner.type for cases where user info wasn't fetched. if (currentUserLogin) { return ownerLogin.toLowerCase() !== currentUserLogin.toLowerCase(); } return repo.owner?.type === "Organization" || repo.owner?.type === "organization"; } function filteredRepositories() { const searchTerm = getState().repoSearch.trim().toLowerCase(); let result = repositories; if (repoOwnerFilter === "personal") { result = result.filter((repo) => !isOrgRepo(repo)); } else if (repoOwnerFilter === "orgs") { result = result.filter((repo) => isOrgRepo(repo)); } if (searchTerm) { result = result.filter((repo) => repo.full_name.toLowerCase().includes(searchTerm)); } return result; } function groupedByOrg(repos) { const groups = {}; for (const repo of repos) { const org = repo.owner?.login || "Unknown"; if (!groups[org]) groups[org] = []; groups[org].push(repo); } return groups; } function repoCardTemplate(repo) { return `
${escapeHtml(repo.full_name)}
${repo.private ? "Private" : "Public"} · ${escapeHtml(repo.updated_at || "recently")}
`; } function dashboardView() { const state = getState(); const activeServer = getActiveServer(); const visibleRepos = filteredRepositories(); appRoot.innerHTML = `
${activeMainTab === "repos" ? `` : ""}
${activeMainTab === "repos" ? `

Repositories

${visibleRepos.length} shown
${repoOwnerFilter === "orgs" ? (() => { const groups = groupedByOrg(visibleRepos); const orgNames = Object.keys(groups).sort(); if (!orgNames.length) { return `
No organisation repositories found
Try refreshing or check your server connection.
`; } return orgNames.map((org) => `
${escapeHtml(org)} ${groups[org].length} repo${groups[org].length !== 1 ? "s" : ""}
${groups[org].map((repo) => repoCardTemplate(repo)).join("")}
`).join(""); })() : `
${visibleRepos.length ? visibleRepos.map((repo) => repoCardTemplate(repo)).join("") : `
No repositories found
Try refreshing or check your server connection.
`}
`}
` : `

Local Repository

${state.selectedRepoName ? `
Active: ${escapeHtml(state.selectedRepoName)}
` : ""}
Commit message
Commit support coming in the next milestone.
${gitOutput ? `
${escapeHtml(gitOutput)}
` : `

Run a git command to see output here.

`}
`}
`; 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) { repositories = [...mockRepos]; currentUserLogin = "alice"; // mock user matches mock personal repos return; } try { const [user, repos] = await Promise.all([ fetchCurrentUser(activeServer), fetchRepositories(activeServer), ]); currentUserLogin = user.login || ""; repositories = repos; } catch (error) { settingsNotice = `Using mock repositories. API fetch failed: ${error.message}`; currentUserLogin = "alice"; 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", () => { activeRightTab = "servers"; render(); }); document.querySelectorAll("[data-right-tab]").forEach((btn) => { btn.addEventListener("click", () => { activeRightTab = btn.dataset.rightTab; render(); }); }); document.querySelectorAll("[data-main-tab]").forEach((btn) => { btn.addEventListener("click", () => { activeMainTab = btn.dataset.mainTab; render(); }); }); document.querySelectorAll("[data-owner-filter]").forEach((btn) => { btn.addEventListener("click", () => { repoOwnerFilter = btn.dataset.ownerFilter; render(); }); }); document.querySelectorAll(".open-repo-btn").forEach((button) => { button.addEventListener("click", () => { state.selectedRepoName = button.dataset.repoName || "Repository"; activeMainTab = "local"; render(); }); }); document.querySelectorAll(".clone-repo-btn").forEach((button) => { button.addEventListener("click", () => { state.cloneUrlInput = button.dataset.cloneUrl || ""; activeRightTab = "clone"; 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(); });