mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-26 07:35:44 -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 { updateService } from './managers/UpdateService.js';
|
||||||
import { HeaderManager } from './components/Header.js';
|
import { HeaderManager } from './components/Header.js';
|
||||||
import { SettingsManager } from './managers/SettingsManager.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
|
// Core application class
|
||||||
export class AppCore {
|
export class AppCore {
|
||||||
@@ -55,10 +56,28 @@ export class AppCore {
|
|||||||
showToast(message, type = 'info') {
|
showToast(message, type = 'info') {
|
||||||
showToast(message, type);
|
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
|
// Create and export a singleton instance
|
||||||
export const appCore = new AppCore();
|
export const appCore = new AppCore();
|
||||||
|
|
||||||
// Export common utilities for global use
|
// Export common utilities for global use
|
||||||
export { showToast, modalManager, state };
|
export { showToast, modalManager, state, lazyLoadImages, initializeInfiniteScroll };
|
||||||
@@ -65,9 +65,7 @@ class LoraPageManager {
|
|||||||
|
|
||||||
async initialize() {
|
async initialize() {
|
||||||
// Initialize page-specific components
|
// Initialize page-specific components
|
||||||
initializeInfiniteScroll();
|
|
||||||
initializeEventListeners();
|
initializeEventListeners();
|
||||||
lazyLoadImages();
|
|
||||||
restoreFolderFilter();
|
restoreFolderFilter();
|
||||||
initFolderTagsVisibility();
|
initFolderTagsVisibility();
|
||||||
new LoraContextMenu();
|
new LoraContextMenu();
|
||||||
@@ -77,6 +75,9 @@ class LoraPageManager {
|
|||||||
|
|
||||||
// Initialize the bulk manager
|
// Initialize the bulk manager
|
||||||
bulkManager.initialize();
|
bulkManager.initialize();
|
||||||
|
|
||||||
|
// Initialize common page features (lazy loading, infinite scroll)
|
||||||
|
appCore.initializePageFeatures();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ class RecipeManager {
|
|||||||
|
|
||||||
// Initialize RecipeModal
|
// Initialize RecipeModal
|
||||||
this.recipeModal = new RecipeModal();
|
this.recipeModal = new RecipeModal();
|
||||||
|
|
||||||
|
// Add state tracking for infinite scroll
|
||||||
|
this.isLoading = false;
|
||||||
|
this.hasMore = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async initialize() {
|
async initialize() {
|
||||||
@@ -27,6 +31,9 @@ class RecipeManager {
|
|||||||
|
|
||||||
// Expose necessary functions to the page
|
// Expose necessary functions to the page
|
||||||
this._exposeGlobalFunctions();
|
this._exposeGlobalFunctions();
|
||||||
|
|
||||||
|
// Initialize common page features (lazy loading, infinite scroll)
|
||||||
|
appCore.initializePageFeatures();
|
||||||
}
|
}
|
||||||
|
|
||||||
_exposeGlobalFunctions() {
|
_exposeGlobalFunctions() {
|
||||||
@@ -34,6 +41,7 @@ class RecipeManager {
|
|||||||
window.recipeManager = this;
|
window.recipeManager = this;
|
||||||
window.importRecipes = () => this.importRecipes();
|
window.importRecipes = () => this.importRecipes();
|
||||||
window.importManager = this.importManager;
|
window.importManager = this.importManager;
|
||||||
|
window.loadMoreRecipes = () => this.loadMoreRecipes();
|
||||||
}
|
}
|
||||||
|
|
||||||
initEventListeners() {
|
initEventListeners() {
|
||||||
@@ -69,10 +77,19 @@ class RecipeManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadRecipes() {
|
async loadRecipes(resetPage = true) {
|
||||||
try {
|
try {
|
||||||
// Show loading indicator
|
// Show loading indicator
|
||||||
document.body.classList.add('loading');
|
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
|
// Build query parameters
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
@@ -101,7 +118,10 @@ class RecipeManager {
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
// Update recipes grid
|
// Update recipes grid
|
||||||
this.updateRecipesGrid(data);
|
this.updateRecipesGrid(data, resetPage);
|
||||||
|
|
||||||
|
// Update pagination state
|
||||||
|
this.hasMore = data.has_more || false;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading recipes:', error);
|
console.error('Error loading recipes:', error);
|
||||||
@@ -109,32 +129,61 @@ class RecipeManager {
|
|||||||
} finally {
|
} finally {
|
||||||
// Hide loading indicator
|
// Hide loading indicator
|
||||||
document.body.classList.remove('loading');
|
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');
|
const grid = document.getElementById('recipeGrid');
|
||||||
if (!grid) return;
|
if (!grid) return;
|
||||||
|
|
||||||
// Check if data exists and has items
|
// Check if data exists and has items
|
||||||
if (!data.items || data.items.length === 0) {
|
if (!data.items || data.items.length === 0) {
|
||||||
|
if (resetGrid) {
|
||||||
grid.innerHTML = `
|
grid.innerHTML = `
|
||||||
<div class="placeholder-message">
|
<div class="placeholder-message">
|
||||||
<p>No recipes found</p>
|
<p>No recipes found</p>
|
||||||
<p>Add recipe images to your recipes folder to see them here.</p>
|
<p>Add recipe images to your recipes folder to see them here.</p>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear grid
|
// Clear grid if resetting
|
||||||
|
if (resetGrid) {
|
||||||
grid.innerHTML = '';
|
grid.innerHTML = '';
|
||||||
|
}
|
||||||
|
|
||||||
// Create recipe cards
|
// Create recipe cards
|
||||||
data.items.forEach(recipe => {
|
data.items.forEach(recipe => {
|
||||||
const recipeCard = new RecipeCard(recipe, (recipe) => this.showRecipeDetails(recipe));
|
const recipeCard = new RecipeCard(recipe, (recipe) => this.showRecipeDetails(recipe));
|
||||||
grid.appendChild(recipeCard.element);
|
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) {
|
showRecipeDetails(recipe) {
|
||||||
|
|||||||
@@ -2,12 +2,32 @@ import { state } from '../state/index.js';
|
|||||||
import { loadMoreLoras } from '../api/loraApi.js';
|
import { loadMoreLoras } from '../api/loraApi.js';
|
||||||
import { debounce } from './debounce.js';
|
import { debounce } from './debounce.js';
|
||||||
|
|
||||||
export function initializeInfiniteScroll() {
|
export function initializeInfiniteScroll(pageType = 'loras') {
|
||||||
if (state.observer) {
|
if (state.observer) {
|
||||||
state.observer.disconnect();
|
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(
|
state.observer = new IntersectionObserver(
|
||||||
(entries) => {
|
(entries) => {
|
||||||
@@ -19,6 +39,12 @@ export function initializeInfiniteScroll() {
|
|||||||
{ threshold: 0.1 }
|
{ 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');
|
const existingSentinel = document.getElementById('scroll-sentinel');
|
||||||
if (existingSentinel) {
|
if (existingSentinel) {
|
||||||
state.observer.observe(existingSentinel);
|
state.observer.observe(existingSentinel);
|
||||||
@@ -26,7 +52,7 @@ export function initializeInfiniteScroll() {
|
|||||||
const sentinel = document.createElement('div');
|
const sentinel = document.createElement('div');
|
||||||
sentinel.id = 'scroll-sentinel';
|
sentinel.id = 'scroll-sentinel';
|
||||||
sentinel.style.height = '10px';
|
sentinel.style.height = '10px';
|
||||||
document.getElementById('loraGrid').appendChild(sentinel);
|
grid.appendChild(sentinel);
|
||||||
state.observer.observe(sentinel);
|
state.observer.observe(sentinel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
<div class="controls">
|
<div class="controls">
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
<div title="Import recipes" class="control-group">
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -85,15 +85,5 @@
|
|||||||
// Will be implemented in recipes.js
|
// Will be implemented in recipes.js
|
||||||
window.location.reload();
|
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>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user