This commit is contained in:
2026-06-27 23:08:19 +02:00
parent 25700821d1
commit 5b44a65e51
22 changed files with 3388 additions and 3538 deletions
+143
View File
@@ -0,0 +1,143 @@
<script lang="ts">
import { Check, RotateCcw, Undo2 } from "@lucide/svelte";
import type { FileStatusKind, GitFileStatus, GitStatus } from "../types";
interface Props {
changedFiles: GitFileStatus[];
stagedCount: number;
unstagedCount: number;
hasRepository: boolean;
isBusy: boolean;
status: GitStatus | null;
onStage: (file: GitFileStatus) => void;
onUnstage: (file: GitFileStatus) => void;
onDiscard: (file: GitFileStatus, staged: boolean) => void;
onStageAll: () => void;
onUnstageAll: () => void;
}
let {
changedFiles = [],
stagedCount = 0,
unstagedCount = 0,
hasRepository = false,
isBusy = false,
status = null,
onStage = () => {},
onUnstage = () => {},
onDiscard = () => {},
onStageAll = () => {},
onUnstageAll = () => {},
}: Props = $props();
function statusLabel(kind: FileStatusKind | null): string {
return kind ?? "none";
}
function displayPath(file: GitFileStatus): string {
return file.old_path ? `${file.old_path} -> ${file.path}` : file.path;
}
let hasUnstaged = $derived(changedFiles.some((f) => f.unstaged !== null));
let hasStaged = $derived(changedFiles.some((f) => f.staged !== null));
</script>
<section class="panel grid grid-rows-[auto_auto_1fr] overflow-hidden" aria-label="Working tree status">
<div class="section-head">
<div>
<span class="eyebrow">Working tree</span>
<h2 class="mt-0.5 text-ink text-base font-bold leading-tight">Status</h2>
</div>
<div class="flex items-center gap-1.5">
<span class="pill pill-count">{stagedCount} staged</span>
<span class="pill pill-count">{unstagedCount} unstaged</span>
</div>
</div>
{#if hasRepository && changedFiles.length > 0}
<div class="status-toolbar">
<button
class="btn-sm"
type="button"
onclick={onStageAll}
disabled={isBusy || !hasUnstaged}
title="Stage all unstaged files"
>
<Check size={14} aria-hidden="true" />
Stage all
</button>
<button
class="btn-sm"
type="button"
onclick={onUnstageAll}
disabled={isBusy || !hasStaged}
title="Unstage all staged files"
>
<Undo2 size={14} aria-hidden="true" />
Unstage all
</button>
</div>
{/if}
{#if !hasRepository}
<div class="blank-state">No repository loaded.</div>
{:else if status?.clean}
<div class="blank-state">Working tree is clean.</div>
{:else if changedFiles.length === 0}
<div class="blank-state">No file changes returned.</div>
{:else}
<div class="overflow-auto p-2">
{#each changedFiles as file (`${file.old_path ?? ""}:${file.path}`)}
<article class="file-row">
<div class="file-title">
<strong title={displayPath(file)}>{displayPath(file)}</strong>
</div>
<div class="change-lanes">
<div class="change-lane" class:inactive={!file.staged}>
<div class="lane-header">
<span class="lane-name">Staged</span>
<span class={`status-badge ${file.staged ?? "none"}`}>{statusLabel(file.staged)}</span>
</div>
<div class="lane-actions">
{#if file.staged}
<button class="btn-sm" type="button" onclick={() => onUnstage(file)} disabled={isBusy} title="Unstage file">
<Undo2 size={14} aria-hidden="true" />
Unstage
</button>
<button class="btn-sm" type="button" onclick={() => onDiscard(file, true)} disabled={isBusy} title="Discard staged changes">
<RotateCcw size={14} aria-hidden="true" />
Discard
</button>
{:else}
<span class="quiet">No staged change</span>
{/if}
</div>
</div>
<div class="change-lane" class:inactive={!file.unstaged}>
<div class="lane-header">
<span class="lane-name">Unstaged</span>
<span class={`status-badge ${file.unstaged ?? "none"}`}>{statusLabel(file.unstaged)}</span>
</div>
<div class="lane-actions">
{#if file.unstaged}
<button class="btn-sm" type="button" onclick={() => onStage(file)} disabled={isBusy} title="Stage file">
<Check size={14} aria-hidden="true" />
Stage
</button>
<button class="btn-sm" type="button" onclick={() => onDiscard(file, false)} disabled={isBusy} title="Discard unstaged changes">
<RotateCcw size={14} aria-hidden="true" />
Discard
</button>
{:else}
<span class="quiet">No unstaged change</span>
{/if}
</div>
</div>
</div>
</article>
{/each}
</div>
{/if}
</section>