diff --git a/static/js/api/baseModelApi.js b/static/js/api/baseModelApi.js index 5a282594..7c7ab734 100644 --- a/static/js/api/baseModelApi.js +++ b/static/js/api/baseModelApi.js @@ -1,7 +1,6 @@ // filepath: d:\Workspace\ComfyUI\custom_nodes\ComfyUI-Lora-Manager\static\js\api\baseModelApi.js import { state, getCurrentPageState } from '../state/index.js'; import { showToast } from '../utils/uiHelpers.js'; -import { showDeleteModal, confirmDelete } from '../utils/modalUtils.js'; import { getSessionItem, saveMapToStorage } from '../utils/storageHelpers.js'; /** @@ -279,6 +278,112 @@ export async function fetchModelsPage(options = {}) { } } +/** + * Reset and reload models using virtual scrolling + * @param {Object} options - Operation options + * @returns {Promise} The fetch result + */ +export async function resetAndReloadWithVirtualScroll(options = {}) { + const { + modelType = 'lora', + updateFolders = false, + fetchPageFunction + } = options; + + const pageState = getCurrentPageState(); + + try { + pageState.isLoading = true; + document.body.classList.add('loading'); + + // Reset page counter + pageState.currentPage = 1; + + // Fetch the first page + const result = await fetchPageFunction(1, pageState.pageSize || 50); + + // Update the virtual scroller + state.virtualScroller.refreshWithData( + result.items, + result.totalItems, + result.hasMore + ); + + // Update state + pageState.hasMore = result.hasMore; + pageState.currentPage = 2; // Next page will be 2 + + // Update folders if needed + if (updateFolders && result.folders) { + updateFolderTags(result.folders); + } + + return result; + } catch (error) { + console.error(`Error reloading ${modelType}s:`, error); + showToast(`Failed to reload ${modelType}s: ${error.message}`, 'error'); + throw error; + } finally { + pageState.isLoading = false; + document.body.classList.remove('loading'); + } +} + +/** + * Load more models using virtual scrolling + * @param {Object} options - Operation options + * @returns {Promise} The fetch result + */ +export async function loadMoreWithVirtualScroll(options = {}) { + const { + modelType = 'lora', + resetPage = false, + updateFolders = false, + fetchPageFunction + } = options; + + const pageState = getCurrentPageState(); + + try { + // Start loading state + pageState.isLoading = true; + document.body.classList.add('loading'); + + // Reset to first page if requested + if (resetPage) { + pageState.currentPage = 1; + } + + // Fetch the first page of data + const result = await fetchPageFunction(pageState.currentPage, pageState.pageSize || 50); + + // Update virtual scroller with the new data + state.virtualScroller.refreshWithData( + result.items, + result.totalItems, + result.hasMore + ); + + // Update state + pageState.hasMore = result.hasMore; + pageState.currentPage = 2; // Next page to load would be 2 + + // Update folders if needed + if (updateFolders && result.folders) { + updateFolderTags(result.folders); + } + + return result; + } catch (error) { + console.error(`Error loading ${modelType}s:`, error); + showToast(`Failed to load ${modelType}s: ${error.message}`, 'error'); + throw error; + } finally { + pageState.isLoading = false; + document.body.classList.remove('loading'); + } +} + // Update folder tags in the UI export function updateFolderTags(folders) { const folderTagsContainer = document.querySelector('.folder-tags'); diff --git a/static/js/api/checkpointApi.js b/static/js/api/checkpointApi.js index c5ebcd3f..ea124ea9 100644 --- a/static/js/api/checkpointApi.js +++ b/static/js/api/checkpointApi.js @@ -1,7 +1,10 @@ import { createCheckpointCard } from '../components/CheckpointCard.js'; import { loadMoreModels, + fetchModelsPage, resetAndReload as baseResetAndReload, + resetAndReloadWithVirtualScroll, + loadMoreWithVirtualScroll, refreshModels as baseRefreshModels, deleteModel as baseDeleteModel, replaceModelPreview, @@ -9,25 +12,67 @@ import { refreshSingleModelMetadata, excludeModel as baseExcludeModel } from './baseModelApi.js'; +import { state } from '../state/index.js'; -// Load more checkpoints with pagination -export async function loadMoreCheckpoints(resetPagination = true) { - return loadMoreModels({ - resetPage: resetPagination, - updateFolders: true, +/** + * Fetch checkpoints with pagination for virtual scrolling + * @param {number} page - Page number to fetch + * @param {number} pageSize - Number of items per page + * @returns {Promise} Object containing items, total count, and pagination info + */ +export async function fetchCheckpointsPage(page = 1, pageSize = 100) { + return fetchModelsPage({ modelType: 'checkpoint', - createCardFunction: createCheckpointCard, + page, + pageSize, endpoint: '/api/checkpoints' }); } +/** + * Load more checkpoints with pagination - updated to work with VirtualScroller + * @param {boolean} resetPage - Whether to reset to the first page + * @param {boolean} updateFolders - Whether to update folder tags + * @returns {Promise} + */ +export async function loadMoreCheckpoints(resetPage = false, updateFolders = false) { + // Check if virtual scroller is available + if (state.virtualScroller) { + return loadMoreWithVirtualScroll({ + modelType: 'checkpoint', + resetPage, + updateFolders, + fetchPageFunction: fetchCheckpointsPage + }); + } else { + // Fall back to the original implementation if virtual scroller isn't available + return loadMoreModels({ + resetPage, + updateFolders, + modelType: 'checkpoint', + createCardFunction: createCheckpointCard, + endpoint: '/api/checkpoints' + }); + } +} + // Reset and reload checkpoints -export async function resetAndReload() { - return baseResetAndReload({ - updateFolders: true, - modelType: 'checkpoint', - loadMoreFunction: loadMoreCheckpoints - }); +export async function resetAndReload(updateFolders = false) { + // Check if virtual scroller is available + if (state.virtualScroller) { + return resetAndReloadWithVirtualScroll({ + modelType: 'checkpoint', + updateFolders, + fetchPageFunction: fetchCheckpointsPage + }); + } else { + // Fall back to original implementation + return baseResetAndReload({ + updateFolders, + modelType: 'checkpoint', + loadMoreFunction: loadMoreCheckpoints + }); + } } // Refresh checkpoints @@ -60,7 +105,11 @@ export async function fetchCivitai() { // Refresh single checkpoint metadata export async function refreshSingleCheckpointMetadata(filePath) { - return refreshSingleModelMetadata(filePath, 'checkpoint'); + const success = await refreshSingleModelMetadata(filePath, 'checkpoint'); + if (success) { + // Reload the current view to show updated data + await resetAndReload(); + } } /** diff --git a/static/js/api/loraApi.js b/static/js/api/loraApi.js index 1df4d2f6..b4bcb767 100644 --- a/static/js/api/loraApi.js +++ b/static/js/api/loraApi.js @@ -3,6 +3,8 @@ import { loadMoreModels, fetchModelsPage, resetAndReload as baseResetAndReload, + resetAndReloadWithVirtualScroll, + loadMoreWithVirtualScroll, refreshModels as baseRefreshModels, deleteModel as baseDeleteModel, replaceModelPreview, @@ -58,45 +60,12 @@ export async function loadMoreLoras(resetPage = false, updateFolders = false) { // Check if virtual scroller is available if (state.virtualScroller) { - try { - // Start loading state - pageState.isLoading = true; - document.body.classList.add('loading'); - - // Reset to first page if requested - if (resetPage) { - pageState.currentPage = 1; - } - - // Fetch the first page of data - const result = await fetchLorasPage(pageState.currentPage, pageState.pageSize || 50); - - // Update virtual scroller with the new data - state.virtualScroller.refreshWithData( - result.items, - result.totalItems, - result.hasMore - ); - - // Update state - pageState.hasMore = result.hasMore; - pageState.currentPage = 2; // Next page to load would be 2 - - // Update folders if needed - if (updateFolders && result.folders) { - // Import function dynamically to avoid circular dependencies - const { updateFolderTags } = await import('./baseModelApi.js'); - updateFolderTags(result.folders); - } - - return result; - } catch (error) { - console.error('Error loading loras:', error); - showToast(`Failed to load loras: ${error.message}`, 'error'); - } finally { - pageState.isLoading = false; - document.body.classList.remove('loading'); - } + return loadMoreWithVirtualScroll({ + modelType: 'lora', + resetPage, + updateFolders, + fetchPageFunction: fetchLorasPage + }); } else { // Fall back to the original implementation if virtual scroller isn't available return loadMoreModels({ @@ -115,7 +84,7 @@ export async function loadMoreLoras(resetPage = false, updateFolders = false) { * @param {number} pageSize - Number of items per page * @returns {Promise} Object containing items, total count, and pagination info */ -export async function fetchLorasPage(page = 1, pageSize = 50) { +export async function fetchLorasPage(page = 1, pageSize = 100) { return fetchModelsPage({ modelType: 'lora', page, @@ -160,42 +129,11 @@ export async function resetAndReload(updateFolders = false) { // Check if virtual scroller is available if (state.virtualScroller) { - try { - pageState.isLoading = true; - document.body.classList.add('loading'); - - // Reset page counter - pageState.currentPage = 1; - - // Fetch the first page - const result = await fetchLorasPage(1, pageState.pageSize || 50); - - // Update the virtual scroller - state.virtualScroller.refreshWithData( - result.items, - result.totalItems, - result.hasMore - ); - - // Update state - pageState.hasMore = result.hasMore; - pageState.currentPage = 2; // Next page will be 2 - - // Update folders if needed - if (updateFolders && result.folders) { - // Import function dynamically to avoid circular dependencies - const { updateFolderTags } = await import('./baseModelApi.js'); - updateFolderTags(result.folders); - } - - return result; - } catch (error) { - console.error('Error reloading loras:', error); - showToast(`Failed to reload loras: ${error.message}`, 'error'); - } finally { - pageState.isLoading = false; - document.body.classList.remove('loading'); - } + return resetAndReloadWithVirtualScroll({ + modelType: 'lora', + updateFolders, + fetchPageFunction: fetchLorasPage + }); } else { // Fall back to original implementation return baseResetAndReload({ diff --git a/static/js/utils/infiniteScroll.js b/static/js/utils/infiniteScroll.js index a3a41000..b7c4b4e5 100644 --- a/static/js/utils/infiniteScroll.js +++ b/static/js/utils/infiniteScroll.js @@ -1,8 +1,9 @@ import { state, getCurrentPageState } from '../state/index.js'; -import { debounce } from './debounce.js'; import { VirtualScroller } from './VirtualScroller.js'; import { createLoraCard, setupLoraCardEventDelegation } from '../components/LoraCard.js'; +import { createCheckpointCard } from '../components/CheckpointCard.js'; import { fetchLorasPage } from '../api/loraApi.js'; +import { fetchCheckpointsPage } from '../api/checkpointApi.js'; import { showToast } from './uiHelpers.js'; // Function to dynamically import the appropriate card creator based on page type @@ -18,13 +19,7 @@ async function getCardCreator(pageType) { return null; } } else if (pageType === 'checkpoints') { - try { - const { createCheckpointCard } = await import('../components/CheckpointCard.js'); - return createCheckpointCard; - } catch (err) { - console.error('Failed to load checkpoint card creator:', err); - return null; - } + return createCheckpointCard; } return null; } @@ -42,13 +37,7 @@ async function getDataFetcher(pageType) { return null; } } else if (pageType === 'checkpoints') { - try { - const { fetchCheckpointsPage } = await import('../api/checkpointApi.js'); - return fetchCheckpointsPage; - } catch (err) { - console.error('Failed to load checkpoint data fetcher:', err); - return null; - } + return fetchCheckpointsPage; } return null; } @@ -105,11 +94,11 @@ async function initializeVirtualScroll(pageType) { } // Change this line to get the actual scrolling container - const pageContainer = document.querySelector('.page-content'); - const pageContent = pageContainer.querySelector('.container'); + const scrollContainer = document.querySelector('.page-content'); + const gridContainer = scrollContainer.querySelector('.container'); - if (!pageContent) { - console.warn('Page content element not found for virtual scroll'); + if (!gridContainer) { + console.warn('Grid container element not found for virtual scroll'); return; } @@ -122,15 +111,15 @@ async function initializeVirtualScroll(pageType) { throw new Error(`Required components not available for ${pageType} page`); } - // Pass the correct scrolling container + // Initialize virtual scroller with renamed container elements state.virtualScroller = new VirtualScroller({ gridElement: grid, - containerElement: pageContent, - scrollContainer: pageContainer, // Add this new parameter + containerElement: gridContainer, + scrollContainer: scrollContainer, createItemFn: createCardFn, fetchItemsFn: fetchDataFn, pageSize: 100, - rowGap: 20 // Add consistent vertical spacing between rows + rowGap: 20 }); // Initialize the virtual scroller