This update introduces functionality for resizing various panels in the UI, including the left sidebar, branch panel, stash panel, and file history. It also adds state persistence for panel sizes and collapsed states using local storage, improving user experience by maintaining preferences across sessions. - Implemented resizing functionality for multiple UI panels - Added local storage support for panel dimensions and collapsed states - Enhanced accessibility features for panel controls
173 lines
5.8 KiB
Svelte
173 lines
5.8 KiB
Svelte
<script lang="ts">
|
|
import { ChevronLeft, ChevronRight, 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;
|
|
collapsed?: boolean;
|
|
onToggleCollapsed?: () => void;
|
|
}
|
|
|
|
let {
|
|
fileHistory = [],
|
|
selectedExplorerPath = "",
|
|
selectedExplorerLabel = "File history",
|
|
hasRepository = false,
|
|
isBusy = false,
|
|
isLoading = false,
|
|
onDiff = () => {},
|
|
onRestore = () => {},
|
|
collapsed = false,
|
|
onToggleCollapsed = () => {},
|
|
}: 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 file-history-panel grid grid-rows-[auto_1fr] overflow-hidden" class:collapsed aria-label="Selected file history">
|
|
{#if collapsed}
|
|
<button
|
|
class="file-history-rail-toggle"
|
|
type="button"
|
|
onclick={onToggleCollapsed}
|
|
aria-expanded={!collapsed}
|
|
title="Expand file history"
|
|
aria-label="Expand file history panel"
|
|
>
|
|
<ChevronLeft size={16} aria-hidden="true" />
|
|
<span>File history</span>
|
|
{#if selectedExplorerPath}
|
|
<small>{fileHistory.length}</small>
|
|
{/if}
|
|
</button>
|
|
{:else}
|
|
<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>
|
|
<div class="file-history-head-actions">
|
|
<span class="pill pill-count file-history-count">{fileHistory.length}</span>
|
|
<button
|
|
class="file-history-toggle panel-collapse-toggle"
|
|
type="button"
|
|
onclick={onToggleCollapsed}
|
|
aria-expanded={!collapsed}
|
|
title="Collapse file history"
|
|
aria-label="Collapse file history panel"
|
|
>
|
|
<ChevronRight size={14} aria-hidden="true" />
|
|
</button>
|
|
</div>
|
|
</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}
|
|
{/if}
|
|
</section>
|