diff --git a/static/js/core.js b/static/js/core.js index 1560a1b5..a81ef9de 100644 --- a/static/js/core.js +++ b/static/js/core.js @@ -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 }; \ No newline at end of file +export { showToast, modalManager, state, lazyLoadImages, initializeInfiniteScroll }; \ No newline at end of file diff --git a/static/js/loras.js b/static/js/loras.js index 7f3c0d4c..1a7380db 100644 --- a/static/js/loras.js +++ b/static/js/loras.js @@ -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(); } } diff --git a/static/js/recipes.js b/static/js/recipes.js index de5809f8..5d13421a 100644 --- a/static/js/recipes.js +++ b/static/js/recipes.js @@ -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 = ` -
- `; + if (resetGrid) { + grid.innerHTML = ` + + `; + } 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) { diff --git a/static/js/utils/infiniteScroll.js b/static/js/utils/infiniteScroll.js index 4723bb7a..a9360b96 100644 --- a/static/js/utils/infiniteScroll.js +++ b/static/js/utils/infiniteScroll.js @@ -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); } } \ No newline at end of file diff --git a/templates/recipes.html b/templates/recipes.html index 44454c27..4c00961a 100644 --- a/templates/recipes.html +++ b/templates/recipes.html @@ -27,7 +27,7 @@