From 93ad81ed872589ba4833cf4197e066641ce06bfc Mon Sep 17 00:00:00 2001 From: Will Miao Date: Wed, 24 Jun 2026 21:11:13 +0800 Subject: [PATCH] fix(ui): replace full-page loading overlay with grid-scoped loader to eliminate flicker - Add .grid-loading-overlay CSS: position:absolute inside card grid, semi-transparent dark background, z-index 100, pointer-events:none - Add showGridLoading() / hideGridLoading() to VirtualScroller: creates/removes the scoped overlay inside the card grid only - Modify loadMoreWithVirtualScroll(): replace full-page state.loadingManager overlay with grid-scoped loading, defer hide via requestAnimationFrame to eliminate blank-frame gap - Clean up gridLoadingOverlay in dispose() to prevent DOM leak --- static/css/components/card.css | 15 +++++++++++++++ static/js/api/baseModelApi.js | 14 ++++++++++++-- static/js/utils/VirtualScroller.js | 29 +++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/static/css/components/card.css b/static/css/components/card.css index f3c83105..e9268abc 100644 --- a/static/css/components/card.css +++ b/static/css/components/card.css @@ -734,6 +734,21 @@ body.hide-card-version .hl-badge { } } +/* Grid-scoped loading overlay (replaces full-page overlay for VirtualScroller refreshes) */ +.grid-loading-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: var(--lora-bg-transparent, oklch(0% 0 0 / 0.3)); + display: flex; + justify-content: center; + align-items: center; + z-index: 100; + pointer-events: none; +} + /* Add after the existing .model-card:hover styles */ @keyframes update-pulse { diff --git a/static/js/api/baseModelApi.js b/static/js/api/baseModelApi.js index 6b54a352..c1a8d586 100644 --- a/static/js/api/baseModelApi.js +++ b/static/js/api/baseModelApi.js @@ -115,7 +115,10 @@ export class BaseModelApiClient { const pageState = this.getPageState(); try { - state.loadingManager.showSimpleLoading(`Loading more ${this.apiConfig.config.displayName}s...`); + // Use grid-scoped loading instead of full-page overlay + if (state.virtualScroller?.showGridLoading) { + state.virtualScroller.showGridLoading(); + } pageState.isLoading = true; if (resetPage) { @@ -154,7 +157,14 @@ export class BaseModelApiClient { throw error; } finally { pageState.isLoading = false; - state.loadingManager.hide(); + // Wait for the next rAF so refreshWithData's scheduleRender has + // completed rendering new cards before hiding the grid loading overlay. + // This eliminates the ~6.7ms blank-frame gap that caused the flicker. + if (state.virtualScroller?.hideGridLoading) { + requestAnimationFrame(() => { + state.virtualScroller.hideGridLoading(); + }); + } } } diff --git a/static/js/utils/VirtualScroller.js b/static/js/utils/VirtualScroller.js index ea3e21fe..bf216697 100644 --- a/static/js/utils/VirtualScroller.js +++ b/static/js/utils/VirtualScroller.js @@ -657,6 +657,9 @@ export class VirtualScroller { this.resizeObserver.disconnect(); } + // Remove any active grid loading overlay + this.hideGridLoading(); + // Remove rendered elements this.clearRenderedItems(); @@ -1130,4 +1133,30 @@ export class VirtualScroller { index: targetIndex }; } + + /** + * Show a grid-scoped loading indicator (replaces full-page overlay) + * Only covers the card grid area, leaving header/sidebar unaffected. + */ + showGridLoading() { + // Remove any stale overlay from a prior deferred hide (e.g. from final rAF) + this.hideGridLoading(); + const overlay = document.createElement('div'); + overlay.className = 'grid-loading-overlay'; + const spinner = document.createElement('div'); + spinner.className = 'loading-spinner'; + overlay.appendChild(spinner); + this.gridElement.appendChild(overlay); + this.gridLoadingOverlay = overlay; + } + + /** + * Hide the grid-scoped loading indicator. + */ + hideGridLoading() { + if (this.gridLoadingOverlay) { + this.gridLoadingOverlay.remove(); + this.gridLoadingOverlay = null; + } + } }