|
|
|
@@ -0,0 +1,488 @@
|
|
|
|
|
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 `
|
|
|
|
|
<div class="stack panel" id="server-form-card">
|
|
|
|
|
<h3 class="title">${server ? "Edit Server" : "Add Server"}</h3>
|
|
|
|
|
<input type="hidden" name="id" value="${escapeHtml(config.id)}" />
|
|
|
|
|
<div>
|
|
|
|
|
<div class="label">Display name</div>
|
|
|
|
|
<input name="displayName" value="${escapeHtml(config.displayName)}" placeholder="Gitpub Main" />
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<div class="label">Gitea server URL</div>
|
|
|
|
|
<input name="serverUrl" value="${escapeHtml(config.serverUrl)}" placeholder="https://git.example.com" />
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<div class="label">Auth method</div>
|
|
|
|
|
<select name="authMethod">
|
|
|
|
|
<option value="token" ${config.authMethod === "token" ? "selected" : ""}>Access Token</option>
|
|
|
|
|
<option value="password" ${config.authMethod === "password" ? "selected" : ""}>Username / Password</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<div class="label">Access token</div>
|
|
|
|
|
<input name="token" value="${escapeHtml(config.token)}" placeholder="gitea token" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="row">
|
|
|
|
|
<div style="flex: 1">
|
|
|
|
|
<div class="label">Username</div>
|
|
|
|
|
<input name="username" value="${escapeHtml(config.username)}" placeholder="username" />
|
|
|
|
|
</div>
|
|
|
|
|
<div style="flex: 1">
|
|
|
|
|
<div class="label">Password</div>
|
|
|
|
|
<input type="password" name="password" value="${escapeHtml(config.password)}" placeholder="password" />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="row wrap">
|
|
|
|
|
<button id="test-server-btn" type="button">Test connection</button>
|
|
|
|
|
<button id="save-server-btn" class="primary" type="button">Save server</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="muted">${escapeHtml(serverTestResult || settingsNotice)}</div>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function welcomeView() {
|
|
|
|
|
appRoot.innerHTML = `
|
|
|
|
|
<div class="welcome-wrap">
|
|
|
|
|
<div class="welcome-card stack">
|
|
|
|
|
<div class="panel stack">
|
|
|
|
|
<h1 class="title">Welcome to Gitpub Desktop</h1>
|
|
|
|
|
<p class="subtitle">
|
|
|
|
|
Connect your first Gitea backend. Gitpub Desktop works with any compatible Gitea server.
|
|
|
|
|
</p>
|
|
|
|
|
${serverFormTemplate(null)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
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 = `
|
|
|
|
|
<div class="layout">
|
|
|
|
|
<aside class="sidebar stack">
|
|
|
|
|
<div>
|
|
|
|
|
<h2 class="title">Gitpub Desktop</h2>
|
|
|
|
|
<p class="subtitle">Lightweight Gitea Git client</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="server-chip">
|
|
|
|
|
<div class="label">Active backend</div>
|
|
|
|
|
<div>${escapeHtml(activeServer?.displayName || "None")}</div>
|
|
|
|
|
<div class="muted">${escapeHtml(activeServer?.serverUrl || "Not configured")}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<button id="refresh-repos-btn" type="button">Refresh repositories</button>
|
|
|
|
|
<button id="open-settings-btn" type="button">Settings</button>
|
|
|
|
|
<div class="panel">
|
|
|
|
|
<div class="label">Recent local repositories</div>
|
|
|
|
|
<ul class="list">
|
|
|
|
|
${state.settings.recentRepositories.length
|
|
|
|
|
? state.settings.recentRepositories.map((path) => `<li>${escapeHtml(path)}</li>`).join("")
|
|
|
|
|
: "<li class='muted'>No repositories opened yet.</li>"}
|
|
|
|
|
</ul>
|
|
|
|
|
</div>
|
|
|
|
|
</aside>
|
|
|
|
|
|
|
|
|
|
<main class="main stack">
|
|
|
|
|
<div class="app-header">
|
|
|
|
|
<div>
|
|
|
|
|
<h2 class="title">Repository Dashboard</h2>
|
|
|
|
|
<p class="subtitle">Browse, clone, and open repositories across multiple Gitea backends.</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="row">
|
|
|
|
|
<input id="repo-search-input" placeholder="Search repositories..." value="${escapeHtml(state.repoSearch)}" />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<section class="panel stack">
|
|
|
|
|
<div class="section-header">
|
|
|
|
|
<h3 class="title">Repositories</h3>
|
|
|
|
|
<span class="muted">${visibleRepos.length} shown</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="repo-grid">
|
|
|
|
|
${visibleRepos
|
|
|
|
|
.map(
|
|
|
|
|
(repo) => `
|
|
|
|
|
<article class="repo-card stack">
|
|
|
|
|
<div><strong>${escapeHtml(repo.full_name)}</strong></div>
|
|
|
|
|
<div class="muted">${repo.private ? "Private" : "Public"} • Updated ${escapeHtml(repo.updated_at || "recently")}</div>
|
|
|
|
|
<div class="row">
|
|
|
|
|
<button class="clone-repo-btn" data-clone-url="${escapeHtml(repo.clone_url || "")}" type="button">Clone</button>
|
|
|
|
|
<button class="open-repo-btn" data-repo-name="${escapeHtml(repo.full_name)}" type="button">Open</button>
|
|
|
|
|
</div>
|
|
|
|
|
</article>
|
|
|
|
|
`
|
|
|
|
|
)
|
|
|
|
|
.join("")}
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<section class="panel stack">
|
|
|
|
|
<h3 class="title">Repository View</h3>
|
|
|
|
|
<div class="row">
|
|
|
|
|
<input id="local-repo-path-input" placeholder="Local repository path" value="${escapeHtml(state.localRepoPathInput)}" />
|
|
|
|
|
<button id="open-local-repo-btn" type="button">Open Local Repo</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="muted">Current repository: ${escapeHtml(state.selectedRepoName || "None selected")}</div>
|
|
|
|
|
<div class="row wrap">
|
|
|
|
|
<button id="git-status-btn" type="button">Status</button>
|
|
|
|
|
<button id="git-branch-btn" type="button">Branch</button>
|
|
|
|
|
<button id="git-pull-btn" type="button">Pull</button>
|
|
|
|
|
<button id="git-push-btn" type="button">Push</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<div class="label">Commit message (MVP placeholder)</div>
|
|
|
|
|
<textarea id="commit-message-input" placeholder="Write commit message...">${escapeHtml(state.commitMessage)}</textarea>
|
|
|
|
|
<button type="button" disabled title="Commit command will be added in next milestone">Commit (next milestone)</button>
|
|
|
|
|
</div>
|
|
|
|
|
<pre class="repo-card">${escapeHtml(gitOutput || "Run a git action to see output.")}</pre>
|
|
|
|
|
</section>
|
|
|
|
|
</main>
|
|
|
|
|
|
|
|
|
|
<aside class="rightbar stack">
|
|
|
|
|
<section class="panel stack">
|
|
|
|
|
<h3 class="title">Clone Repository</h3>
|
|
|
|
|
<input id="clone-url-input" placeholder="https://git.example.com/org/repo.git" value="${escapeHtml(state.cloneUrlInput)}" />
|
|
|
|
|
<input id="clone-destination-input" placeholder="/Users/me/code/repo" value="${escapeHtml(state.cloneDestinationInput || state.settings.defaultCloneDirectory)}" />
|
|
|
|
|
<button id="clone-btn" class="primary" type="button">Clone</button>
|
|
|
|
|
<span class="muted">Uses system Git executable from settings.</span>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<section class="panel stack">
|
|
|
|
|
<h3 class="title">Settings</h3>
|
|
|
|
|
<div class="label">Theme</div>
|
|
|
|
|
<select id="theme-select">
|
|
|
|
|
<option value="dark" ${state.settings.theme === "dark" ? "selected" : ""}>Dark (default)</option>
|
|
|
|
|
</select>
|
|
|
|
|
<div class="label">Git executable path</div>
|
|
|
|
|
<input id="git-path-input" value="${escapeHtml(state.settings.gitExecutablePath)}" placeholder="git or /usr/bin/git" />
|
|
|
|
|
<div class="label">Default clone directory</div>
|
|
|
|
|
<input id="default-clone-dir-input" value="${escapeHtml(state.settings.defaultCloneDirectory)}" placeholder="/Users/me/code" />
|
|
|
|
|
<button id="save-basic-settings-btn" type="button">Save basic settings</button>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<section class="panel stack">
|
|
|
|
|
<h3 class="title">Server Management</h3>
|
|
|
|
|
<div class="stack">
|
|
|
|
|
${state.settings.servers
|
|
|
|
|
.map(
|
|
|
|
|
(server) => `
|
|
|
|
|
<div class="server-chip stack">
|
|
|
|
|
<strong>${escapeHtml(server.displayName)}</strong>
|
|
|
|
|
<span class="muted">${escapeHtml(server.serverUrl)}</span>
|
|
|
|
|
<div class="row wrap">
|
|
|
|
|
<button class="set-default-server-btn" data-id="${server.id}" type="button">Set Default</button>
|
|
|
|
|
<button class="edit-server-btn" data-id="${server.id}" type="button">Edit</button>
|
|
|
|
|
<button class="delete-server-btn danger" data-id="${server.id}" type="button">Remove</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
`
|
|
|
|
|
)
|
|
|
|
|
.join("")}
|
|
|
|
|
</div>
|
|
|
|
|
<button id="add-server-btn" type="button">Add new server</button>
|
|
|
|
|
<div id="server-form-slot" class="stack"></div>
|
|
|
|
|
<div class="muted">${escapeHtml(settingsNotice)}</div>
|
|
|
|
|
</section>
|
|
|
|
|
</aside>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
});
|