diff --git a/src/App.svelte b/src/App.svelte index 5c83312..0fea306 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -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(); 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 @@
@@ -4477,6 +4433,7 @@ onCollapseAllFolders={collapseAllExplorerFolders} onSelectNode={selectExplorerNode} onOpenFile={openFileFromExplorer} + onFileHistory={openFileHistoryDialog} onBlame={openBlame} collapsed={explorerPanelCollapsed} onToggleCollapsed={toggleExplorerPanelCollapsed} @@ -4601,8 +4558,8 @@ onkeydown={onHistoryAsideResizeKeydown} > - -
{/if} @@ -4772,6 +4699,21 @@ /> {/if} +{#if fileHistoryDialogOpen} + {#await import("./lib/components/FileHistoryDialog.svelte") then module} + + {/await} +{/if} + {#if globalSearchOpen} {#await import("./lib/components/GlobalSearchDialog.svelte") then module} - 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); - }, - }; - } - - -
- {#if collapsed} - - {:else} -
-
- {selectedExplorerLabel} -

{selectedExplorerPath ? fileName(selectedExplorerPath) : "No file"}

-
-
- {fileHistory.length} - -
-
- - {#if !hasRepository} -
No repository loaded.
- {:else if !selectedExplorerPath} -
Select a file in Explorer.
- {:else if isLoading} -
- - Loading history… - -
- {:else if fileHistory.length === 0} -
No history returned for this selection.
- {:else} -
- {#each fileHistory as item (item.hash)} -
-
-
- -
- -
- - -
-
-
- {/each} -
- {/if} - {/if} -