feat(dialog): add file history dialog component

This commit is contained in:
2026-08-04 18:41:19 +02:00
parent 6dd70ec52a
commit 41f474f1eb
+106
View File
@@ -0,0 +1,106 @@
<script lang="ts">
import { AlertCircle, GitCompare, History, LoaderCircle, RotateCcw, X } from "@lucide/svelte";
import type { GitCommit } from "../types";
const commitDateFormatter = new Intl.DateTimeFormat(undefined, {
dateStyle: "medium",
timeStyle: "short",
});
interface Props {
fileHistory: GitCommit[];
filePath: string;
isBusy: boolean;
isLoading: boolean;
error?: string;
onDiff: (commit: GitCommit) => void;
onRestore: (commit: GitCommit) => void;
onClose: () => void;
}
let {
fileHistory = [],
filePath = "",
isBusy = false,
isLoading = false,
error = "",
onDiff = () => {},
onRestore = () => {},
onClose = () => {},
}: Props = $props();
function formatCommitDate(value: string): string {
const date = new Date(value);
if (Number.isNaN(date.getTime())) return value;
return commitDateFormatter.format(date);
}
function fileName(path: string): string {
return path.split(/[\\/]/).pop() ?? path;
}
function closeFromBackdrop(event: MouseEvent) {
if (event.target === event.currentTarget && !isBusy) onClose();
}
</script>
<div class="dialog-backdrop app-chrome-backdrop file-history-dialog-backdrop" role="presentation" onclick={closeFromBackdrop}>
<div class="file-history-dialog" role="dialog" aria-modal="true" aria-labelledby="file-history-dialog-title" tabindex="-1">
<header class="file-history-dialog-header">
<div class="file-history-dialog-icon" aria-hidden="true"><History size={19} /></div>
<div class="file-history-dialog-heading">
<span class="eyebrow">File history</span>
<h2 id="file-history-dialog-title">{fileName(filePath)}</h2>
<span class="file-history-dialog-path" title={filePath}>{filePath}</span>
</div>
<button class="dialog-icon-button" type="button" onclick={onClose} disabled={isBusy} aria-label="Close file history">
<X size={16} aria-hidden="true" />
</button>
</header>
<div class="file-history-dialog-body">
{#if isLoading}
<div class="file-history-dialog-state" role="status" aria-live="polite">
<LoaderCircle class="spin" size={22} aria-hidden="true" />
<strong>Loading file history…</strong>
<span>Reading commits that changed this file.</span>
</div>
{:else if error}
<div class="file-history-dialog-state error" role="alert">
<AlertCircle size={22} aria-hidden="true" />
<strong>File history could not be loaded.</strong>
<span>{error}</span>
</div>
{:else if fileHistory.length === 0}
<div class="file-history-dialog-state">
<History size={22} aria-hidden="true" />
<strong>No history found.</strong>
<span>This file has no committed changes yet.</span>
</div>
{:else}
<div class="file-history-dialog-list">
{#each fileHistory as item (item.hash)}
<article class="file-history-dialog-row">
<div class="file-history-dialog-commit">
<span class="commit-avatar" aria-hidden="true">{item.author_name.trim().slice(0, 2).toUpperCase() || "?"}</span>
<div>
<strong title={item.summary}>{item.summary}</strong>
<span><code>{item.short_hash}</code> · {item.author_name}</span>
</div>
</div>
<time datetime={item.date}>{formatCommitDate(item.date)}</time>
<div class="file-history-dialog-actions">
<button class="btn-sm" type="button" onclick={() => onDiff(item)} disabled={isBusy} title="Show changes against the working tree">
<GitCompare size={14} aria-hidden="true" /> Diff
</button>
<button class="btn-sm" type="button" onclick={() => onRestore(item)} disabled={isBusy} title="Restore this file from the selected commit">
<RotateCcw size={14} aria-hidden="true" /> Restore
</button>
</div>
</article>
{/each}
</div>
{/if}
</div>
</div>
</div>