Introduces a new "Repository Management" view for browsing open and recent repositories. Adds a tabbed interface for quickly switching between multiple active repositories, with persistent state. Also includes a new command to open the current repository in the native file explorer.
210 lines
6.7 KiB
Svelte
210 lines
6.7 KiB
Svelte
<script lang="ts">
|
|
import { onDestroy, onMount } from "svelte";
|
|
import { getCurrentWindow } from "@tauri-apps/api/window";
|
|
import { Download, FolderOpen, GitBranch, GitCompare, LoaderCircle, Minus, RefreshCw, Search, Upload, X } from "@lucide/svelte";
|
|
|
|
export let branch: string = "";
|
|
export let ahead: number = 0;
|
|
export let behind: number = 0;
|
|
export let repoName: string = "";
|
|
export let hasRepository: boolean = false;
|
|
export let isBusy: boolean = false;
|
|
export let operation: string = "";
|
|
export let autoRefreshEnabled: boolean = true;
|
|
export let autoRefreshInFlight: boolean = false;
|
|
export let onPull: () => void = () => {};
|
|
export let onPush: () => void = () => {};
|
|
export let onRefresh: () => void = () => {};
|
|
export let onSearch: () => void = () => {};
|
|
export let onCompare: () => void = () => {};
|
|
export let onOpenInExplorer: () => void = () => {};
|
|
export let onToggleAutoRefresh: () => void = () => {};
|
|
|
|
const win = getCurrentWindow();
|
|
let isMaximized = false;
|
|
let unlisten: (() => void) | undefined;
|
|
|
|
onMount(async () => {
|
|
isMaximized = await win.isMaximized();
|
|
unlisten = await win.onResized(async () => {
|
|
isMaximized = await win.isMaximized();
|
|
});
|
|
});
|
|
|
|
onDestroy(() => {
|
|
unlisten?.();
|
|
});
|
|
</script>
|
|
|
|
<header class="titlebar" data-tauri-drag-region>
|
|
<!-- Brand -->
|
|
<div class="titlebar-brand" data-tauri-drag-region>
|
|
<svg
|
|
width="15"
|
|
height="15"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
aria-hidden="true"
|
|
data-tauri-drag-region
|
|
>
|
|
<circle cx="12" cy="18" r="3" />
|
|
<circle cx="6" cy="6" r="3" />
|
|
<circle cx="18" cy="6" r="3" />
|
|
<path d="M18 9v1a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2V9" />
|
|
<line x1="12" y1="12" x2="12" y2="15" />
|
|
</svg>
|
|
<span data-tauri-drag-region>GitLite</span>
|
|
</div>
|
|
|
|
<!-- Center: repo + branch info -->
|
|
<div class="titlebar-info" data-tauri-drag-region>
|
|
{#if hasRepository}
|
|
{#if repoName}
|
|
<span class="tb-repo" data-tauri-drag-region>{repoName}</span>
|
|
<span class="tb-sep" data-tauri-drag-region aria-hidden="true">/</span>
|
|
{/if}
|
|
<GitBranch size={12} aria-hidden="true" />
|
|
<span class="tb-branch" data-tauri-drag-region>{branch}</span>
|
|
{#if ahead > 0}
|
|
<span class="tb-sync ahead" title="{ahead} commits ahead">↑{ahead}</span>
|
|
{/if}
|
|
{#if behind > 0}
|
|
<span class="tb-sync behind" title="{behind} commits behind">↓{behind}</span>
|
|
{/if}
|
|
{:else}
|
|
<span class="tb-no-repo" data-tauri-drag-region>No repository open</span>
|
|
{/if}
|
|
</div>
|
|
|
|
<!-- Right: actions + window controls -->
|
|
<div class="titlebar-right">
|
|
<div class="titlebar-actions" role="toolbar" aria-label="Repository actions">
|
|
<button
|
|
class="tb-action"
|
|
onclick={onOpenInExplorer}
|
|
disabled={!hasRepository || isBusy}
|
|
title="Open repository in Explorer"
|
|
aria-label="Open repository in Explorer"
|
|
>
|
|
<FolderOpen size={14} aria-hidden="true" />
|
|
<span class="tb-action-label">Explorer</span>
|
|
</button>
|
|
|
|
<button
|
|
class="tb-action"
|
|
onclick={onSearch}
|
|
disabled={!hasRepository || isBusy}
|
|
title="Global search"
|
|
aria-label="Global search"
|
|
>
|
|
<Search size={14} aria-hidden="true" />
|
|
<span class="tb-action-label">Search</span>
|
|
</button>
|
|
|
|
<button
|
|
class="tb-action"
|
|
onclick={onCompare}
|
|
disabled={!hasRepository || isBusy}
|
|
title="Compare commits"
|
|
aria-label="Compare commits"
|
|
>
|
|
<GitCompare size={14} aria-hidden="true" />
|
|
<span class="tb-action-label">Compare</span>
|
|
</button>
|
|
|
|
<button
|
|
class="tb-action"
|
|
onclick={onPull}
|
|
disabled={!hasRepository || isBusy}
|
|
title="Pull"
|
|
aria-label="Pull"
|
|
>
|
|
{#if operation === "Pulling"}
|
|
<LoaderCircle class="spin" size={14} aria-hidden="true" />
|
|
{:else}
|
|
<Download size={14} aria-hidden="true" />
|
|
{/if}
|
|
<span class="tb-action-label">Pull</span>
|
|
</button>
|
|
|
|
<button
|
|
class="tb-action"
|
|
onclick={onPush}
|
|
disabled={!hasRepository || isBusy}
|
|
title="Push"
|
|
aria-label="Push"
|
|
>
|
|
{#if operation === "Pushing"}
|
|
<LoaderCircle class="spin" size={14} aria-hidden="true" />
|
|
{:else}
|
|
<Upload size={14} aria-hidden="true" />
|
|
{/if}
|
|
<span class="tb-action-label">Push</span>
|
|
</button>
|
|
|
|
<button
|
|
class="tb-action"
|
|
onclick={onRefresh}
|
|
disabled={isBusy || !hasRepository}
|
|
title="Refresh"
|
|
aria-label="Refresh"
|
|
>
|
|
<RefreshCw
|
|
class={operation === "Refreshing" ? "spin" : ""}
|
|
size={14}
|
|
aria-hidden="true"
|
|
/>
|
|
<span class="tb-action-label">Refresh</span>
|
|
</button>
|
|
|
|
<button
|
|
class="tb-action auto-toggle"
|
|
class:active={autoRefreshEnabled}
|
|
onclick={onToggleAutoRefresh}
|
|
aria-pressed={autoRefreshEnabled}
|
|
title={autoRefreshEnabled ? "Auto-refresh on — click to disable" : "Auto-refresh off — click to enable"}
|
|
aria-label="Toggle auto-refresh"
|
|
>
|
|
<RefreshCw
|
|
class={autoRefreshInFlight ? "spin" : ""}
|
|
size={14}
|
|
aria-hidden="true"
|
|
/>
|
|
<span class="tb-action-label">Auto</span>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="titlebar-divider" aria-hidden="true"></div>
|
|
|
|
<div class="titlebar-controls">
|
|
<button class="tb-btn" onclick={() => win.minimize()} title="Minimize" aria-label="Minimize">
|
|
<Minus size={12} aria-hidden="true" />
|
|
</button>
|
|
<button
|
|
class="tb-btn"
|
|
onclick={() => win.toggleMaximize()}
|
|
title={isMaximized ? "Restore" : "Maximize"}
|
|
aria-label={isMaximized ? "Restore" : "Maximize"}
|
|
>
|
|
{#if isMaximized}
|
|
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true">
|
|
<rect x="3" y="1" width="8" height="8" rx="1" stroke="currentColor" stroke-width="1.5" />
|
|
<path d="M1 3v7a1 1 0 0 0 1 1h7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
|
|
</svg>
|
|
{:else}
|
|
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true">
|
|
<rect x="1" y="1" width="10" height="10" rx="1" stroke="currentColor" stroke-width="1.5" />
|
|
</svg>
|
|
{/if}
|
|
</button>
|
|
<button class="tb-btn close" onclick={() => win.close()} title="Close" aria-label="Close">
|
|
<X size={12} aria-hidden="true" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</header>
|