change to dark mode
This commit is contained in:
@@ -19,7 +19,8 @@
|
|||||||
"Bash(grep -qE \"\\(Running \\\\`target|error\\\\[|panicked|terminated\\)\" /tmp/claude-1000/-mnt-data-Development-GitLite/71a8b49f-ac61-4ad9-aaba-dc0219044038/tasks/bt54rikhh.output)",
|
"Bash(grep -qE \"\\(Running \\\\`target|error\\\\[|panicked|terminated\\)\" /tmp/claude-1000/-mnt-data-Development-GitLite/71a8b49f-ac61-4ad9-aaba-dc0219044038/tasks/bt54rikhh.output)",
|
||||||
"Bash(grep -qE \"\\(Running \\\\`target|error\\\\[|panicked|terminated\\)\" /tmp/claude-1000/-mnt-data-Development-GitLite/71a8b49f-ac61-4ad9-aaba-dc0219044038/tasks/b7nyub68s.output)",
|
"Bash(grep -qE \"\\(Running \\\\`target|error\\\\[|panicked|terminated\\)\" /tmp/claude-1000/-mnt-data-Development-GitLite/71a8b49f-ac61-4ad9-aaba-dc0219044038/tasks/b7nyub68s.output)",
|
||||||
"Bash(npm install *)",
|
"Bash(npm install *)",
|
||||||
"Bash(cargo check *)"
|
"Bash(cargo check *)",
|
||||||
|
"Bash(npx vite *)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-14
@@ -636,7 +636,7 @@
|
|||||||
/>
|
/>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<!-- Center: summary + status + commit + file history + compare -->
|
<!-- Center: summary + status + commit + compare -->
|
||||||
<section class="main-panel" aria-label="Repository status">
|
<section class="main-panel" aria-label="Repository status">
|
||||||
<div class="repo-summary">
|
<div class="repo-summary">
|
||||||
<div class="repo-meta">
|
<div class="repo-meta">
|
||||||
@@ -679,18 +679,6 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="file-history-section">
|
|
||||||
<FileHistoryPanel
|
|
||||||
{fileHistory}
|
|
||||||
{selectedExplorerPath}
|
|
||||||
selectedExplorerLabel={selectedExplorerPath ? `${selectedExplorerKind === "folder" ? "Folder" : "File"} history` : "File history"}
|
|
||||||
{hasRepository}
|
|
||||||
{isBusy}
|
|
||||||
onDiff={diffSelectedFileFromCommit}
|
|
||||||
onRestore={restoreSelectedFileFromCommit}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ComparePanel
|
<ComparePanel
|
||||||
{commits}
|
{commits}
|
||||||
{hasRepository}
|
{hasRepository}
|
||||||
@@ -707,7 +695,7 @@
|
|||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Right sidebar: commit history -->
|
<!-- Right sidebar: commit graph + file history -->
|
||||||
<aside class="history-aside" aria-label="Commit history">
|
<aside class="history-aside" aria-label="Commit history">
|
||||||
<HistoryPanel
|
<HistoryPanel
|
||||||
{commits}
|
{commits}
|
||||||
@@ -722,6 +710,15 @@
|
|||||||
expandedCommitHashes = next;
|
expandedCommitHashes = next;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<FileHistoryPanel
|
||||||
|
{fileHistory}
|
||||||
|
{selectedExplorerPath}
|
||||||
|
selectedExplorerLabel={selectedExplorerPath ? `${selectedExplorerKind === "folder" ? "Folder" : "File"} history` : "File history"}
|
||||||
|
{hasRepository}
|
||||||
|
{isBusy}
|
||||||
|
onDiff={diffSelectedFileFromCommit}
|
||||||
|
onRestore={restoreSelectedFileFromCommit}
|
||||||
|
/>
|
||||||
</aside>
|
</aside>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+436
-281
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { ArrowRight, X } from "@lucide/svelte";
|
import { ArrowRight, FileCode, X } from "@lucide/svelte";
|
||||||
import type { GitCommitComparison, GitDiffFile, FileStatusKind } from "../types";
|
import type { GitCommitComparison, GitDiffFile, FileStatusKind } from "../types";
|
||||||
|
|
||||||
type DiffLineKind = "meta" | "hunk" | "add" | "del" | "context";
|
type SplitRow =
|
||||||
|
| { type: "span"; kind: "meta" | "hunk"; text: string }
|
||||||
|
| {
|
||||||
|
type: "pair";
|
||||||
|
leftNum?: number; leftText?: string; leftKind: "del" | "context" | "empty";
|
||||||
|
rightNum?: number; rightText?: string; rightKind: "add" | "context" | "empty";
|
||||||
|
};
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
comparison: GitCommitComparison;
|
comparison: GitCommitComparison;
|
||||||
@@ -20,69 +26,115 @@
|
|||||||
onSelectFile = () => {},
|
onSelectFile = () => {},
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
function statusLabel(kind: FileStatusKind): string {
|
|
||||||
return kind;
|
|
||||||
}
|
|
||||||
|
|
||||||
function displayDiffFile(file: GitDiffFile): string {
|
function displayDiffFile(file: GitDiffFile): string {
|
||||||
return file.old_path ? `${file.old_path} -> ${file.path}` : file.path;
|
return file.old_path ? `${file.old_path} → ${file.path}` : file.path;
|
||||||
}
|
|
||||||
|
|
||||||
function diffLineKind(line: string): DiffLineKind {
|
|
||||||
if (line.startsWith("diff ") || line.startsWith("index ") || line.startsWith("--- ") ||
|
|
||||||
line.startsWith("+++ ") || line.startsWith("new file") || line.startsWith("deleted file") ||
|
|
||||||
line.startsWith("rename ") || line.startsWith("similarity ")) return "meta";
|
|
||||||
if (line.startsWith("@@")) return "hunk";
|
|
||||||
if (line.startsWith("+")) return "add";
|
|
||||||
if (line.startsWith("-")) return "del";
|
|
||||||
return "context";
|
|
||||||
}
|
|
||||||
|
|
||||||
function diffLines(patch: string): { kind: DiffLineKind; text: string }[] {
|
|
||||||
return patch.replace(/\n$/, "").split("\n").map((text) => ({ kind: diffLineKind(text), text }));
|
|
||||||
}
|
|
||||||
|
|
||||||
function stripDiffPathPrefix(value: string): string {
|
|
||||||
const trimmed = value.trim();
|
|
||||||
if (trimmed === "/dev/null") return trimmed;
|
|
||||||
if (trimmed.startsWith("a/") || trimmed.startsWith("b/")) return trimmed.slice(2);
|
|
||||||
return trimmed;
|
|
||||||
}
|
|
||||||
|
|
||||||
function patchFilePath(segment: string): string {
|
|
||||||
let plusPath = "";
|
|
||||||
let minusPath = "";
|
|
||||||
for (const line of segment.split("\n")) {
|
|
||||||
if (line.startsWith("+++ ")) plusPath = stripDiffPathPrefix(line.slice(4));
|
|
||||||
else if (line.startsWith("--- ")) minusPath = stripDiffPathPrefix(line.slice(4));
|
|
||||||
else if (line.startsWith("@@")) break;
|
|
||||||
}
|
|
||||||
return (plusPath && plusPath !== "/dev/null") ? plusPath : minusPath;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildDiffByPath(patch: string): Map<string, string> {
|
function buildDiffByPath(patch: string): Map<string, string> {
|
||||||
const map = new Map<string, string>();
|
const map = new Map<string, string>();
|
||||||
if (!patch.trim()) return map;
|
if (!patch.trim()) return map;
|
||||||
const segments: string[] = [];
|
|
||||||
let current: string[] = [];
|
let current: string[] = [];
|
||||||
for (const line of patch.split("\n")) {
|
for (const line of patch.split("\n")) {
|
||||||
if (line.startsWith("diff --git ") && current.length > 0) {
|
if (line.startsWith("diff --git ") && current.length > 0) {
|
||||||
segments.push(current.join("\n"));
|
const seg = current.join("\n");
|
||||||
|
const p = segmentPath(seg);
|
||||||
|
if (p) map.set(p, seg);
|
||||||
current = [];
|
current = [];
|
||||||
}
|
}
|
||||||
current.push(line);
|
current.push(line);
|
||||||
}
|
}
|
||||||
if (current.length > 0) segments.push(current.join("\n"));
|
if (current.length > 0) {
|
||||||
for (const segment of segments) {
|
const seg = current.join("\n");
|
||||||
const path = patchFilePath(segment);
|
const p = segmentPath(seg);
|
||||||
if (path) map.set(path, segment);
|
if (p) map.set(p, seg);
|
||||||
}
|
}
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function segmentPath(seg: string): string {
|
||||||
|
let plus = "";
|
||||||
|
let minus = "";
|
||||||
|
for (const line of seg.split("\n")) {
|
||||||
|
if (line.startsWith("+++ ")) {
|
||||||
|
const r = line.slice(4).trim();
|
||||||
|
plus = r.startsWith("b/") ? r.slice(2) : r;
|
||||||
|
} else if (line.startsWith("--- ")) {
|
||||||
|
const r = line.slice(4).trim();
|
||||||
|
minus = r.startsWith("a/") ? r.slice(2) : r;
|
||||||
|
} else if (line.startsWith("@@")) break;
|
||||||
|
}
|
||||||
|
return (plus && plus !== "/dev/null") ? plus : minus;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildSplitRows(patch: string): SplitRow[] {
|
||||||
|
if (!patch.trim()) return [];
|
||||||
|
const lines = patch.replace(/\n$/, "").split("\n");
|
||||||
|
const rows: SplitRow[] = [];
|
||||||
|
let leftNum = 0;
|
||||||
|
let rightNum = 0;
|
||||||
|
const dels: string[] = [];
|
||||||
|
const adds: string[] = [];
|
||||||
|
|
||||||
|
function flush() {
|
||||||
|
if (dels.length === 0 && adds.length === 0) return;
|
||||||
|
const count = Math.max(dels.length, adds.length);
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const hasDel = i < dels.length;
|
||||||
|
const hasAdd = i < adds.length;
|
||||||
|
if (hasDel) leftNum++;
|
||||||
|
if (hasAdd) rightNum++;
|
||||||
|
rows.push({
|
||||||
|
type: "pair",
|
||||||
|
leftNum: hasDel ? leftNum : undefined,
|
||||||
|
leftText: hasDel ? dels[i].slice(1) : undefined,
|
||||||
|
leftKind: hasDel ? "del" : "empty",
|
||||||
|
rightNum: hasAdd ? rightNum : undefined,
|
||||||
|
rightText: hasAdd ? adds[i].slice(1) : undefined,
|
||||||
|
rightKind: hasAdd ? "add" : "empty",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
dels.length = 0;
|
||||||
|
adds.length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
const isMeta =
|
||||||
|
line.startsWith("diff ") || line.startsWith("index ") ||
|
||||||
|
line.startsWith("--- ") || line.startsWith("+++ ") ||
|
||||||
|
line.startsWith("new file") || line.startsWith("deleted file") ||
|
||||||
|
line.startsWith("rename ") || line.startsWith("similarity ");
|
||||||
|
|
||||||
|
if (isMeta) {
|
||||||
|
flush();
|
||||||
|
rows.push({ type: "span", kind: "meta", text: line });
|
||||||
|
} else if (line.startsWith("@@")) {
|
||||||
|
flush();
|
||||||
|
const m = line.match(/@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
|
||||||
|
if (m) { leftNum = parseInt(m[1]) - 1; rightNum = parseInt(m[2]) - 1; }
|
||||||
|
rows.push({ type: "span", kind: "hunk", text: line });
|
||||||
|
} else if (line.startsWith("-")) {
|
||||||
|
dels.push(line);
|
||||||
|
} else if (line.startsWith("+")) {
|
||||||
|
adds.push(line);
|
||||||
|
} else {
|
||||||
|
flush();
|
||||||
|
leftNum++;
|
||||||
|
rightNum++;
|
||||||
|
rows.push({
|
||||||
|
type: "pair",
|
||||||
|
leftNum, leftText: line.slice(1), leftKind: "context",
|
||||||
|
rightNum, rightText: line.slice(1), rightKind: "context",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flush();
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
let diffByPath = $derived(buildDiffByPath(comparison.patch));
|
let diffByPath = $derived(buildDiffByPath(comparison.patch));
|
||||||
let selectedDiffFile = $derived(comparison.files.find((f) => f.path === selectedDiffPath) ?? null);
|
let selectedFile = $derived(comparison.files.find((f) => f.path === selectedDiffPath) ?? null);
|
||||||
let selectedDiffPatch = $derived(selectedDiffFile ? (diffByPath.get(selectedDiffFile.path) ?? "") : "");
|
let selectedPatch = $derived(selectedFile ? (diffByPath.get(selectedFile.path) ?? "") : "");
|
||||||
|
let splitRows = $derived(buildSplitRows(selectedPatch));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -91,12 +143,13 @@
|
|||||||
onclick={(e) => { if (e.target === e.currentTarget) onClose(); }}
|
onclick={(e) => { if (e.target === e.currentTarget) onClose(); }}
|
||||||
>
|
>
|
||||||
<div class="dialog" role="dialog" aria-modal="true" aria-label="Commit comparison" tabindex="-1">
|
<div class="dialog" role="dialog" aria-modal="true" aria-label="Commit comparison" tabindex="-1">
|
||||||
|
|
||||||
<header class="dialog-header">
|
<header class="dialog-header">
|
||||||
<div>
|
<div>
|
||||||
<span class="eyebrow">Compare</span>
|
<span class="eyebrow">Compare</span>
|
||||||
<h2 class="dialog-range">
|
<h2 class="dialog-range">
|
||||||
<span class="hash">{comparison.from_short}</span>
|
<span class="hash">{comparison.from_short}</span>
|
||||||
<ArrowRight size={16} aria-hidden="true" />
|
<ArrowRight size={14} aria-hidden="true" />
|
||||||
<span class="hash">{comparison.to_short}</span>
|
<span class="hash">{comparison.to_short}</span>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
@@ -106,9 +159,11 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
{#if comparison.files.length === 0}
|
{#if comparison.files.length === 0}
|
||||||
<div class="blank-state">No differences to show — these versions are identical.</div>
|
<div class="blank-state">No differences — these versions are identical.</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="dialog-body">
|
<div class="dialog-body">
|
||||||
|
|
||||||
|
<!-- File list -->
|
||||||
<aside class="dialog-files" aria-label="Changed files">
|
<aside class="dialog-files" aria-label="Changed files">
|
||||||
{#each comparison.files as file (`${file.old_path ?? ""}:${file.path}`)}
|
{#each comparison.files as file (`${file.old_path ?? ""}:${file.path}`)}
|
||||||
<button
|
<button
|
||||||
@@ -118,7 +173,7 @@
|
|||||||
onclick={() => onSelectFile(file)}
|
onclick={() => onSelectFile(file)}
|
||||||
title={displayDiffFile(file)}
|
title={displayDiffFile(file)}
|
||||||
>
|
>
|
||||||
<span class={`status-badge ${file.status}`}>{statusLabel(file.status)}</span>
|
<span class={`status-badge ${file.status}`}>{file.status}</span>
|
||||||
<strong>{displayDiffFile(file)}</strong>
|
<strong>{displayDiffFile(file)}</strong>
|
||||||
<span class="diff-counts">
|
<span class="diff-counts">
|
||||||
<span class="adds">+{file.additions}</span>
|
<span class="adds">+{file.additions}</span>
|
||||||
@@ -128,15 +183,52 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<div class="dialog-diff" aria-label="File diff">
|
<!-- Diff pane -->
|
||||||
{#if !selectedDiffFile}
|
<div class="dialog-diff">
|
||||||
|
{#if !selectedFile}
|
||||||
<div class="blank-state">Select a file to see its changes.</div>
|
<div class="blank-state">Select a file to see its changes.</div>
|
||||||
{:else if selectedDiffPatch.trim().length === 0}
|
{:else if splitRows.length === 0}
|
||||||
<div class="blank-state">No textual changes for this file.</div>
|
<div class="blank-state">No textual changes for this file.</div>
|
||||||
{:else}
|
{:else}
|
||||||
<pre class="diff-view" aria-label="Unified diff">{#each diffLines(selectedDiffPatch) as line}<span class={`diff-line ${line.kind}`}>{line.text || " "}</span>{/each}</pre>
|
<!-- Path bar -->
|
||||||
|
<div class="diff-header">
|
||||||
|
<FileCode size={13} aria-hidden="true" />
|
||||||
|
<span>{displayDiffFile(selectedFile)}</span>
|
||||||
|
<span class="diff-counts" style="margin-left: auto; flex-shrink: 0;">
|
||||||
|
<span class="adds">+{selectedFile.additions}</span>
|
||||||
|
<span class="dels">-{selectedFile.deletions}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Column headers -->
|
||||||
|
<div class="split-col-headers">
|
||||||
|
<div class="split-col-label">
|
||||||
|
<span>Before</span>
|
||||||
|
<span class="split-col-hash">{comparison.from_short}</span>
|
||||||
|
</div>
|
||||||
|
<div class="split-col-label">
|
||||||
|
<span>After</span>
|
||||||
|
<span class="split-col-hash">{comparison.to_short}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Split diff grid -->
|
||||||
|
<div class="split-diff" role="table" aria-label="Side-by-side diff">
|
||||||
|
{#each splitRows as row, i (i)}
|
||||||
|
{#if row.type === "span"}
|
||||||
|
<div class="split-span split-{row.kind}">{row.text}</div>
|
||||||
|
{:else}
|
||||||
|
<div class="split-num" class:del={row.leftKind === "del"} class:empty={row.leftKind === "empty"}>{row.leftNum ?? ""}</div>
|
||||||
|
<div class="split-cell" class:del={row.leftKind === "del"} class:empty={row.leftKind === "empty"}>{row.leftText ?? " "}</div>
|
||||||
|
<div class="split-divider"></div>
|
||||||
|
<div class="split-num" class:add={row.rightKind === "add"} class:empty={row.rightKind === "empty"}>{row.rightNum ?? ""}</div>
|
||||||
|
<div class="split-cell" class:add={row.rightKind === "add"} class:empty={row.rightKind === "empty"}>{row.rightText ?? " "}</div>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user