diff --git a/src/App.svelte b/src/App.svelte index 94d1185..494eccd 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -1,5 +1,6 @@
+
+ +
{/if}
diff --git a/src/lib/components/LinePatchDialog.svelte b/src/lib/components/LinePatchDialog.svelte index c200c4f..7f81838 100644 --- a/src/lib/components/LinePatchDialog.svelte +++ b/src/lib/components/LinePatchDialog.svelte @@ -47,6 +47,7 @@ }: Props = $props(); let parsed = $state({ headerLines: [], hunks: [], binary: false }); + let patchScroll = $state(null); let scopeLabel = $derived(staged ? "Staged changes" : "Unstaged changes"); let displayPath = $derived(file.old_path ? `${file.old_path} -> ${file.path}` : file.path); @@ -118,6 +119,23 @@ if (isBusy || isLoading) return; await onApply(action, buildHunkPatch(hunk)); } + + function hunkPosition(index: number): number { + const totalLines = parsed.hunks.reduce((sum, hunk) => sum + Math.max(hunk.lines.length, 1), 0); + const precedingLines = parsed.hunks.slice(0, index).reduce((sum, hunk) => sum + Math.max(hunk.lines.length, 1), 0); + return (precedingLines / Math.max(totalLines - 1, 1)) * 100; + } + + function hunkKind(hunk: PatchHunk): "add" | "delete" | "mixed" { + const hasAdd = hunk.lines.some((line) => line.kind === "add"); + const hasDelete = hunk.lines.some((line) => line.kind === "delete"); + return hasAdd && hasDelete ? "mixed" : hasAdd ? "add" : "delete"; + } + + function scrollToHunk(hunkId: string) { + const target = patchScroll?.querySelector(`[data-hunk-id="${hunkId}"]`); + if (patchScroll && target) patchScroll.scrollTop = Math.max(target.offsetTop - 8, 0); + } diff --git a/src/lib/components/StatusPanel.svelte b/src/lib/components/StatusPanel.svelte index 2a6027b..df88eb1 100644 --- a/src/lib/components/StatusPanel.svelte +++ b/src/lib/components/StatusPanel.svelte @@ -156,11 +156,11 @@ }); -
+
- Working tree -

Status

+ Workspace +

Changes

{stagedCount} staged @@ -170,26 +170,6 @@ {#if hasRepository && changedFiles.length > 0}
- -
{:else} -
- {#each changedFiles as file (`${file.old_path ?? ""}:${file.path}`)} -
-
- -
+
+
+
+
Unstaged{unstagedCount}
+ +
+
+ {#each changedFiles.filter((file) => file.unstaged !== null) as file (`unstaged:${fileKey(file)}`)} + {@const stageTargets = selectedStageTargets(file)} +
+ + {statusLabel(file.unstaged)} +
+ + + +
+
+ {/each} + {#if unstagedCount === 0}

No unstaged changes.

{/if} +
+
-
-
-
- Staged - {statusLabel(file.staged)} +
+
+
Staged{stagedCount}
+ +
+
+ {#each changedFiles.filter((file) => file.staged !== null) as file (`staged:${fileKey(file)}`)} + {@const unstageTargets = selectedUnstageTargets(file)} +
+ + {statusLabel(file.staged)} +
+ + +
-
- {#if file.staged} - {@const unstageTargets = selectedUnstageTargets(file)} - - - - {:else} - No staged change - {/if} -
-
- -
-
- Unstaged - {statusLabel(file.unstaged)} -
-
- {#if file.unstaged} - {@const stageTargets = selectedStageTargets(file)} - - - - {:else} - No unstaged change - {/if} -
-
-
-
- {/each} + + {/each} + {#if stagedCount === 0}

Stage files to include them in the next commit.

{/if} +
+
{/if}