mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-24 06:32:12 -03:00
Implement lazy loading and infinite scroll features in core application
- Added lazy loading for images and initialized infinite scroll in the AppCore class to enhance performance across various pages. - Updated LoraPageManager and RecipeManager to utilize the new initializePageFeatures method for common UI features. - Enhanced infinite scroll functionality to dynamically load more content based on the page type, improving user experience. - Refactored recipes.html to trigger the import modal through the ModalManager for better modal handling.
This commit is contained in:
@@ -5,7 +5,8 @@ import { modalManager } from './managers/ModalManager.js';
|
||||
import { updateService } from './managers/UpdateService.js';
|
||||
import { HeaderManager } from './components/Header.js';
|
||||
import { SettingsManager } from './managers/SettingsManager.js';
|
||||
import { showToast, initTheme, initBackToTop, updatePanelPositions } from './utils/uiHelpers.js';
|
||||
import { showToast, initTheme, initBackToTop, updatePanelPositions, lazyLoadImages } from './utils/uiHelpers.js';
|
||||
import { initializeInfiniteScroll } from './utils/infiniteScroll.js';
|
||||
|
||||
// Core application class
|
||||
export class AppCore {
|
||||
@@ -55,10 +56,28 @@ export class AppCore {
|
||||
showToast(message, type = 'info') {
|
||||
showToast(message, type);
|
||||
}
|
||||
|
||||
// Initialize common UI features based on page type
|
||||
initializePageFeatures() {
|
||||
const pageType = this.getPageType();
|
||||
|
||||
// Initialize lazy loading for images on all pages
|
||||
lazyLoadImages();
|
||||
|
||||
// Initialize infinite scroll for pages that need it
|
||||
if (['loras', 'recipes', 'checkpoints'].includes(pageType)) {
|
||||
initializeInfiniteScroll(pageType);
|
||||
}
|
||||
|
||||
// Update panel positions
|
||||
updatePanelPositions();
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
// Create and export a singleton instance
|
||||
export const appCore = new AppCore();
|
||||
|
||||
// Export common utilities for global use
|
||||
export { showToast, modalManager, state };
|
||||
export { showToast, modalManager, state, lazyLoadImages, initializeInfiniteScroll };
|
||||
@@ -65,9 +65,7 @@ class LoraPageManager {
|
||||
|
||||
async initialize() {
|
||||
// Initialize page-specific components
|
||||
initializeInfiniteScroll();
|
||||
initializeEventListeners();
|
||||
lazyLoadImages();
|
||||
restoreFolderFilter();
|
||||
initFolderTagsVisibility();
|
||||
new LoraContextMenu();
|
||||
@@ -77,6 +75,9 @@ class LoraPageManager {
|
||||
|
||||
// Initialize the bulk manager
|
||||
bulkManager.initialize();
|
||||
|
||||
// Initialize common page features (lazy loading, infinite scroll)
|
||||
appCore.initializePageFeatures();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,10 @@ class RecipeManager {
|
||||
|
||||
// Initialize RecipeModal
|
||||
this.recipeModal = new RecipeModal();
|
||||
|
||||
// Add state tracking for infinite scroll
|
||||
this.isLoading = false;
|
||||
this.hasMore = true;
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
@@ -27,6 +31,9 @@ class RecipeManager {
|
||||
|
||||
// Expose necessary functions to the page
|
||||
this._exposeGlobalFunctions();
|
||||
|
||||
// Initialize common page features (lazy loading, infinite scroll)
|
||||
appCore.initializePageFeatures();
|
||||
}
|
||||
|
||||
_exposeGlobalFunctions() {
|
||||
@@ -34,6 +41,7 @@ class RecipeManager {
|
||||
window.recipeManager = this;
|
||||
window.importRecipes = () => this.importRecipes();
|
||||
window.importManager = this.importManager;
|
||||
window.loadMoreRecipes = () => this.loadMoreRecipes();
|
||||
}
|
||||
|
||||
initEventListeners() {
|
||||
@@ -69,10 +77,19 @@ class RecipeManager {
|
||||
}
|
||||
}
|
||||
|
||||
async loadRecipes() {
|
||||
async loadRecipes(resetPage = true) {
|
||||
try {
|
||||
// Show loading indicator
|
||||
document.body.classList.add('loading');
|
||||
this.isLoading = true;
|
||||
|
||||
// Reset to first page if requested
|
||||
if (resetPage) {
|
||||
this.currentPage = 1;
|
||||
// Clear grid if resetting
|
||||
const grid = document.getElementById('recipeGrid');
|
||||
if (grid) grid.innerHTML = '';
|
||||
}
|
||||
|
||||
// Build query parameters
|
||||
const params = new URLSearchParams({
|
||||
@@ -101,7 +118,10 @@ class RecipeManager {
|
||||
const data = await response.json();
|
||||
|
||||
// Update recipes grid
|
||||
this.updateRecipesGrid(data);
|
||||
this.updateRecipesGrid(data, resetPage);
|
||||
|
||||
// Update pagination state
|
||||
this.hasMore = data.has_more || false;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading recipes:', error);
|
||||
@@ -109,32 +129,61 @@ class RecipeManager {
|
||||
} finally {
|
||||
// Hide loading indicator
|
||||
document.body.classList.remove('loading');
|
||||
this.isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
updateRecipesGrid(data) {
|
||||
// Load more recipes for infinite scroll
|
||||
async loadMoreRecipes() {
|
||||
if (this.isLoading || !this.hasMore) return;
|
||||
|
||||
this.currentPage++;
|
||||
await this.loadRecipes(false);
|
||||
}
|
||||
|
||||
updateRecipesGrid(data, resetGrid = true) {
|
||||
const grid = document.getElementById('recipeGrid');
|
||||
if (!grid) return;
|
||||
|
||||
// Check if data exists and has items
|
||||
if (!data.items || data.items.length === 0) {
|
||||
grid.innerHTML = `
|
||||
<div class="placeholder-message">
|
||||
<p>No recipes found</p>
|
||||
<p>Add recipe images to your recipes folder to see them here.</p>
|
||||
</div>
|
||||
`;
|
||||
if (resetGrid) {
|
||||
grid.innerHTML = `
|
||||
<div class="placeholder-message">
|
||||
<p>No recipes found</p>
|
||||
<p>Add recipe images to your recipes folder to see them here.</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear grid
|
||||
grid.innerHTML = '';
|
||||
// Clear grid if resetting
|
||||
if (resetGrid) {
|
||||
grid.innerHTML = '';
|
||||
}
|
||||
|
||||
// Create recipe cards
|
||||
data.items.forEach(recipe => {
|
||||
const recipeCard = new RecipeCard(recipe, (recipe) => this.showRecipeDetails(recipe));
|
||||
grid.appendChild(recipeCard.element);
|
||||
});
|
||||
|
||||
// Add sentinel for infinite scroll if needed
|
||||
if (this.hasMore) {
|
||||
let sentinel = document.getElementById('scroll-sentinel');
|
||||
if (!sentinel) {
|
||||
sentinel = document.createElement('div');
|
||||
sentinel.id = 'scroll-sentinel';
|
||||
sentinel.style.height = '10px';
|
||||
grid.appendChild(sentinel);
|
||||
|
||||
// Re-observe the sentinel if we have an observer
|
||||
if (window.state && window.state.observer) {
|
||||
window.state.observer.observe(sentinel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
showRecipeDetails(recipe) {
|
||||
|
||||
@@ -2,12 +2,32 @@ import { state } from '../state/index.js';
|
||||
import { loadMoreLoras } from '../api/loraApi.js';
|
||||
import { debounce } from './debounce.js';
|
||||
|
||||
export function initializeInfiniteScroll() {
|
||||
export function initializeInfiniteScroll(pageType = 'loras') {
|
||||
if (state.observer) {
|
||||
state.observer.disconnect();
|
||||
}
|
||||
|
||||
const debouncedLoadMore = debounce(loadMoreLoras, 200);
|
||||
// Determine the load more function and grid ID based on page type
|
||||
let loadMoreFunction;
|
||||
let gridId;
|
||||
|
||||
switch (pageType) {
|
||||
case 'recipes':
|
||||
loadMoreFunction = window.recipeManager?.loadMoreRecipes || (() => console.warn('loadMoreRecipes not found'));
|
||||
gridId = 'recipeGrid';
|
||||
break;
|
||||
case 'checkpoints':
|
||||
loadMoreFunction = window.checkpointManager?.loadMoreCheckpoints || (() => console.warn('loadMoreCheckpoints not found'));
|
||||
gridId = 'checkpointGrid';
|
||||
break;
|
||||
case 'loras':
|
||||
default:
|
||||
loadMoreFunction = loadMoreLoras;
|
||||
gridId = 'loraGrid';
|
||||
break;
|
||||
}
|
||||
|
||||
const debouncedLoadMore = debounce(loadMoreFunction, 200);
|
||||
|
||||
state.observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
@@ -19,6 +39,12 @@ export function initializeInfiniteScroll() {
|
||||
{ threshold: 0.1 }
|
||||
);
|
||||
|
||||
const grid = document.getElementById(gridId);
|
||||
if (!grid) {
|
||||
console.warn(`Grid with ID "${gridId}" not found for infinite scroll`);
|
||||
return;
|
||||
}
|
||||
|
||||
const existingSentinel = document.getElementById('scroll-sentinel');
|
||||
if (existingSentinel) {
|
||||
state.observer.observe(existingSentinel);
|
||||
@@ -26,7 +52,7 @@ export function initializeInfiniteScroll() {
|
||||
const sentinel = document.createElement('div');
|
||||
sentinel.id = 'scroll-sentinel';
|
||||
sentinel.style.height = '10px';
|
||||
document.getElementById('loraGrid').appendChild(sentinel);
|
||||
grid.appendChild(sentinel);
|
||||
state.observer.observe(sentinel);
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@
|
||||
<div class="controls">
|
||||
<div class="action-buttons">
|
||||
<div title="Import recipes" class="control-group">
|
||||
<button onclick="importRecipes()"><i class="fas fa-file-import"></i> Import</button>
|
||||
<button onclick="modalManager.showModal('importModal')"><i class="fas fa-file-import"></i> Import</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -85,15 +85,5 @@
|
||||
// Will be implemented in recipes.js
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
// Import recipes
|
||||
function importRecipes() {
|
||||
// Show import modal
|
||||
const importModal = document.getElementById('importModal');
|
||||
if (importModal) {
|
||||
importModal.classList.remove('hidden');
|
||||
importModal.style.display = 'block';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user