Files
GitLite/src/lib/TitleBar.svelte
T
Christoph Brandau d0bca62362 refactor(ui): Improve title bar layout using flex and grid
The structure of the application title bar has been updated to utilize modern CSS layout techniques like Grid and Flexbox. This refactoring improves the responsiveness and organization of key elements such as repository name, branch indicator, and synchronization status. The changes ensure better alignment and handling of varying content lengths across different viewports.

- Restructures repo/branch info into a dedicated context container
- Uses flex properties for dynamic sizing of title bar sections
- Groups sync indicators into a cohesive unit
2026-07-10 22:21:53 +02:00

257 lines
8.0 KiB
Svelte

<script lang="ts">
import { onDestroy, onMount } from "svelte";
import { getVersion } from "@tauri-apps/api/app";
import { getCurrentWindow } from "@tauri-apps/api/window";
import { CloudDownload, Download, FolderOpen, GitBranch, GitCompare, LoaderCircle, Minus, RefreshCw, Search, Settings, Upload, X } from "@lucide/svelte";
import iconUrl from "../../src-tauri/icons/icon.png";
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 onFetch: () => void = () => {};
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 = () => {};
export let onOpenSettings: () => void = () => {};
let win: ReturnType<typeof getCurrentWindow> | null = null;
let isMaximized = false;
let unlisten: (() => void) | undefined;
let appVersion = "";
onMount(async () => {
try {
win = getCurrentWindow();
isMaximized = await win.isMaximized();
unlisten = await win.onResized(async () => {
isMaximized = await win?.isMaximized() ?? false;
});
} catch {
win = null;
}
try {
appVersion = await getVersion();
} catch {
appVersion = "";
}
});
onDestroy(() => {
unlisten?.();
});
function minimizeWindow() {
void win?.minimize();
}
function toggleMaximizeWindow() {
void win?.toggleMaximize();
}
function closeWindow() {
void win?.close();
}
</script>
<header class="titlebar" data-tauri-drag-region>
<!-- Brand -->
<div class="titlebar-brand" data-tauri-drag-region>
<span class="titlebar-brand-icon" data-tauri-drag-region>
<img src={iconUrl} alt="" data-tauri-drag-region />
</span>
<span data-tauri-drag-region>Gitty</span>
{#if appVersion}
<span class="tb-version" data-tauri-drag-region title="Version {appVersion}">v{appVersion}</span>
{/if}
</div>
<!-- Center: repo + branch info -->
<div class="titlebar-info" data-tauri-drag-region>
{#if hasRepository}
<div class="titlebar-context" data-tauri-drag-region>
{#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 title={branch}>{branch}</span>
</div>
{#if ahead > 0 || behind > 0}
<div class="tb-sync-group" aria-label="Branch synchronization status">
{#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}
</div>
{/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={onFetch}
disabled={!hasRepository || isBusy}
title="Fetch"
aria-label="Fetch"
>
{#if operation === "Fetching"}
<LoaderCircle class="spin" size={14} aria-hidden="true" />
{:else}
<CloudDownload size={14} aria-hidden="true" />
{/if}
<span class="tb-action-label">Fetch</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>
<button
class="tb-action"
onclick={onOpenSettings}
title="Settings"
aria-label="Settings"
>
<Settings size={14} aria-hidden="true" />
<span class="tb-action-label">Settings</span>
</button>
</div>
<div class="titlebar-divider" aria-hidden="true"></div>
<div class="titlebar-controls">
<button class="tb-btn" onclick={minimizeWindow} title="Minimize" aria-label="Minimize">
<Minus size={12} aria-hidden="true" />
</button>
<button
class="tb-btn"
onclick={toggleMaximizeWindow}
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={closeWindow} title="Close" aria-label="Close">
<X size={12} aria-hidden="true" />
</button>
</div>
</div>
</header>