add Loading Spinner when file History is loading
Make Renamed instad of Del and untracked
This commit is contained in:
+27
-7
@@ -117,6 +117,8 @@
|
||||
let expandedExplorerPaths = new Set<string>();
|
||||
let expandedCommitHashes = new Set<string>();
|
||||
let fileHistory: GitCommit[] = [];
|
||||
let fileHistoryLoading = false;
|
||||
let fileHistoryRequestId = 0;
|
||||
let commitMessage = "";
|
||||
let errorMessage = "";
|
||||
let operation = "";
|
||||
@@ -537,7 +539,9 @@
|
||||
}
|
||||
|
||||
async function refreshFileHistory(path = activeRepoPath, file = selectedExplorerPath) {
|
||||
fileHistory = file ? await listFileHistory(path, file, 100) : [];
|
||||
const requestId = ++fileHistoryRequestId;
|
||||
const history = file ? await listFileHistory(path, file, 100) : [];
|
||||
if (requestId === fileHistoryRequestId) fileHistory = history;
|
||||
}
|
||||
|
||||
// ── Repository operations ──────────────────────────────────────────────────
|
||||
@@ -1112,13 +1116,30 @@
|
||||
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.
|
||||
async function loadSelectedFileHistory(path: string, repo = activeRepoPath) {
|
||||
const requestId = ++fileHistoryRequestId;
|
||||
fileHistoryLoading = true;
|
||||
try {
|
||||
const history = await listFileHistory(repo, path, 100);
|
||||
if (requestId === fileHistoryRequestId) fileHistory = history;
|
||||
} catch (error) {
|
||||
if (requestId === fileHistoryRequestId) {
|
||||
fileHistory = [];
|
||||
errorMessage = errorToMessage(error);
|
||||
}
|
||||
} finally {
|
||||
if (requestId === fileHistoryRequestId) fileHistoryLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function selectExplorerNode(node: ExplorerNode) {
|
||||
if (!activeRepoPath || (selectedExplorerPath === node.path && selectedExplorerKind === node.kind)) return;
|
||||
selectedExplorerPath = node.path;
|
||||
selectedExplorerKind = node.kind;
|
||||
await runOperation(`Loading ${node.path} history`, async () => {
|
||||
await refreshFileHistory(activeRepoPath, node.path);
|
||||
});
|
||||
await loadSelectedFileHistory(node.path);
|
||||
}
|
||||
|
||||
async function selectFileFromSearch(file: GitRepositoryFile) {
|
||||
@@ -1127,9 +1148,7 @@
|
||||
selectedExplorerKind = "file";
|
||||
expandedExplorerPaths = new Set([...expandedExplorerPaths, ...explorerParentFolders(file.path)]);
|
||||
|
||||
await runOperation(`Loading ${file.path} history`, async () => {
|
||||
await refreshFileHistory(activeRepoPath, file.path);
|
||||
});
|
||||
await loadSelectedFileHistory(file.path);
|
||||
}
|
||||
|
||||
async function restoreSelectedFileFromCommit(target: GitCommit) {
|
||||
@@ -1642,6 +1661,7 @@
|
||||
selectedExplorerLabel={selectedExplorerPath ? `${selectedExplorerKind === "folder" ? "Folder" : "File"} history` : "File history"}
|
||||
{hasRepository}
|
||||
{isBusy}
|
||||
isLoading={fileHistoryLoading}
|
||||
onDiff={diffSelectedFileFromCommit}
|
||||
onRestore={restoreSelectedFileFromCommit}
|
||||
/>
|
||||
|
||||
+76
@@ -1371,6 +1371,82 @@
|
||||
.file-history-actions .commit-action-buttons { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); width: 100%; justify-content: stretch; }
|
||||
.file-history-actions .commit-action-buttons button { min-width: 0; justify-content: center; padding-inline: 6px; }
|
||||
|
||||
/* --- File history loading (scoped, non-blocking) --- */
|
||||
|
||||
.file-history-loading {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
min-height: 140px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.file-history-loading-graph { width: 56px; height: 56px; overflow: visible; }
|
||||
|
||||
.file-history-loading-graph .fhl-ring {
|
||||
fill: none;
|
||||
stroke: rgba(100, 108, 255, 0.22);
|
||||
stroke-width: 3;
|
||||
stroke-dasharray: 26 18;
|
||||
transform-origin: 60px 60px;
|
||||
animation: fhl-ring-spin 5s linear infinite;
|
||||
}
|
||||
|
||||
.file-history-loading-graph .fhl-trunk,
|
||||
.file-history-loading-graph .fhl-branch {
|
||||
fill: none;
|
||||
stroke: url(#fhl-line);
|
||||
stroke-width: 5;
|
||||
stroke-linecap: round;
|
||||
}
|
||||
|
||||
.file-history-loading-graph .fhl-branch {
|
||||
stroke-dasharray: 90;
|
||||
stroke-dashoffset: 90;
|
||||
animation: fhl-branch-draw 2.4s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.file-history-loading-graph .fhl-node {
|
||||
fill: var(--color-surface-alt);
|
||||
stroke: url(#fhl-line);
|
||||
stroke-width: 5;
|
||||
animation: fhl-node-pulse 2.4s ease-in-out infinite;
|
||||
}
|
||||
.file-history-loading-graph .fhl-n1 { animation-delay: 0s; }
|
||||
.file-history-loading-graph .fhl-n2 { animation-delay: 0.5s; }
|
||||
.file-history-loading-graph .fhl-n3 { animation-delay: 1s; }
|
||||
.file-history-loading-graph .fhl-n4 { animation-delay: 1.5s; }
|
||||
|
||||
.file-history-loading-label {
|
||||
color: var(--color-ink-faint);
|
||||
font-size: 12.5px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
|
||||
@keyframes fhl-ring-spin { to { transform: rotate(360deg); } }
|
||||
@keyframes fhl-branch-draw {
|
||||
0% { stroke-dashoffset: 90; opacity: 0.35; }
|
||||
45% { stroke-dashoffset: 0; opacity: 1; }
|
||||
100% { stroke-dashoffset: 0; opacity: 1; }
|
||||
}
|
||||
@keyframes fhl-node-pulse {
|
||||
0%, 100% { fill: var(--color-surface-alt); filter: none; }
|
||||
50% {
|
||||
fill: var(--color-primary);
|
||||
filter: drop-shadow(0 0 6px rgba(100, 108, 255, 0.8));
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.file-history-loading-graph .fhl-ring,
|
||||
.file-history-loading-graph .fhl-branch,
|
||||
.file-history-loading-graph .fhl-node { animation: none; }
|
||||
.file-history-loading-graph .fhl-branch { stroke-dashoffset: 0; }
|
||||
}
|
||||
|
||||
/* --- Git graph --- */
|
||||
|
||||
.graph-list { padding: 0; }
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
selectedExplorerLabel: string;
|
||||
hasRepository: boolean;
|
||||
isBusy: boolean;
|
||||
isLoading?: boolean;
|
||||
onDiff: (commit: GitCommit) => void;
|
||||
onRestore: (commit: GitCommit) => void;
|
||||
}
|
||||
@@ -18,6 +19,7 @@
|
||||
selectedExplorerLabel = "File history",
|
||||
hasRepository = false,
|
||||
isBusy = false,
|
||||
isLoading = false,
|
||||
onDiff = () => {},
|
||||
onRestore = () => {},
|
||||
}: Props = $props();
|
||||
@@ -89,6 +91,25 @@
|
||||
<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}
|
||||
|
||||
Reference in New Issue
Block a user