Adds Git LFS support to the backend API and related UI. The app now exposes commands to inspect, install, track and pull. A sidecar git-lfs binary is bundled and a prep script is added. This prepares the correct binary for each target platform. - Expose Git LFS status and management commands in API - Bundle and prepare a sidecar git-lfs binary for targets - Update packaging, docs, and README with LFS notes
251 lines
10 KiB
Svelte
251 lines
10 KiB
Svelte
<script lang="ts">
|
|
import {
|
|
Box,
|
|
ChevronDown,
|
|
Code2,
|
|
CloudDownload,
|
|
CloudOff,
|
|
Download,
|
|
FolderOpen,
|
|
GitCompare,
|
|
History,
|
|
ListRestart,
|
|
LoaderCircle,
|
|
RefreshCw,
|
|
Search,
|
|
Upload,
|
|
Settings2,
|
|
Terminal,
|
|
} from "@lucide/svelte";
|
|
|
|
export let hasRepository: boolean = false;
|
|
export let isBusy: boolean = false;
|
|
export let operation: string = "";
|
|
export let ahead: number = 0;
|
|
export let behind: number = 0;
|
|
export let localOnly: boolean = false;
|
|
export let language: "en" | "de" = "en";
|
|
export let editorName: string = "Editor";
|
|
export let terminalName: string = "Terminal";
|
|
export let fileManagerName: string = "Explorer";
|
|
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 onInteractiveRebase: () => void = () => {};
|
|
export let onReflog: () => void = () => {};
|
|
export let onOpenInExplorer: () => void = () => {};
|
|
export let onOpenInEditor: () => void = () => {};
|
|
export let onOpenTerminal: () => void = () => {};
|
|
export let onFetchPrune: () => void = () => {};
|
|
export let onForcePush: () => void = () => {};
|
|
export let onSyncOptions: () => void = () => {};
|
|
export let onOpenLfs: () => void = () => {};
|
|
|
|
let historyOpen = false;
|
|
let syncOpen = false;
|
|
let toolbarElement: HTMLDivElement;
|
|
|
|
$: isGerman = language === "de";
|
|
$: pushLabel = localOnly ? (isGerman ? "Veröffentlichen" : "Publish") : "Push";
|
|
$: pushTitle = localOnly
|
|
? (isGerman
|
|
? "Dieser Branch existiert nur lokal. Veröffentlichen erstellt den Remote-Branch und richtet das Tracking ein."
|
|
: "This branch exists only locally. Publish creates the remote branch and configures tracking.")
|
|
: "Push";
|
|
|
|
function runHistoryAction(action: () => void) {
|
|
historyOpen = false;
|
|
action();
|
|
}
|
|
|
|
function handleWindowClick(event: MouseEvent) {
|
|
if (toolbarElement && !toolbarElement.contains(event.target as Node)) { historyOpen = false; syncOpen = false; }
|
|
}
|
|
|
|
function handleWindowKeydown(event: KeyboardEvent) {
|
|
if (event.key === "Escape" && historyOpen) {
|
|
event.stopPropagation();
|
|
historyOpen = false;
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<svelte:window onclick={handleWindowClick} onkeydown={handleWindowKeydown} />
|
|
|
|
<div bind:this={toolbarElement} class="repo-toolbar" role="toolbar" aria-label={isGerman ? "Repository-Aktionen" : "Repository actions"}>
|
|
<div class="repo-toolbar-sync">
|
|
<div class="repo-action-group repo-sync-actions">
|
|
<button
|
|
class="repo-action fetch"
|
|
onclick={onFetch}
|
|
disabled={!hasRepository || isBusy}
|
|
title="Fetch"
|
|
aria-label="Fetch"
|
|
>
|
|
{#if operation === "Fetching"}
|
|
<LoaderCircle class="spin" size={15} aria-hidden="true" />
|
|
{:else}
|
|
<CloudDownload size={15} aria-hidden="true" />
|
|
{/if}
|
|
<span class="repo-action-label">Fetch</span>
|
|
</button>
|
|
|
|
<button
|
|
class="repo-action sync-primary"
|
|
onclick={onPull}
|
|
disabled={!hasRepository || isBusy}
|
|
title="Pull"
|
|
aria-label={behind > 0
|
|
? `Pull, ${behind} ${isGerman ? (behind === 1 ? "Remote-Commit voraus" : "Remote-Commits voraus") : (behind === 1 ? "commit behind" : "commits behind")}`
|
|
: "Pull"}
|
|
>
|
|
{#if operation === "Pulling"}
|
|
<LoaderCircle class="spin" size={15} aria-hidden="true" />
|
|
{:else}
|
|
<Download size={15} aria-hidden="true" />
|
|
{/if}
|
|
<span class="repo-action-label">Pull</span>
|
|
{#if behind > 0}<span class="repo-action-count behind">↓{behind}</span>{/if}
|
|
</button>
|
|
|
|
<button
|
|
class="repo-action sync-primary"
|
|
class:publish-local={localOnly}
|
|
onclick={onPush}
|
|
disabled={!hasRepository || isBusy}
|
|
title={pushTitle}
|
|
aria-label={localOnly
|
|
? pushTitle
|
|
: ahead > 0
|
|
? `Push, ${ahead} ${isGerman ? (ahead === 1 ? "lokaler Commit voraus" : "lokale Commits voraus") : (ahead === 1 ? "commit ahead" : "commits ahead")}`
|
|
: "Push"}
|
|
>
|
|
{#if operation === "Pushing"}
|
|
<LoaderCircle class="spin" size={15} aria-hidden="true" />
|
|
{:else}
|
|
<Upload size={15} aria-hidden="true" />
|
|
{/if}
|
|
<span class="repo-action-label">{pushLabel}</span>
|
|
{#if localOnly}
|
|
<span class="repo-action-local-marker"><CloudOff size={9} aria-hidden="true" />{isGerman ? "NUR LOKAL" : "LOCAL"}</span>
|
|
{:else if ahead > 0}
|
|
<span class="repo-action-count ahead">↑{ahead}</span>
|
|
{/if}
|
|
</button>
|
|
<div class="repo-history-wrap">
|
|
<button class="repo-action" type="button" onclick={() => { syncOpen = !syncOpen; historyOpen = false; }} disabled={!hasRepository || isBusy} aria-label={isGerman ? "Sync-Optionen" : "Sync options"} aria-haspopup="menu">
|
|
<ChevronDown size={14} aria-hidden="true" />
|
|
</button>
|
|
{#if syncOpen}
|
|
<div class="repo-history-menu" role="menu">
|
|
<button type="button" role="menuitem" onclick={() => { syncOpen = false; onFetchPrune(); }}><CloudDownload size={15} /><span><strong>Fetch + Prune</strong><small>{isGerman ? "Veraltete Remote-Branches entfernen" : "Remove stale remote branches"}</small></span></button>
|
|
<button type="button" role="menuitem" onclick={() => { syncOpen = false; onForcePush(); }}><Upload size={15} /><span><strong>Force with lease</strong><small>{isGerman ? "Sicheres Pushen nach Rebase" : "Safe push after rebase"}</small></span></button>
|
|
<button type="button" role="menuitem" onclick={() => { syncOpen = false; onSyncOptions(); }}><Settings2 size={15} /><span><strong>{isGerman ? "Remotes & Strategien" : "Remotes & strategies"}</strong><small>{isGerman ? "Upstream, Pull und Remote verwalten" : "Manage upstream, pull and remotes"}</small></span></button>
|
|
<button type="button" role="menuitem" onclick={() => { syncOpen = false; onOpenLfs(); }}><Box size={15} /><span><strong>Git LFS</strong><small>{isGerman ? "Große Dateien und LFS-Installation verwalten" : "Manage large files and LFS installation"}</small></span></button>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="repo-toolbar-divider" aria-hidden="true"></div>
|
|
|
|
<div class="repo-action-group repo-inspect-actions">
|
|
<button
|
|
class="repo-action"
|
|
onclick={onSearch}
|
|
disabled={!hasRepository || isBusy}
|
|
title={isGerman ? "Globale Suche" : "Global search"}
|
|
aria-label={isGerman ? "Globale Suche" : "Global search"}
|
|
>
|
|
<Search size={15} aria-hidden="true" />
|
|
<span class="repo-action-label">{isGerman ? "Suchen" : "Search"}</span>
|
|
</button>
|
|
|
|
<button
|
|
class="repo-action"
|
|
onclick={onCompare}
|
|
disabled={!hasRepository || isBusy}
|
|
title={isGerman ? "Branches oder Commits vergleichen" : "Compare branches or commits"}
|
|
aria-label={isGerman ? "Branches oder Commits vergleichen" : "Compare branches or commits"}
|
|
>
|
|
<GitCompare size={15} aria-hidden="true" />
|
|
<span class="repo-action-label">{isGerman ? "Vergleichen" : "Compare"}</span>
|
|
</button>
|
|
|
|
<div class="repo-history-wrap">
|
|
<button
|
|
class="repo-action history-trigger"
|
|
class:active={historyOpen}
|
|
type="button"
|
|
onclick={() => { historyOpen = !historyOpen; }}
|
|
disabled={!hasRepository || isBusy}
|
|
aria-haspopup="menu"
|
|
aria-expanded={historyOpen}
|
|
title={isGerman ? "Verlauf und Rebase" : "History and rebase"}
|
|
>
|
|
<History size={15} aria-hidden="true" />
|
|
<span class="repo-action-label">{isGerman ? "Verlauf" : "History"}</span>
|
|
<ChevronDown class="history-chevron" size={13} aria-hidden="true" />
|
|
</button>
|
|
|
|
{#if historyOpen}
|
|
<div class="repo-history-menu" role="menu">
|
|
<button type="button" role="menuitem" onclick={() => runHistoryAction(onReflog)}>
|
|
<History size={15} aria-hidden="true" />
|
|
<span>
|
|
<strong>Reflog</strong>
|
|
<small>{isGerman ? "Lokale Referenzbewegungen" : "Local reference movements"}</small>
|
|
</span>
|
|
</button>
|
|
<button type="button" role="menuitem" onclick={() => runHistoryAction(onInteractiveRebase)}>
|
|
<ListRestart size={15} aria-hidden="true" />
|
|
<span>
|
|
<strong>{isGerman ? "Interaktiver Rebase" : "Interactive rebase"}</strong>
|
|
<small>{isGerman ? "Commits ordnen und zusammenfassen" : "Reorder and combine commits"}</small>
|
|
</span>
|
|
</button>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="repo-toolbar-spacer"></div>
|
|
<div class="repo-toolbar-divider utility" aria-hidden="true"></div>
|
|
|
|
<div class="repo-action-group repo-utility-actions">
|
|
<button class="repo-action" onclick={onOpenInEditor} disabled={!hasRepository || isBusy} title={isGerman ? `Repository in ${editorName} öffnen` : `Open repository in ${editorName}`}>
|
|
<Code2 size={15} aria-hidden="true" />
|
|
<span class="repo-action-label utility-label">{editorName}</span>
|
|
</button>
|
|
<button class="repo-action" onclick={onOpenTerminal} disabled={!hasRepository || isBusy} title={isGerman ? `${terminalName} im Repository öffnen` : `Open ${terminalName} in repository`}>
|
|
<Terminal size={15} aria-hidden="true" />
|
|
<span class="repo-action-label utility-label">{terminalName}</span>
|
|
</button>
|
|
<button
|
|
class="repo-action"
|
|
onclick={onOpenInExplorer}
|
|
disabled={!hasRepository || isBusy}
|
|
title={isGerman ? `Repository in ${fileManagerName} öffnen` : `Open repository in ${fileManagerName}`}
|
|
aria-label={isGerman ? `Repository in ${fileManagerName} öffnen` : `Open repository in ${fileManagerName}`}
|
|
>
|
|
<FolderOpen size={15} aria-hidden="true" />
|
|
<span class="repo-action-label utility-label">{fileManagerName}</span>
|
|
</button>
|
|
|
|
<button
|
|
class="repo-action"
|
|
onclick={onRefresh}
|
|
disabled={isBusy || !hasRepository}
|
|
title={isGerman ? "Manuell aktualisieren" : "Refresh now"}
|
|
aria-label={isGerman ? "Manuell aktualisieren" : "Refresh now"}
|
|
>
|
|
<RefreshCw class={operation === "Refreshing" ? "spin" : ""} size={16} aria-hidden="true" />
|
|
<span class="repo-action-label utility-label">{isGerman ? "Aktualisieren" : "Refresh"}</span>
|
|
</button>
|
|
</div>
|
|
</div>
|