Add external editor/file picker, update theme
Introduce file/application pickers and external-editor integration, plus a visual refresh. Frontend: add UI for selecting/rescanning installed IDEs, custom editor input, "Open in File Explorer" and "Open in Code Editor" actions, clone destination browse, helper utilities, new icons, and many CSS theme/UX improvements (variables, shadows, scrollbars, selection, refined component styles). State: track installedIdes and scan status. Tauri API: expose browseDirectory, browseApplication and scanInstalledIdes, and wire UI handlers to call them. Backend: add InstalledIde struct and update tauri Cargo manifest and capabilities to allow dialogs. Overall improves editor/workflow integrations and modernizes the app styling.
This commit is contained in:
+190
-4
@@ -1,6 +1,8 @@
|
||||
import { fetchCurrentUser, fetchRepoBranches, fetchRepoContents, fetchRepositories } from "./gitea-api.js";
|
||||
import { getActiveServer, getState, setSettings, updateSettings, addRecentRepo } from "./state.js";
|
||||
import {
|
||||
browseApplication,
|
||||
browseDirectory,
|
||||
checkoutBranch,
|
||||
commitChanges,
|
||||
createBranch,
|
||||
@@ -24,6 +26,7 @@ import {
|
||||
runGitPush,
|
||||
runGitSync,
|
||||
runGitStatus,
|
||||
scanInstalledIdes,
|
||||
scanLocalRepos,
|
||||
testGiteaConnection,
|
||||
} from "./tauri-api.js";
|
||||
@@ -50,6 +53,8 @@ let repoOwnerFilter = "all";
|
||||
const maxPreviewBytes = 256 * 1024;
|
||||
const defaultRepositoryName = "Gitpub-Desktop";
|
||||
const defaultBranchName = "main";
|
||||
const DEFAULT_EDITOR_VALUE = "__default_code__";
|
||||
const CUSTOM_EDITOR_VALUE = "__custom__";
|
||||
|
||||
const FOLDER_ICON = `<svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path fill="#54aeff" d="M1.75 1A1.75 1.75 0 0 0 0 2.75v10.5C0 14.216.784 15 1.75 15h12.5A1.75 1.75 0 0 0 16 13.25v-8.5A1.75 1.75 0 0 0 14.25 3H7.5a.25.25 0 0 1-.2-.1l-.9-1.2C6.07 1.26 5.55 1 5 1H1.75Z"/></svg>`;
|
||||
const FILE_ICON = `<svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path fill="#848d97" d="M2 1.75C2 .784 2.784 0 3.75 0h6.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v9.586A1.75 1.75 0 0 1 13.25 16h-9.5A1.75 1.75 0 0 1 2 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h9.5a.25.25 0 0 0 .25-.25V6h-2.75A1.75 1.75 0 0 1 9 4.25V1.5Zm6.75.062V4.25c0 .138.112.25.25.25h2.688l-.011-.013-2.914-2.914-.013-.011Z"/></svg>`;
|
||||
@@ -59,6 +64,8 @@ const SYNC_ICON = `<svg width="15" height="15" viewBox="0 0 16 16" aria-hidden="
|
||||
const PULL_ICON = `<svg width="15" height="15" viewBox="0 0 16 16" aria-hidden="true"><path fill="currentColor" d="M7.25 1.75a.75.75 0 0 1 1.5 0v8.69l2.72-2.72a.749.749 0 0 1 1.275.326.749.749 0 0 1-.215.734l-4 4a.75.75 0 0 1-1.06 0l-4-4A.749.749 0 0 1 4.53 7.72l2.72 2.72V1.75ZM2.75 14a.75.75 0 0 1 .75-.75h9a.75.75 0 0 1 0 1.5h-9a.75.75 0 0 1-.75-.75Z"/></svg>`;
|
||||
const PUSH_ICON = `<svg width="15" height="15" viewBox="0 0 16 16" aria-hidden="true"><path fill="currentColor" d="M7.47 3.22a.75.75 0 0 1 1.06 0l4 4A.749.749 0 0 1 12 8.5a.749.749 0 0 1-.53-.22L8.75 5.56v8.69a.75.75 0 0 1-1.5 0V5.56L4.53 8.28A.749.749 0 0 1 3.255 7.954a.749.749 0 0 1 .215-.734l4-4ZM2.75 2a.75.75 0 0 1 .75-.75h9a.75.75 0 0 1 0 1.5h-9A.75.75 0 0 1 2.75 2Z"/></svg>`;
|
||||
const PUBLISH_ICON = `<svg width="15" height="15" viewBox="0 0 16 16" aria-hidden="true"><path fill="currentColor" d="M8 1.25a.75.75 0 0 1 .75.75v1.13A3.001 3.001 0 0 1 11 6v.5h1.25A1.75 1.75 0 0 1 14 8.25v4A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-4A1.75 1.75 0 0 1 3.75 6.5H5V6a3.001 3.001 0 0 1 2.25-2.87V2A.75.75 0 0 1 8 1.25ZM6.5 6v.5h3V6a1.5 1.5 0 0 0-3 0Zm-.53 4.28 1.5-1.5a.75.75 0 0 1 1.06 0l1.5 1.5a.75.75 0 1 1-1.06 1.06l-.22-.22v1.13a.75.75 0 0 1-1.5 0v-1.13l-.22.22a.75.75 0 0 1-1.06-1.06Z"/></svg>`;
|
||||
const EXPLORER_ICON = `<svg width="15" height="15" viewBox="0 0 16 16" aria-hidden="true"><path fill="currentColor" d="M1.75 2A1.75 1.75 0 0 0 0 3.75v8.5C0 13.216.784 14 1.75 14h12.5A1.75 1.75 0 0 0 16 12.25v-6.5A1.75 1.75 0 0 0 14.25 4H7.5a.25.25 0 0 1-.2-.1l-.9-1.2A1.75 1.75 0 0 0 5 2H1.75Zm0 1.5H5a.25.25 0 0 1 .2.1l.9 1.2c.331.441.85.7 1.4.7h6.75a.25.25 0 0 1 .25.25v6.5a.25.25 0 0 1-.25.25H1.75a.25.25 0 0 1-.25-.25v-8.5a.25.25 0 0 1 .25-.25Z"/></svg>`;
|
||||
const EDITOR_ICON = `<svg width="15" height="15" viewBox="0 0 16 16" aria-hidden="true"><path fill="currentColor" d="M11.013 1.427a1.75 1.75 0 0 1 2.474 0l1.086 1.086a1.75 1.75 0 0 1 0 2.474l-7.7 7.7a1.75 1.75 0 0 1-.744.44l-3.018.862a.75.75 0 0 1-.927-.927l.862-3.018a1.75 1.75 0 0 1 .44-.744l7.527-7.873ZM12.427 2.487a.25.25 0 0 0-.354 0l-.963.963 1.44 1.44.963-.963a.25.25 0 0 0 0-.354l-1.086-1.086ZM11.49 5.95l-1.44-1.44-5.503 5.503a.25.25 0 0 0-.063.106l-.558 1.955 1.955-.558a.25.25 0 0 0 .106-.063L11.49 5.95ZM1 14.25a.75.75 0 0 1 .75-.75h12.5a.75.75 0 0 1 0 1.5H1.75a.75.75 0 0 1-.75-.75Z"/></svg>`;
|
||||
|
||||
function uid() {
|
||||
return `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
||||
@@ -89,6 +96,17 @@ function repoNameFromPath(path = "") {
|
||||
return path.split(/[/\\]/).filter(Boolean).pop() || path;
|
||||
}
|
||||
|
||||
function repoNameFromUrl(url = "") {
|
||||
const cleanUrl = url.trim().split(/[?#]/)[0].replace(/\/+$/, "").replace(/\.git$/i, "");
|
||||
return cleanUrl.split(/[/\\:]/).filter(Boolean).pop() || "";
|
||||
}
|
||||
|
||||
function joinDirectoryPath(parent = "", child = "") {
|
||||
if (!parent || !child) return parent || child;
|
||||
const separator = parent.includes("\\") ? "\\" : "/";
|
||||
return `${parent.replace(/[\\/]+$/, "")}${separator}${child}`;
|
||||
}
|
||||
|
||||
function currentRepositoryName() {
|
||||
return getState().selectedRepoName || defaultRepositoryName;
|
||||
}
|
||||
@@ -121,6 +139,31 @@ function selectedGitPath() {
|
||||
return getState().settings.gitExecutablePath;
|
||||
}
|
||||
|
||||
function isDetectedEditorPath(value = "", detectedEditors = []) {
|
||||
const normalizedValue = value.trim().toLowerCase();
|
||||
return detectedEditors.some((editor) => editor.executablePath.toLowerCase() === normalizedValue);
|
||||
}
|
||||
|
||||
function selectedEditorDropdownValue(state) {
|
||||
const editorPath = state.settings.externalEditorPath?.trim() || "";
|
||||
if (!editorPath) return DEFAULT_EDITOR_VALUE;
|
||||
return isDetectedEditorPath(editorPath, state.installedIdes) ? editorPath : CUSTOM_EDITOR_VALUE;
|
||||
}
|
||||
|
||||
function externalEditorOptionsTemplate(state) {
|
||||
const editorPath = state.settings.externalEditorPath?.trim() || "";
|
||||
const selectedValue = selectedEditorDropdownValue(state);
|
||||
const detectedOptions = state.installedIdes
|
||||
.map((editor) => `<option value="${escapeHtml(editor.executablePath)}" ${selectedValue === editor.executablePath ? "selected" : ""}>${escapeHtml(editor.name)}</option>`)
|
||||
.join("");
|
||||
|
||||
return `
|
||||
<option value="${DEFAULT_EDITOR_VALUE}" ${selectedValue === DEFAULT_EDITOR_VALUE ? "selected" : ""}>VS Code command (code)</option>
|
||||
${detectedOptions}
|
||||
<option value="${CUSTOM_EDITOR_VALUE}" ${selectedValue === CUSTOM_EDITOR_VALUE ? "selected" : ""}>Custom application…</option>
|
||||
`;
|
||||
}
|
||||
|
||||
function statusLabel(status = "") {
|
||||
const labels = {
|
||||
modified: "Modified",
|
||||
@@ -866,7 +909,11 @@ function cloneModalContent(state) {
|
||||
</div>
|
||||
<div>
|
||||
<div class="label">Destination path</div>
|
||||
<input id="clone-destination-input" placeholder="${escapeHtml(state.settings.defaultCloneDirectory || "/Users/me/code/repo")}" value="${escapeHtml(state.cloneDestinationInput || state.settings.defaultCloneDirectory)}" />
|
||||
<div class="gd-path-picker">
|
||||
<input id="clone-destination-input" placeholder="${escapeHtml(state.settings.defaultCloneDirectory || "/Users/me/code/repo")}" value="${escapeHtml(state.cloneDestinationInput || state.settings.defaultCloneDirectory)}" />
|
||||
<button id="clone-destination-browse-btn" type="button">Browse</button>
|
||||
</div>
|
||||
<span class="muted" style="font-size:12px">Choose a parent folder and Gitpub will create the repository folder inside it.</span>
|
||||
</div>
|
||||
<button id="clone-btn" class="primary" type="button">Clone Repository</button>
|
||||
${gitOutput
|
||||
@@ -907,6 +954,8 @@ function serversModalContent(state) {
|
||||
}
|
||||
|
||||
function settingsModalContent(state) {
|
||||
const editorDropdownValue = selectedEditorDropdownValue(state);
|
||||
const customEditorPath = editorDropdownValue === CUSTOM_EDITOR_VALUE ? state.settings.externalEditorPath?.trim() || "" : "";
|
||||
return `
|
||||
<div class="gd-modal-two-col">
|
||||
<div class="gd-modal-section">
|
||||
@@ -925,8 +974,19 @@ function settingsModalContent(state) {
|
||||
<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" />
|
||||
<div class="label">External editor command</div>
|
||||
<input id="external-editor-input" value="${escapeHtml(state.settings.externalEditorPath)}" placeholder="code" />
|
||||
<div class="label">Code editor</div>
|
||||
<div class="gd-editor-picker">
|
||||
<select id="external-editor-select">
|
||||
${externalEditorOptionsTemplate(state)}
|
||||
</select>
|
||||
<button id="rescan-editors-btn" type="button" ${state.installedIdeScanLoading ? "disabled" : ""}>${state.installedIdeScanLoading ? "Scanning..." : "Rescan"}</button>
|
||||
</div>
|
||||
${state.installedIdeScanError ? `<div class="viewer-error" style="margin-top:6px">${escapeHtml(state.installedIdeScanError)}</div>` : ""}
|
||||
${!state.installedIdeScanLoading && !state.installedIdes.length ? `<div class="muted" style="font-size:12px;margin-top:4px">No installed IDEs detected yet. You can use the default <code>code</code> command or choose a custom app.</div>` : ""}
|
||||
<div id="custom-editor-row" class="gd-path-picker ${editorDropdownValue === CUSTOM_EDITOR_VALUE ? "" : "hidden"}" style="margin-top:8px">
|
||||
<input id="custom-editor-input" value="${escapeHtml(customEditorPath)}" placeholder="Choose an application or executable" />
|
||||
<button id="custom-editor-browse-btn" type="button">Browse</button>
|
||||
</div>
|
||||
|
||||
<div class="gd-modal-divider"></div>
|
||||
|
||||
@@ -1119,6 +1179,14 @@ function dashboardView() {
|
||||
</div>
|
||||
|
||||
<div class="gd-toolbar-right">
|
||||
<div class="gd-external-actions" role="group" aria-label="Repository actions">
|
||||
<button id="open-file-explorer-btn" class="gd-icon-action" type="button" ${!hasLocalRepo ? "disabled" : ""} title="Open in File Explorer" aria-label="Open in File Explorer">
|
||||
${EXPLORER_ICON}
|
||||
</button>
|
||||
<button id="open-code-editor-btn" class="gd-icon-action" type="button" ${!hasLocalRepo ? "disabled" : ""} title="Open in Code Editor" aria-label="Open in Code Editor">
|
||||
${EDITOR_ICON}
|
||||
</button>
|
||||
</div>
|
||||
<div class="gd-view-toggle" role="group" aria-label="View mode">
|
||||
<button class="${activeView === "changes" ? "active" : ""}" data-view="changes" type="button">Changes</button>
|
||||
<button class="${activeView === "history" ? "active" : ""}" data-view="history" type="button">History</button>
|
||||
@@ -1141,6 +1209,14 @@ function dashboardView() {
|
||||
<svg width="14" height="14" viewBox="0 0 16 16" aria-hidden="true"><path fill="currentColor" fill-rule="evenodd" d="M2 1.75C2 .784 2.784 0 3.75 0h6.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v9.586A1.75 1.75 0 0 1 13.25 16h-9.5A1.75 1.75 0 0 1 2 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h9.5a.25.25 0 0 0 .25-.25V6h-2.75A1.75 1.75 0 0 1 9 4.25V1.5Zm6.75.062V4.25c0 .138.112.25.25.25h2.688l-.011-.013-2.914-2.914-.013-.011Z"/></svg>
|
||||
File Viewer
|
||||
</button>
|
||||
<button class="gd-utility-menu-item" id="open-file-explorer-menu-btn" type="button" role="menuitem" ${!hasLocalRepo ? "disabled" : ""}>
|
||||
${EXPLORER_ICON}
|
||||
Open in File Explorer
|
||||
</button>
|
||||
<button class="gd-utility-menu-item" id="open-code-editor-menu-btn" type="button" role="menuitem" ${!hasLocalRepo ? "disabled" : ""}>
|
||||
${EDITOR_ICON}
|
||||
Open in Code Editor
|
||||
</button>
|
||||
<div class="gd-utility-separator"></div>
|
||||
<button class="gd-utility-menu-item" data-open-modal="servers" type="button" role="menuitem">
|
||||
<svg width="14" height="14" viewBox="0 0 16 16" aria-hidden="true"><path fill="currentColor" fill-rule="evenodd" d="M1.5 1.75a.25.25 0 0 1 .25-.25h12.5a.25.25 0 0 1 .25.25v4a.25.25 0 0 1-.25.25H1.75a.25.25 0 0 1-.25-.25v-4Zm.25-1.75A1.75 1.75 0 0 0 0 1.75v4C0 6.716.784 7.5 1.75 7.5h12.5A1.75 1.75 0 0 0 16 5.75v-4A1.75 1.75 0 0 0 14.25 0H1.75ZM0 10.25C0 9.284.784 8.5 1.75 8.5h12.5A1.75 1.75 0 0 1 16 10.25v4A1.75 1.75 0 0 1 14.25 16H1.75A1.75 1.75 0 0 1 0 14.25v-4Zm1.5.25a.25.25 0 0 0-.25.25v4c0 .138.112.25.25.25h12.5a.25.25 0 0 0 .25-.25v-4a.25.25 0 0 0-.25-.25H1.75Z"/></svg>
|
||||
@@ -1870,6 +1946,62 @@ async function openLocalViewer() {
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshInstalledIdes() {
|
||||
const state = getState();
|
||||
state.installedIdeScanLoading = true;
|
||||
state.installedIdeScanError = "";
|
||||
render();
|
||||
|
||||
try {
|
||||
state.installedIdes = await scanInstalledIdes();
|
||||
} catch (error) {
|
||||
state.installedIdes = [];
|
||||
state.installedIdeScanError = `Unable to scan installed IDEs: ${error.message}`;
|
||||
} finally {
|
||||
state.installedIdeScanLoading = false;
|
||||
render();
|
||||
}
|
||||
}
|
||||
|
||||
async function openSelectedRepoInFileExplorer() {
|
||||
const state = getState();
|
||||
if (!state.selectedRepoPath) {
|
||||
gitOutput = "Select or enter a local repository path first.";
|
||||
render();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await openInFileExplorer(state.selectedRepoPath);
|
||||
gitOutput = `Opened in File Explorer:\n${state.selectedRepoPath}`;
|
||||
} catch (error) {
|
||||
gitOutput = `Open in File Explorer failed: ${error.message}`;
|
||||
} finally {
|
||||
utilityMenuOpen = false;
|
||||
render();
|
||||
}
|
||||
}
|
||||
|
||||
async function openSelectedRepoInCodeEditor() {
|
||||
const state = getState();
|
||||
if (!state.selectedRepoPath) {
|
||||
gitOutput = "Select or enter a local repository path first.";
|
||||
render();
|
||||
return;
|
||||
}
|
||||
|
||||
const editorCommand = state.settings.externalEditorPath?.trim() || "code";
|
||||
try {
|
||||
await openInExternalEditor(state.selectedRepoPath, editorCommand);
|
||||
gitOutput = `Opened in Code Editor with "${editorCommand}":\n${state.selectedRepoPath}`;
|
||||
} catch (error) {
|
||||
gitOutput = `Open in Code Editor failed: ${error.message}`;
|
||||
} finally {
|
||||
utilityMenuOpen = false;
|
||||
render();
|
||||
}
|
||||
}
|
||||
|
||||
async function runRepoCommand(actionName, runner, operation = "") {
|
||||
const state = getState();
|
||||
if (!state.selectedRepoPath) {
|
||||
@@ -1916,6 +2048,9 @@ function bindDashboardEvents() {
|
||||
activeModal = btn.dataset.openModal;
|
||||
utilityMenuOpen = false;
|
||||
render();
|
||||
if (activeModal === "settings" && !state.installedIdeScanLoading && !state.installedIdes.length) {
|
||||
refreshInstalledIdes();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1926,6 +2061,11 @@ function bindDashboardEvents() {
|
||||
render();
|
||||
});
|
||||
|
||||
document.getElementById("open-file-explorer-btn")?.addEventListener("click", openSelectedRepoInFileExplorer);
|
||||
document.getElementById("open-file-explorer-menu-btn")?.addEventListener("click", openSelectedRepoInFileExplorer);
|
||||
document.getElementById("open-code-editor-btn")?.addEventListener("click", openSelectedRepoInCodeEditor);
|
||||
document.getElementById("open-code-editor-menu-btn")?.addEventListener("click", openSelectedRepoInCodeEditor);
|
||||
|
||||
// Modal close
|
||||
document.getElementById("modal-close-btn")?.addEventListener("click", () => {
|
||||
activeModal = "";
|
||||
@@ -2245,6 +2385,23 @@ function bindDashboardEvents() {
|
||||
render();
|
||||
});
|
||||
|
||||
document.getElementById("clone-destination-browse-btn")?.addEventListener("click", async () => {
|
||||
state.cloneUrlInput = document.getElementById("clone-url-input")?.value?.trim() || "";
|
||||
let selectedDirectory = "";
|
||||
try {
|
||||
selectedDirectory = await browseDirectory(state.settings.defaultCloneDirectory || "");
|
||||
} catch (error) {
|
||||
gitOutput = `Could not open folder picker: ${error.message}`;
|
||||
render();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!selectedDirectory) return;
|
||||
const repoName = repoNameFromUrl(state.cloneUrlInput);
|
||||
state.cloneDestinationInput = repoName ? joinDirectoryPath(selectedDirectory, repoName) : selectedDirectory;
|
||||
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() || "";
|
||||
@@ -2273,12 +2430,41 @@ function bindDashboardEvents() {
|
||||
});
|
||||
|
||||
// Settings modal handlers
|
||||
document.getElementById("external-editor-select")?.addEventListener("change", (event) => {
|
||||
document.getElementById("custom-editor-row")?.classList.toggle("hidden", event.target.value !== CUSTOM_EDITOR_VALUE);
|
||||
});
|
||||
document.getElementById("rescan-editors-btn")?.addEventListener("click", () => refreshInstalledIdes());
|
||||
document.getElementById("custom-editor-browse-btn")?.addEventListener("click", async () => {
|
||||
const currentValue = document.getElementById("custom-editor-input")?.value?.trim() || "";
|
||||
let selectedApplication = "";
|
||||
try {
|
||||
selectedApplication = await browseApplication(currentValue);
|
||||
} catch (error) {
|
||||
settingsNotice = `Could not open application picker: ${error.message}`;
|
||||
render();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!selectedApplication) return;
|
||||
const input = document.getElementById("custom-editor-input");
|
||||
if (input) input.value = selectedApplication;
|
||||
});
|
||||
|
||||
document.getElementById("save-basic-settings-btn")?.addEventListener("click", () => {
|
||||
const editorSelection = document.getElementById("external-editor-select")?.value || DEFAULT_EDITOR_VALUE;
|
||||
const customEditorPath = document.getElementById("custom-editor-input")?.value?.trim() || "";
|
||||
const externalEditorPath =
|
||||
editorSelection === DEFAULT_EDITOR_VALUE
|
||||
? ""
|
||||
: editorSelection === CUSTOM_EDITOR_VALUE
|
||||
? customEditorPath
|
||||
: editorSelection;
|
||||
|
||||
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() || "",
|
||||
externalEditorPath: document.getElementById("external-editor-input")?.value?.trim() || "",
|
||||
externalEditorPath,
|
||||
activeServerId: document.getElementById("default-server-select")?.value || null,
|
||||
autoFetchOnRepoOpen: Boolean(document.getElementById("auto-fetch-checkbox")?.checked),
|
||||
});
|
||||
|
||||
@@ -16,6 +16,9 @@ const state = {
|
||||
localRepoScanResults: [],
|
||||
localRepoScanLoading: false,
|
||||
localRepoScanError: "",
|
||||
installedIdes: [],
|
||||
installedIdeScanLoading: false,
|
||||
installedIdeScanError: "",
|
||||
cloneUrlInput: "",
|
||||
cloneDestinationInput: "",
|
||||
commitMessage: "",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const invoke = window.__TAURI__?.core?.invoke;
|
||||
const dialog = window.__TAURI__?.dialog;
|
||||
|
||||
function ensureInvoke() {
|
||||
if (!invoke) {
|
||||
@@ -6,6 +7,33 @@ function ensureInvoke() {
|
||||
}
|
||||
}
|
||||
|
||||
function ensureDialog() {
|
||||
if (!dialog?.open) {
|
||||
throw new Error("Tauri dialog API is not available.");
|
||||
}
|
||||
}
|
||||
|
||||
export async function browseDirectory(defaultPath = "") {
|
||||
ensureDialog();
|
||||
return dialog.open({
|
||||
directory: true,
|
||||
multiple: false,
|
||||
defaultPath: defaultPath || undefined,
|
||||
});
|
||||
}
|
||||
|
||||
export async function browseApplication(defaultPath = "") {
|
||||
ensureDialog();
|
||||
return dialog.open({
|
||||
directory: false,
|
||||
multiple: false,
|
||||
defaultPath: defaultPath || undefined,
|
||||
filters: [
|
||||
{ name: "Applications", extensions: ["exe", "cmd", "bat", "app"] },
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
export async function runGitClone(repoUrl, destinationPath, gitPath) {
|
||||
ensureInvoke();
|
||||
return invoke("git_clone", {
|
||||
@@ -131,6 +159,11 @@ export async function openInExternalEditor(repoPath, editorPath) {
|
||||
return invoke("open_in_external_editor", { repoPath, editorPath });
|
||||
}
|
||||
|
||||
export async function scanInstalledIdes() {
|
||||
ensureInvoke();
|
||||
return invoke("scan_installed_ides");
|
||||
}
|
||||
|
||||
export async function scanLocalRepos(roots = [], allowedRemoteUrls = [], gitPath = "", maxDepth = 4, maxResults = 200) {
|
||||
ensureInvoke();
|
||||
return invoke("scan_local_repos", {
|
||||
|
||||
Reference in New Issue
Block a user