This update introduces a context menu for repository tabs, allowing users to easily close individual tabs, close all other tabs, or close tabs to the right. Additionally, several visual elements related to loading animations have been removed to streamline the user interface. - Implement context menu actions for repository tab management - Remove unnecessary loading animations from the splash screen and overlays - Enhance user experience with improved tab management functionality
140 lines
4.8 KiB
Svelte
140 lines
4.8 KiB
Svelte
<script lang="ts">
|
|
import { GitCompare, History, RotateCcw } from "@lucide/svelte";
|
|
import iconUrl from "../../../src-tauri/icons/icon.png";
|
|
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">
|
|
<div class="file-history-loading-mark" aria-hidden="true">
|
|
<span class="file-history-loading-halo"></span>
|
|
<svg class="file-history-loading-traces" viewBox="0 0 120 120">
|
|
<path class="fhl-trace fhl-trace-main" d="M14 82 C38 60, 48 58, 60 60 S84 66, 106 36" />
|
|
<path class="fhl-trace fhl-trace-branch" d="M28 36 C52 48, 70 70, 91 94" />
|
|
</svg>
|
|
<img src={iconUrl} alt="" class="file-history-loading-icon" />
|
|
</div>
|
|
<span class="file-history-loading-label">Loading history…</span>
|
|
<span class="file-history-loading-bar" aria-hidden="true"><span></span></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>
|