145 lines
5.0 KiB
Svelte
145 lines
5.0 KiB
Svelte
<script lang="ts">
|
|
import { GitCompare, History, RotateCcw } from "@lucide/svelte";
|
|
import type { GitCommit } from "../types";
|
|
|
|
interface Props {
|
|
fileHistory: GitCommit[];
|
|
selectedExplorerPath: string;
|
|
selectedExplorerLabel: string;
|
|
hasRepository: boolean;
|
|
isBusy: boolean;
|
|
isLoading?: boolean;
|
|
onDiff: (commit: GitCommit) => void;
|
|
onRestore: (commit: GitCommit) => void;
|
|
}
|
|
|
|
let {
|
|
fileHistory = [],
|
|
selectedExplorerPath = "",
|
|
selectedExplorerLabel = "File history",
|
|
hasRepository = false,
|
|
isBusy = false,
|
|
isLoading = false,
|
|
onDiff = () => {},
|
|
onRestore = () => {},
|
|
}: Props = $props();
|
|
|
|
function formatCommitDate(value: string): string {
|
|
const date = new Date(value);
|
|
if (Number.isNaN(date.getTime())) return value;
|
|
return new Intl.DateTimeFormat(undefined, { dateStyle: "medium", timeStyle: "short" }).format(date);
|
|
}
|
|
|
|
function fileName(path: string): string {
|
|
return path.split("/").pop() ?? path;
|
|
}
|
|
|
|
function pathTooltip(node: HTMLElement, text: string) {
|
|
let el: HTMLDivElement | null = null;
|
|
|
|
function show(e: MouseEvent) {
|
|
if (!text || !text.includes("/")) return;
|
|
el = document.createElement("div");
|
|
el.className = "path-tooltip";
|
|
el.textContent = text;
|
|
document.body.appendChild(el);
|
|
move(e);
|
|
}
|
|
|
|
function move(e: MouseEvent) {
|
|
if (!el) return;
|
|
const tw = el.offsetWidth;
|
|
const left = Math.max(8, Math.min(e.clientX - tw / 2, window.innerWidth - tw - 8));
|
|
el.style.left = `${left}px`;
|
|
el.style.top = `${e.clientY - 44}px`;
|
|
}
|
|
|
|
function hide() {
|
|
el?.remove();
|
|
el = null;
|
|
}
|
|
|
|
node.addEventListener("mouseenter", show);
|
|
node.addEventListener("mousemove", move);
|
|
node.addEventListener("mouseleave", hide);
|
|
|
|
return {
|
|
update(v: string) { text = v; },
|
|
destroy() {
|
|
hide();
|
|
node.removeEventListener("mouseenter", show);
|
|
node.removeEventListener("mousemove", move);
|
|
node.removeEventListener("mouseleave", hide);
|
|
},
|
|
};
|
|
}
|
|
</script>
|
|
|
|
<section class="panel grid grid-rows-[auto_1fr] overflow-hidden" aria-label="Selected file history">
|
|
<div class="section-head file-history-head">
|
|
<div class="file-history-heading">
|
|
<span class="eyebrow">{selectedExplorerLabel}</span>
|
|
<h2
|
|
class="file-history-name"
|
|
use:pathTooltip={selectedExplorerPath}
|
|
>{selectedExplorerPath ? fileName(selectedExplorerPath) : "No file"}</h2>
|
|
</div>
|
|
<span class="pill pill-count file-history-count">{fileHistory.length}</span>
|
|
</div>
|
|
|
|
{#if !hasRepository}
|
|
<div class="blank-state">No repository loaded.</div>
|
|
{:else if !selectedExplorerPath}
|
|
<div class="blank-state">Select a file in Explorer.</div>
|
|
{:else if isLoading}
|
|
<div class="file-history-loading" role="status" aria-live="polite">
|
|
<svg class="file-history-loading-graph" viewBox="0 0 120 120" aria-hidden="true">
|
|
<defs>
|
|
<linearGradient id="fhl-line" x1="0" y1="0" x2="1" y2="1">
|
|
<stop offset="0%" stop-color="#646cff" />
|
|
<stop offset="100%" stop-color="#41d1ff" />
|
|
</linearGradient>
|
|
</defs>
|
|
<circle class="fhl-ring" cx="60" cy="60" r="52" />
|
|
<path class="fhl-trunk" d="M42 22 L42 98" />
|
|
<path class="fhl-branch" d="M42 44 C42 62, 82 58, 82 76 L82 88" />
|
|
<circle class="fhl-node fhl-n1" cx="42" cy="30" r="6" />
|
|
<circle class="fhl-node fhl-n2" cx="42" cy="60" r="6" />
|
|
<circle class="fhl-node fhl-n3" cx="82" cy="88" r="6" />
|
|
<circle class="fhl-node fhl-n4" cx="42" cy="90" r="6" />
|
|
</svg>
|
|
<span class="file-history-loading-label">Loading history…</span>
|
|
</div>
|
|
{:else if fileHistory.length === 0}
|
|
<div class="blank-state">No history returned for this selection.</div>
|
|
{:else}
|
|
<div class="history-list overflow-auto p-2">
|
|
{#each fileHistory as item (item.hash)}
|
|
<article class="commit-row compact file-history-row">
|
|
<div class="commit-line">
|
|
<History size={16} aria-hidden="true" />
|
|
<div class="commit-line-text">
|
|
<strong title={item.summary}>{item.summary}</strong>
|
|
<span>{item.short_hash} - {item.author_name}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="commit-actions file-history-actions">
|
|
<time datetime={item.date}>{formatCommitDate(item.date)}</time>
|
|
<div class="commit-action-buttons">
|
|
<button class="btn-sm" type="button" onclick={() => onDiff(item)} disabled={isBusy} title="Show changes vs working tree">
|
|
<GitCompare size={15} aria-hidden="true" />
|
|
Diff
|
|
</button>
|
|
<button class="btn-sm" type="button" onclick={() => onRestore(item)} disabled={isBusy} title="Restore selected file from this commit">
|
|
<RotateCcw size={15} aria-hidden="true" />
|
|
Restore
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</article>
|
|
{/each}
|
|
</div>
|
|
{/if}
|
|
</section>
|