feat(ui): add init repository dialog
Add a modal dialog to initialize a new Git repository, replacing the previous prompt-based flow. It integrates with the app state and invokes the initialization operation then opens the repository on confirmation. Also adds styles, keyboard handling, accessibility hints, a folder browser, initial-branch input, and German/English labels with busy state. - New dialog component with folder picker, branch input, and i18n. - Wire dialog open/close and confirm handler to init and open actions. - Add CSS rules for dialog layout, controls, and error/busy states.
This commit is contained in:
+23
-4
@@ -26,6 +26,7 @@
|
|||||||
import DiscardConfirmDialog from "./lib/components/DiscardConfirmDialog.svelte";
|
import DiscardConfirmDialog from "./lib/components/DiscardConfirmDialog.svelte";
|
||||||
import ExplorerPanel from "./lib/components/ExplorerPanel.svelte";
|
import ExplorerPanel from "./lib/components/ExplorerPanel.svelte";
|
||||||
import HistoryPanel from "./lib/components/HistoryPanel.svelte";
|
import HistoryPanel from "./lib/components/HistoryPanel.svelte";
|
||||||
|
import InitRepositoryDialog from "./lib/components/InitRepositoryDialog.svelte";
|
||||||
import NewBranchDialog from "./lib/components/NewBranchDialog.svelte";
|
import NewBranchDialog from "./lib/components/NewBranchDialog.svelte";
|
||||||
import RenameBranchDialog from "./lib/components/RenameBranchDialog.svelte";
|
import RenameBranchDialog from "./lib/components/RenameBranchDialog.svelte";
|
||||||
import ReflogDialog from "./lib/components/ReflogDialog.svelte";
|
import ReflogDialog from "./lib/components/ReflogDialog.svelte";
|
||||||
@@ -313,6 +314,7 @@
|
|||||||
let repoStatusCache: Record<string, RepoTab> = {};
|
let repoStatusCache: Record<string, RepoTab> = {};
|
||||||
let repoSearch = "";
|
let repoSearch = "";
|
||||||
let cloneDialogOpen = false;
|
let cloneDialogOpen = false;
|
||||||
|
let initRepositoryDialogOpen = false;
|
||||||
let pullStrategy: PullStrategy = (localStorage.getItem("gitlite.pullStrategy") as PullStrategy) || "merge";
|
let pullStrategy: PullStrategy = (localStorage.getItem("gitlite.pullStrategy") as PullStrategy) || "merge";
|
||||||
let selectedRemote = localStorage.getItem("gitlite.selectedRemote") || "";
|
let selectedRemote = localStorage.getItem("gitlite.selectedRemote") || "";
|
||||||
let remoteActionForceWithLease = false;
|
let remoteActionForceWithLease = false;
|
||||||
@@ -3926,10 +3928,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function initializeRepository() {
|
async function initializeRepository() {
|
||||||
const selected = await openDialog({ directory: true, multiple: false, title: "Choose an empty or existing folder" });
|
initRepositoryDialogOpen = true;
|
||||||
if (typeof selected !== "string") return;
|
}
|
||||||
const branch = window.prompt("Initial branch name", "main")?.trim(); if (!branch) return;
|
|
||||||
await runOperation("Initializing repository", async () => { await initRepository(selected, branch); await openRepo(selected); });
|
async function confirmInitializeRepository(path: string, branch: string) {
|
||||||
|
const selected = path.trim();
|
||||||
|
if (!selected) return;
|
||||||
|
await runOperation("Initializing repository", async () => {
|
||||||
|
await initRepository(selected, branch);
|
||||||
|
initRepositoryDialogOpen = false;
|
||||||
|
await openRepo(selected);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function revertHistoryCommit(commit: GitCommit) {
|
async function revertHistoryCommit(commit: GitCommit) {
|
||||||
@@ -6090,6 +6099,16 @@
|
|||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<!-- Initialize repository dialog -->
|
||||||
|
{#if initRepositoryDialogOpen}
|
||||||
|
<InitRepositoryDialog
|
||||||
|
isBusy={operation === "Initializing repository"}
|
||||||
|
language={appLanguage}
|
||||||
|
onInit={confirmInitializeRepository}
|
||||||
|
onClose={() => { if (!isBusy) initRepositoryDialogOpen = false; }}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<!-- Full-screen overlay while a repository is being opened -->
|
<!-- Full-screen overlay while a repository is being opened -->
|
||||||
{#if openingRepo}
|
{#if openingRepo}
|
||||||
<RepoLoadingOverlay repoName={repoDisplayName} />
|
<RepoLoadingOverlay repoName={repoDisplayName} />
|
||||||
|
|||||||
+35
@@ -3878,6 +3878,13 @@
|
|||||||
max-height: calc(100vh - 32px);
|
max-height: calc(100vh - 32px);
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
.init-repository-dialog {
|
||||||
|
display: block;
|
||||||
|
width: min(540px, calc(100vw - 32px));
|
||||||
|
height: auto;
|
||||||
|
max-height: calc(100vh - 32px);
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
.rename-branch-dialog {
|
.rename-branch-dialog {
|
||||||
display: block;
|
display: block;
|
||||||
width: min(520px, calc(100vw - 32px));
|
width: min(520px, calc(100vw - 32px));
|
||||||
@@ -4172,6 +4179,34 @@
|
|||||||
gap: 14px;
|
gap: 14px;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
|
.init-repository-header { padding: 16px 18px; }
|
||||||
|
.init-repository-heading { display: flex; align-items: center; gap: 12px; }
|
||||||
|
.init-repository-heading h2 { margin: 2px 0 0; color: var(--color-ink); font-size: 15px; line-height: 1.25; }
|
||||||
|
.init-repository-icon {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
border: 1px solid color-mix(in srgb, var(--color-accent) 34%, var(--color-border));
|
||||||
|
border-radius: 9px;
|
||||||
|
color: var(--color-accent);
|
||||||
|
background: color-mix(in srgb, var(--color-accent) 10%, var(--color-surface-raised));
|
||||||
|
}
|
||||||
|
.init-repository-form { display: flex; flex-direction: column; gap: 16px; padding: 18px; }
|
||||||
|
.init-repository-description { margin: 0; color: var(--color-ink-dim); font-size: 11.5px; line-height: 1.5; }
|
||||||
|
.init-repository-field > span { font-size: 9.5px; }
|
||||||
|
.init-repository-field > div { position: relative; align-items: center; }
|
||||||
|
.init-repository-field > div > svg { position: absolute; left: 11px; z-index: 1; color: var(--color-ink-faint); pointer-events: none; }
|
||||||
|
.init-repository-field > div > input { height: 36px; padding-left: 36px; font-family: var(--font-mono); font-size: 12px; }
|
||||||
|
.init-repository-field small { color: var(--color-ink-faint); font-size: 9.5px; line-height: 1.4; }
|
||||||
|
.init-repository-path-field { padding: 12px; border: 1px solid var(--color-border-subtle); border-radius: 9px; background: var(--color-surface-raised); }
|
||||||
|
.init-repository-path-control { display: grid !important; grid-template-columns: minmax(0, 1fr) auto; gap: 8px; }
|
||||||
|
.init-repository-path-control > input { grid-column: 1; }
|
||||||
|
.init-repository-browse { grid-column: 2; display: inline-flex; align-items: center; gap: 6px; height: 36px; font-size: 11.5px; white-space: nowrap; }
|
||||||
|
.init-repository-error { color: var(--color-danger, #ff6b78) !important; }
|
||||||
|
.init-repository-actions { padding-top: 2px; }
|
||||||
|
.init-repository-actions > button { font-size: 11.5px; }
|
||||||
.rename-branch-form {
|
.rename-branch-form {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -0,0 +1,149 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import { open as openDialog } from "@tauri-apps/plugin-dialog";
|
||||||
|
import { Folder, FolderOpen, GitBranch, LoaderCircle, Plus, X } from "@lucide/svelte";
|
||||||
|
import type { AppLanguage } from "../types";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
isBusy: boolean;
|
||||||
|
language: AppLanguage;
|
||||||
|
onInit: (path: string, branch: string) => void;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
let {
|
||||||
|
isBusy = false,
|
||||||
|
language = "en",
|
||||||
|
onInit = () => {},
|
||||||
|
onClose = () => {},
|
||||||
|
}: Props = $props();
|
||||||
|
|
||||||
|
let path = $state("");
|
||||||
|
let branch = $state("main");
|
||||||
|
let browseError = $state("");
|
||||||
|
const isGerman = $derived(language === "de");
|
||||||
|
const pathPlaceholder = navigator.userAgent.includes("Windows")
|
||||||
|
? "C:\\Projects\\my-repository"
|
||||||
|
: "/home/user/projects/my-repository";
|
||||||
|
|
||||||
|
function submit(event: SubmitEvent) {
|
||||||
|
event.preventDefault();
|
||||||
|
const targetPath = path.trim();
|
||||||
|
const branchName = branch.trim();
|
||||||
|
if (!targetPath || !branchName || isBusy) return;
|
||||||
|
onInit(targetPath, branchName);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleKeydown(event: KeyboardEvent) {
|
||||||
|
if (event.key === "Escape" && !isBusy) onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function chooseFolder() {
|
||||||
|
if (isBusy) return;
|
||||||
|
browseError = "";
|
||||||
|
try {
|
||||||
|
const selected = await openDialog({
|
||||||
|
title: isGerman ? "Ordner für das neue Repository auswählen" : "Select folder for the new repository",
|
||||||
|
directory: true,
|
||||||
|
multiple: false,
|
||||||
|
defaultPath: path.trim() || undefined,
|
||||||
|
});
|
||||||
|
if (typeof selected === "string") path = selected;
|
||||||
|
} catch (error) {
|
||||||
|
browseError = error instanceof Error ? error.message : String(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
window.addEventListener("keydown", handleKeydown);
|
||||||
|
return () => window.removeEventListener("keydown", handleKeydown);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="dialog-backdrop app-chrome-backdrop" role="presentation">
|
||||||
|
<div
|
||||||
|
class="dialog init-repository-dialog"
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
aria-labelledby="init-repository-title"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<header class="dialog-header init-repository-header">
|
||||||
|
<div class="init-repository-heading">
|
||||||
|
<span class="init-repository-icon" aria-hidden="true"><Plus size={18} /></span>
|
||||||
|
<div>
|
||||||
|
<span class="eyebrow">{isGerman ? "Neues Repository" : "New repository"}</span>
|
||||||
|
<h2 id="init-repository-title">{isGerman ? "Repository initialisieren" : "Initialize repository"}</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="dialog-close" type="button" onclick={onClose} disabled={isBusy} title={isGerman ? "Schließen" : "Close"}>
|
||||||
|
<X size={18} aria-hidden="true" />
|
||||||
|
</button>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<form class="init-repository-form" onsubmit={submit}>
|
||||||
|
<p class="init-repository-description">
|
||||||
|
{isGerman
|
||||||
|
? "GitLite richtet in diesem Ordner ein neues Git-Repository ein. Vorhandene Dateien bleiben unverändert."
|
||||||
|
: "GitLite will create a new Git repository in this folder. Existing files will remain unchanged."}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<label class="new-branch-field init-repository-field init-repository-path-field">
|
||||||
|
<span>{isGerman ? "Zielordner" : "Repository folder"}</span>
|
||||||
|
<div class="init-repository-path-control">
|
||||||
|
<Folder size={16} aria-hidden="true" />
|
||||||
|
<!-- svelte-ignore a11y_autofocus -->
|
||||||
|
<input
|
||||||
|
bind:value={path}
|
||||||
|
autocomplete="off"
|
||||||
|
spellcheck="false"
|
||||||
|
placeholder={pathPlaceholder}
|
||||||
|
disabled={isBusy}
|
||||||
|
autofocus
|
||||||
|
aria-describedby="init-repository-path-hint"
|
||||||
|
/>
|
||||||
|
<button class="btn-secondary init-repository-browse" type="button" onclick={chooseFolder} disabled={isBusy}>
|
||||||
|
<FolderOpen size={15} aria-hidden="true" />
|
||||||
|
{isGerman ? "Auswählen…" : "Browse…"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<small id="init-repository-path-hint">
|
||||||
|
{isGerman ? "Der Ordner wird angelegt, falls er noch nicht existiert." : "The folder will be created if it does not exist."}
|
||||||
|
</small>
|
||||||
|
{#if browseError}<small class="init-repository-error" role="alert">{browseError}</small>{/if}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="new-branch-field init-repository-field">
|
||||||
|
<span>{isGerman ? "Name des ersten Branches" : "Initial branch name"}</span>
|
||||||
|
<div>
|
||||||
|
<GitBranch size={16} aria-hidden="true" />
|
||||||
|
<input
|
||||||
|
bind:value={branch}
|
||||||
|
autocomplete="off"
|
||||||
|
spellcheck="false"
|
||||||
|
placeholder="main"
|
||||||
|
disabled={isBusy}
|
||||||
|
aria-describedby="init-repository-branch-hint"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<small id="init-repository-branch-hint">
|
||||||
|
{isGerman ? "Du kannst den Branch später jederzeit umbenennen." : "You can rename the branch at any time."}
|
||||||
|
</small>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="new-branch-actions init-repository-actions">
|
||||||
|
<button class="btn-secondary" type="button" onclick={onClose} disabled={isBusy}>
|
||||||
|
{isGerman ? "Abbrechen" : "Cancel"}
|
||||||
|
</button>
|
||||||
|
<button class="btn-primary" type="submit" disabled={isBusy || path.trim().length === 0 || branch.trim().length === 0}>
|
||||||
|
{#if isBusy}
|
||||||
|
<LoaderCircle class="spin" size={16} aria-hidden="true" />
|
||||||
|
{:else}
|
||||||
|
<Plus size={16} aria-hidden="true" />
|
||||||
|
{/if}
|
||||||
|
{isGerman ? "Repository erstellen" : "Create repository"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
Reference in New Issue
Block a user