refactor(app): remove file history panel and related logic

This commit is contained in:
2026-08-04 18:41:19 +02:00
parent 90070697dd
commit 6dd70ec52a
2 changed files with 62 additions and 297 deletions
+62 -120
View File
@@ -20,7 +20,6 @@
import CredentialDialog from "./lib/components/CredentialDialog.svelte";
import DiscardConfirmDialog from "./lib/components/DiscardConfirmDialog.svelte";
import ExplorerPanel from "./lib/components/ExplorerPanel.svelte";
import FileHistoryPanel from "./lib/components/FileHistoryPanel.svelte";
import HistoryPanel from "./lib/components/HistoryPanel.svelte";
import NewBranchDialog from "./lib/components/NewBranchDialog.svelte";
import RenameBranchDialog from "./lib/components/RenameBranchDialog.svelte";
@@ -220,7 +219,6 @@
const STASH_PANEL_COLLAPSED_KEY = "gitlite.stashPanelCollapsed.v2";
const EXPLORER_PANEL_COLLAPSED_KEY = "gitlite.explorerPanelCollapsed.v1";
const HISTORY_ASIDE_WIDTH_KEY = "gitlite.historyAsideWidth.v1";
const FILE_HISTORY_WIDTH_KEY = "gitlite.fileHistoryWidth.v1";
const COMMIT_PANEL_DEFAULT_HEIGHT = 220;
const COMMIT_PANEL_MIN_HEIGHT = COMMIT_PANEL_DEFAULT_HEIGHT;
const COMMIT_PANEL_MAX_HEIGHT = 640;
@@ -237,9 +235,6 @@
const HISTORY_ASIDE_DEFAULT_WIDTH = 620;
const HISTORY_ASIDE_MIN_WIDTH = 560;
const HISTORY_ASIDE_MAX_WIDTH = 920;
const FILE_HISTORY_DEFAULT_WIDTH = 300;
const FILE_HISTORY_MIN_WIDTH = 240;
const FILE_HISTORY_MAX_WIDTH = 520;
const ERROR_AUTO_HIDE_MS = 6000;
const COMMIT_HISTORY_PAGE_SIZE = 50;
@@ -282,6 +277,8 @@
let expandedCommitHashes = new Set<string>();
let fileHistory: GitCommit[] = [];
let fileHistoryLoading = false;
let fileHistoryError = "";
let fileHistoryDialogOpen = false;
let fileHistoryRequestId = 0;
let activeFileHistoryRequestId = "";
let repoOpenRequestId = 0;
@@ -414,11 +411,6 @@
let resizingHistoryAside = false;
let historyResizeStartX = 0;
let historyResizeStartWidth = 0;
let fileHistoryWidth = loadFileHistoryWidth();
let resizingFileHistory = false;
let fileHistoryResizeStartX = 0;
let fileHistoryResizeStartWidth = 0;
let fileHistoryCollapsed = true;
let themeMediaQuery: MediaQueryList | undefined;
let appVersion = "";
@@ -1512,28 +1504,6 @@
}
}
function clampFileHistoryWidth(value: number): number {
return Math.min(FILE_HISTORY_MAX_WIDTH, Math.max(FILE_HISTORY_MIN_WIDTH, Math.round(value)));
}
function loadFileHistoryWidth(): number {
try {
const stored = Number(localStorage.getItem(FILE_HISTORY_WIDTH_KEY));
if (Number.isFinite(stored) && stored > 0) return clampFileHistoryWidth(stored);
} catch {
// Fall through to the default below.
}
return FILE_HISTORY_DEFAULT_WIDTH;
}
function persistFileHistoryWidth(value: number) {
try {
localStorage.setItem(FILE_HISTORY_WIDTH_KEY, String(value));
} catch {
// Local storage is best-effort only; resizing must keep working without it.
}
}
function startCommitPanelResize(event: PointerEvent) {
event.preventDefault();
resizingCommitPanel = true;
@@ -1715,50 +1685,14 @@
persistHistoryAsideWidth(historyAsideWidth);
}
function startFileHistoryResize(event: PointerEvent) {
if (fileHistoryCollapsed) return;
event.preventDefault();
resizingFileHistory = true;
fileHistoryResizeStartX = event.clientX;
fileHistoryResizeStartWidth = fileHistoryWidth;
(event.currentTarget as HTMLElement).setPointerCapture(event.pointerId);
}
function onFileHistoryResizeMove(event: PointerEvent) {
if (!resizingFileHistory) return;
fileHistoryWidth = clampFileHistoryWidth(fileHistoryResizeStartWidth + (fileHistoryResizeStartX - event.clientX));
}
function endFileHistoryResize(event: PointerEvent) {
if (!resizingFileHistory) return;
resizingFileHistory = false;
persistFileHistoryWidth(fileHistoryWidth);
const target = event.currentTarget as HTMLElement;
if (target.hasPointerCapture(event.pointerId)) target.releasePointerCapture(event.pointerId);
}
function onFileHistoryResizeKeydown(event: KeyboardEvent) {
if (fileHistoryCollapsed || (event.key !== "ArrowLeft" && event.key !== "ArrowRight")) return;
event.preventDefault();
fileHistoryWidth = clampFileHistoryWidth(fileHistoryWidth + (event.key === "ArrowLeft" ? 20 : -20));
persistFileHistoryWidth(fileHistoryWidth);
}
function toggleFileHistoryCollapsed() {
fileHistoryCollapsed = !fileHistoryCollapsed;
}
function revealFileHistory() {
fileHistoryCollapsed = false;
}
function hideAndResetFileHistory() {
fileHistoryRequestId += 1;
cancelActiveFileHistoryLoad();
activeFileHistoryRequestId = "";
fileHistoryLoading = false;
fileHistoryError = "";
fileHistory = [];
fileHistoryCollapsed = true;
fileHistoryDialogOpen = false;
}
function rememberRecentRepo(path: string) {
@@ -1833,6 +1767,9 @@
expandedExplorerPaths = new Set();
expandedCommitHashes = new Set();
fileHistory = [];
fileHistoryLoading = false;
fileHistoryError = "";
fileHistoryDialogOpen = false;
compareFrom = "";
compareTo = "";
comparison = null;
@@ -2004,10 +1941,7 @@
if (selectedExplorerPath && !explorerPathExists(repoFiles, selectedExplorerPath)) {
selectedExplorerPath = "";
selectedExplorerKind = "file";
cancelActiveFileHistoryLoad();
activeFileHistoryRequestId = "";
fileHistoryLoading = false;
fileHistory = [];
hideAndResetFileHistory();
}
}
@@ -2016,7 +1950,7 @@
await refreshBranchList(path);
await refreshTags(path);
await refreshCommitHistory(path);
if (lastFileHistoryHeadHash !== previousHeadHash && !fileHistoryCollapsed) {
if (lastFileHistoryHeadHash !== previousHeadHash && fileHistoryDialogOpen) {
await refreshFileHistory(path);
}
}
@@ -2031,7 +1965,9 @@
void cancelFileHistory(requestId).catch(() => {});
}
async function refreshFileHistory(path = activeRepoPath, file = selectedExplorerPath) {
async function refreshFileHistory(path = activeRepoPath, file = selectedExplorerPath, force = false) {
if (!force && !fileHistoryDialogOpen && !globalSearchOpen) return;
const requestId = ++fileHistoryRequestId;
cancelActiveFileHistoryLoad();
@@ -2045,6 +1981,7 @@
const historyRequestId = `file-history-${requestId}-${Date.now()}`;
activeFileHistoryRequestId = historyRequestId;
fileHistoryLoading = true;
fileHistoryError = "";
fileHistory = [];
try {
@@ -2056,7 +1993,10 @@
const message = errorToMessage(error);
if (requestId === fileHistoryRequestId && activeFileHistoryRequestId === historyRequestId) {
fileHistory = [];
if (!isCancellationMessage(message)) errorMessage = message;
if (!isCancellationMessage(message)) {
fileHistoryError = message;
if (!fileHistoryDialogOpen) errorMessage = message;
}
}
} finally {
if (requestId === fileHistoryRequestId && activeFileHistoryRequestId === historyRequestId) {
@@ -3718,19 +3658,37 @@
return folders;
}
// Loads history for a selected explorer node without blocking the rest of the UI
// (isBusy/runOperation would disable every button in the app while this awaits).
// A request id guards against a slower, stale request overwriting a newer selection.
// Loads history without blocking the rest of the UI. A request id guards
// against a slower, stale request overwriting a newer file selection.
async function loadSelectedFileHistory(path: string, repo = activeRepoPath) {
await refreshFileHistory(repo, path);
await refreshFileHistory(repo, path, true);
}
function openFileHistoryDialog(node: ExplorerNode) {
if (!activeRepoPath || node.kind !== "file" || !node.tracked) return;
selectedExplorerPath = node.path;
selectedExplorerKind = "file";
fileHistoryDialogOpen = true;
fileHistoryError = "";
void loadSelectedFileHistory(node.path);
trackEvent("file_history_opened", {
source: "explorer_context_menu",
});
}
function closeFileHistoryDialog() {
fileHistoryRequestId += 1;
cancelActiveFileHistoryLoad();
activeFileHistoryRequestId = "";
fileHistoryLoading = false;
fileHistoryError = "";
fileHistoryDialogOpen = false;
}
async function selectExplorerNode(node: ExplorerNode) {
if (!activeRepoPath) return;
selectedExplorerPath = node.path;
selectedExplorerKind = node.kind;
revealFileHistory();
void loadSelectedFileHistory(node.path);
trackEvent("explorer_node_selected", {
kind: node.kind,
tracked: node.tracked ? 1 : 0,
@@ -3742,7 +3700,6 @@
selectedExplorerPath = file.path;
selectedExplorerKind = "file";
expandedExplorerPaths = new Set([...expandedExplorerPaths, ...explorerParentFolders(file.path)]);
revealFileHistory();
void loadSelectedFileHistory(file.path);
trackEvent("explorer_file_selected", {
@@ -3767,8 +3724,6 @@
if (!activeRepoPath || node.kind !== "file") return;
selectedExplorerPath = node.path;
selectedExplorerKind = "file";
revealFileHistory();
void loadSelectedFileHistory(node.path);
try {
await openRepositoryFile(activeRepoPath, node.path);
@@ -4012,7 +3967,8 @@
helpOpen = false;
return;
}
if (event.key === "Escape" && repoTabContextMenu) closeRepoTabContextMenu();
if (event.key === "Escape" && fileHistoryDialogOpen && !isBusy) closeFileHistoryDialog();
else if (event.key === "Escape" && repoTabContextMenu) closeRepoTabContextMenu();
else if (event.key === "Escape" && pendingDiscard && !isBusy) closeDiscardConfirm();
else if (event.key === "Escape" && compareDialogOpen) closeCompareDialog();
else if (event.key === "Escape" && newBranchCommit) newBranchCommit = null;
@@ -4374,7 +4330,7 @@
<section
class="workspace"
aria-label="Git workspace"
style="--left-sidebar-width: {leftSidebarWidth}px; --history-aside-width: {historyAsideWidth}px; --file-history-width: {fileHistoryWidth}px;"
style="--left-sidebar-width: {leftSidebarWidth}px; --history-aside-width: {historyAsideWidth}px;"
>
<!-- Left sidebar: branches + explorer -->
@@ -4477,6 +4433,7 @@
onCollapseAllFolders={collapseAllExplorerFolders}
onSelectNode={selectExplorerNode}
onOpenFile={openFileFromExplorer}
onFileHistory={openFileHistoryDialog}
onBlame={openBlame}
collapsed={explorerPanelCollapsed}
onToggleCollapsed={toggleExplorerPanelCollapsed}
@@ -4601,8 +4558,8 @@
onkeydown={onHistoryAsideResizeKeydown}
></div>
<!-- Right sidebar: commit graph + file history -->
<aside class="history-aside" class:file-history-collapsed={fileHistoryCollapsed} aria-label="Commit history">
<!-- Right sidebar: commit graph -->
<aside class="history-aside" aria-label="Commit history">
<HistoryPanel
{commits}
{localBranchNames}
@@ -4627,36 +4584,6 @@
expandedCommitHashes = next;
}}
/>
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<div
class="file-history-resize-handle"
class:resizing={resizingFileHistory}
role="separator"
aria-orientation="vertical"
aria-label="Resize file history width"
aria-valuenow={fileHistoryWidth}
aria-valuemin={FILE_HISTORY_MIN_WIDTH}
aria-valuemax={FILE_HISTORY_MAX_WIDTH}
tabindex={fileHistoryCollapsed ? -1 : 0}
onpointerdown={startFileHistoryResize}
onpointermove={onFileHistoryResizeMove}
onpointerup={endFileHistoryResize}
onpointercancel={endFileHistoryResize}
onkeydown={onFileHistoryResizeKeydown}
></div>
<FileHistoryPanel
{fileHistory}
{selectedExplorerPath}
selectedExplorerLabel={selectedExplorerPath ? `${selectedExplorerKind === "folder" ? "Folder" : "File"} history` : "File history"}
{hasRepository}
{isBusy}
isLoading={fileHistoryLoading}
onDiff={diffSelectedFileFromCommit}
onRestore={restoreSelectedFileFromCommit}
collapsed={fileHistoryCollapsed}
onToggleCollapsed={toggleFileHistoryCollapsed}
/>
</aside>
</section>
{/if}
@@ -4772,6 +4699,21 @@
/>
{/if}
{#if fileHistoryDialogOpen}
{#await import("./lib/components/FileHistoryDialog.svelte") then module}
<module.default
{fileHistory}
filePath={selectedExplorerPath}
{isBusy}
isLoading={fileHistoryLoading}
error={fileHistoryError}
onDiff={diffSelectedFileFromCommit}
onRestore={restoreSelectedFileFromCommit}
onClose={closeFileHistoryDialog}
/>
{/await}
{/if}
{#if globalSearchOpen}
{#await import("./lib/components/GlobalSearchDialog.svelte") then module}
<module.default
-177
View File
@@ -1,177 +0,0 @@
<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";
const commitDateFormatter = new Intl.DateTimeFormat(undefined, {
dateStyle: "medium",
timeStyle: "short",
});
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 commitDateFormatter.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>