mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-06-09 20:39:25 -03:00
fix(recipe): prevent empty grid by removing preserveScroll from refresh triggers
Bug: when scrolling down on recipes page, any operation with preserveScroll: true would fetch only page 1 data then restore scroll position to beyond the loaded items, leaving the grid empty. Fix: - Remove preserveScroll: true from all 7 must-refresh trigger paths (filter, search, sort, import, settings reload, sync, rebuild cache, sidebar folder nav) - Replace full list refresh with updateSingleItem() for repair and bulk missing-LoRA download operations - Update tests to match new scroll-free behavior
This commit is contained in:
@@ -301,7 +301,7 @@ export async function syncChanges() {
|
|||||||
state.loadingManager.showSimpleLoading('Syncing changes...');
|
state.loadingManager.showSimpleLoading('Syncing changes...');
|
||||||
|
|
||||||
// Simply reload the recipes without rebuilding cache
|
// Simply reload the recipes without rebuilding cache
|
||||||
await resetAndReload(false, { preserveScroll: true });
|
await resetAndReload(false);
|
||||||
|
|
||||||
showToast('toast.recipes.syncComplete', {}, 'success');
|
showToast('toast.recipes.syncComplete', {}, 'success');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -329,7 +329,7 @@ export async function refreshRecipes() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// After successful cache rebuild, reload the recipes
|
// After successful cache rebuild, reload the recipes
|
||||||
await resetAndReload(false, { preserveScroll: true });
|
await resetAndReload(false);
|
||||||
|
|
||||||
showToast('toast.recipes.refreshComplete', {}, 'success');
|
showToast('toast.recipes.refreshComplete', {}, 'success');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -306,8 +306,14 @@ export class RecipeContextMenu extends BaseContextMenu {
|
|||||||
if (result.success) {
|
if (result.success) {
|
||||||
if (result.repaired > 0) {
|
if (result.repaired > 0) {
|
||||||
showToast('recipes.contextMenu.repair.success', {}, 'success');
|
showToast('recipes.contextMenu.repair.success', {}, 'success');
|
||||||
// Refresh the current card or reload
|
const detailResponse = await fetch(`/api/lm/recipe/${recipeId}`);
|
||||||
this.resetAndReload();
|
if (detailResponse.ok) {
|
||||||
|
const updatedRecipe = await detailResponse.json();
|
||||||
|
const filePath = this.currentCard?.dataset?.filepath;
|
||||||
|
if (filePath && state.virtualScroller) {
|
||||||
|
state.virtualScroller.updateSingleItem(filePath, updatedRecipe);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
showToast('recipes.contextMenu.repair.skipped', {}, 'info');
|
showToast('recipes.contextMenu.repair.skipped', {}, 'info');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,7 +432,7 @@ export class BatchImportManager {
|
|||||||
|
|
||||||
// Refresh recipes list to show newly imported recipes
|
// Refresh recipes list to show newly imported recipes
|
||||||
if (window.recipeManager && typeof window.recipeManager.loadRecipes === 'function') {
|
if (window.recipeManager && typeof window.recipeManager.loadRecipes === 'function') {
|
||||||
window.recipeManager.loadRecipes({ preserveScroll: true });
|
window.recipeManager.loadRecipes(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show results step
|
// Show results step
|
||||||
|
|||||||
@@ -309,9 +309,22 @@ export class BulkMissingLoraDownloadManager {
|
|||||||
}, 'warning');
|
}, 'warning');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refresh the recipes list to update LoRA status
|
// Update each affected recipe card with fresh data (LoRA inLibrary flags changed)
|
||||||
if (window.recipeManager) {
|
if (state.virtualScroller) {
|
||||||
window.recipeManager.loadRecipes({ preserveScroll: true });
|
const { extractRecipeId } = await import('../api/recipeApi.js');
|
||||||
|
for (const recipe of this.pendingRecipes) {
|
||||||
|
const recipeId = extractRecipeId(recipe.file_path);
|
||||||
|
if (!recipeId) continue;
|
||||||
|
try {
|
||||||
|
const detailRes = await fetch(`/api/lm/recipe/${encodeURIComponent(recipeId)}`);
|
||||||
|
if (detailRes.ok) {
|
||||||
|
const updated = await detailRes.json();
|
||||||
|
state.virtualScroller.updateSingleItem(recipe.file_path, updated);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Failed to update recipe card after LoRA download:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -662,7 +662,7 @@ export class FilterManager {
|
|||||||
|
|
||||||
// Call the appropriate manager's load method based on page type
|
// Call the appropriate manager's load method based on page type
|
||||||
if (this.currentPage === 'recipes' && window.recipeManager) {
|
if (this.currentPage === 'recipes' && window.recipeManager) {
|
||||||
await window.recipeManager.loadRecipes({ preserveScroll: true });
|
await window.recipeManager.loadRecipes(true);
|
||||||
} else if (this.currentPage === 'loras' || this.currentPage === 'embeddings' || this.currentPage === 'checkpoints') {
|
} else if (this.currentPage === 'loras' || this.currentPage === 'embeddings' || this.currentPage === 'checkpoints') {
|
||||||
// For models page, reset the page and reload
|
// For models page, reset the page and reload
|
||||||
await getModelApiClient().loadMoreWithVirtualScroll(true, false);
|
await getModelApiClient().loadMoreWithVirtualScroll(true, false);
|
||||||
@@ -746,7 +746,7 @@ export class FilterManager {
|
|||||||
|
|
||||||
// Reload data using the appropriate method for the current page
|
// Reload data using the appropriate method for the current page
|
||||||
if (this.currentPage === 'recipes' && window.recipeManager) {
|
if (this.currentPage === 'recipes' && window.recipeManager) {
|
||||||
await window.recipeManager.loadRecipes({ preserveScroll: true });
|
await window.recipeManager.loadRecipes(true);
|
||||||
} else if (this.currentPage === 'loras' || this.currentPage === 'checkpoints' || this.currentPage === 'embeddings') {
|
} else if (this.currentPage === 'loras' || this.currentPage === 'checkpoints' || this.currentPage === 'embeddings') {
|
||||||
await getModelApiClient().loadMoreWithVirtualScroll(true, true);
|
await getModelApiClient().loadMoreWithVirtualScroll(true, true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -301,7 +301,7 @@ export class SearchManager {
|
|||||||
|
|
||||||
// Call the appropriate manager's load method based on page type
|
// Call the appropriate manager's load method based on page type
|
||||||
if (this.currentPage === 'recipes' && window.recipeManager) {
|
if (this.currentPage === 'recipes' && window.recipeManager) {
|
||||||
window.recipeManager.loadRecipes({ preserveScroll: true });
|
window.recipeManager.loadRecipes(true);
|
||||||
} else if (this.currentPage === 'loras' || this.currentPage === 'embeddings' || this.currentPage === 'checkpoints') {
|
} else if (this.currentPage === 'loras' || this.currentPage === 'embeddings' || this.currentPage === 'checkpoints') {
|
||||||
// For models page, reset the page and reload
|
// For models page, reset the page and reload
|
||||||
getModelApiClient().loadMoreWithVirtualScroll(true, false);
|
getModelApiClient().loadMoreWithVirtualScroll(true, false);
|
||||||
|
|||||||
@@ -2876,7 +2876,7 @@ export class SettingsManager {
|
|||||||
await resetAndReload(false);
|
await resetAndReload(false);
|
||||||
} else if (this.currentPage === 'recipes') {
|
} else if (this.currentPage === 'recipes') {
|
||||||
// Reload the recipes without updating folders
|
// Reload the recipes without updating folders
|
||||||
await window.recipeManager.loadRecipes({ preserveScroll: true });
|
await window.recipeManager.loadRecipes(true);
|
||||||
} else if (this.currentPage === 'checkpoints') {
|
} else if (this.currentPage === 'checkpoints') {
|
||||||
// Reload the checkpoints without updating folders
|
// Reload the checkpoints without updating folders
|
||||||
await resetAndReload(false);
|
await resetAndReload(false);
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ export class DownloadManager {
|
|||||||
modalManager.closeModal('importModal');
|
modalManager.closeModal('importModal');
|
||||||
|
|
||||||
// Refresh the recipe
|
// Refresh the recipe
|
||||||
window.recipeManager.loadRecipes({ preserveScroll: true });
|
window.recipeManager.loadRecipes(true);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error:', error);
|
console.error('Error:', error);
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class RecipePageControls {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async resetAndReload() {
|
async resetAndReload() {
|
||||||
await refreshVirtualScroll({ preserveScroll: true });
|
await refreshVirtualScroll();
|
||||||
}
|
}
|
||||||
|
|
||||||
async refreshModels(fullRebuild = false) {
|
async refreshModels(fullRebuild = false) {
|
||||||
|
|||||||
@@ -177,9 +177,7 @@ describe('RecipeSidebarApiClient bulk operations', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('preserves scroll position for recipe reloads when requested', async () => {
|
it('reloads recipes without preserving scroll', async () => {
|
||||||
const scrollSnapshot = { scrollContainer: { scrollTop: 480 }, scrollTop: 480 };
|
|
||||||
captureScrollPositionMock.mockReturnValue(scrollSnapshot);
|
|
||||||
global.fetch.mockResolvedValue({
|
global.fetch.mockResolvedValue({
|
||||||
ok: true,
|
ok: true,
|
||||||
json: async () => ({
|
json: async () => ({
|
||||||
@@ -189,18 +187,18 @@ describe('RecipeSidebarApiClient bulk operations', () => {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
await resetAndReload(false, { preserveScroll: true });
|
await resetAndReload(false);
|
||||||
|
|
||||||
expect(captureScrollPositionMock).toHaveBeenCalledTimes(1);
|
expect(captureScrollPositionMock).not.toHaveBeenCalled();
|
||||||
expect(virtualScrollerMock.refreshWithData).toHaveBeenCalledWith(
|
expect(virtualScrollerMock.refreshWithData).toHaveBeenCalledWith(
|
||||||
[{ id: 'recipe-1' }],
|
[{ id: 'recipe-1' }],
|
||||||
1,
|
1,
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
expect(restoreScrollPositionMock).toHaveBeenCalledWith(scrollSnapshot);
|
expect(restoreScrollPositionMock).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('uses scroll-preserving reloads for syncChanges', async () => {
|
it('uses scroll-free reloads for syncChanges', async () => {
|
||||||
global.fetch.mockResolvedValue({
|
global.fetch.mockResolvedValue({
|
||||||
ok: true,
|
ok: true,
|
||||||
json: async () => ({
|
json: async () => ({
|
||||||
@@ -212,8 +210,8 @@ describe('RecipeSidebarApiClient bulk operations', () => {
|
|||||||
|
|
||||||
await syncChanges();
|
await syncChanges();
|
||||||
|
|
||||||
expect(captureScrollPositionMock).toHaveBeenCalledTimes(1);
|
expect(captureScrollPositionMock).not.toHaveBeenCalled();
|
||||||
expect(restoreScrollPositionMock).toHaveBeenCalledTimes(1);
|
expect(restoreScrollPositionMock).not.toHaveBeenCalled();
|
||||||
expect(loadingManagerMock.restoreProgressBar).toHaveBeenCalledTimes(1);
|
expect(loadingManagerMock.restoreProgressBar).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user