mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-25 07:05:43 -03:00
Refactor recipe and checkpoint management to implement virtual scrolling and improve state handling
This commit is contained in:
174
static/js/api/recipeApi.js
Normal file
174
static/js/api/recipeApi.js
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
import { RecipeCard } from '../components/RecipeCard.js';
|
||||||
|
import {
|
||||||
|
fetchModelsPage,
|
||||||
|
resetAndReloadWithVirtualScroll,
|
||||||
|
loadMoreWithVirtualScroll
|
||||||
|
} from './baseModelApi.js';
|
||||||
|
import { state, getCurrentPageState } from '../state/index.js';
|
||||||
|
import { showToast } from '../utils/uiHelpers.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch recipes with pagination for virtual scrolling
|
||||||
|
* @param {number} page - Page number to fetch
|
||||||
|
* @param {number} pageSize - Number of items per page
|
||||||
|
* @returns {Promise<Object>} Object containing items, total count, and pagination info
|
||||||
|
*/
|
||||||
|
export async function fetchRecipesPage(page = 1, pageSize = 100) {
|
||||||
|
const pageState = getCurrentPageState();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
page: page,
|
||||||
|
page_size: pageSize || pageState.pageSize || 20,
|
||||||
|
sort_by: pageState.sortBy
|
||||||
|
});
|
||||||
|
|
||||||
|
// If we have a specific recipe ID to load
|
||||||
|
if (pageState.customFilter?.active && pageState.customFilter?.recipeId) {
|
||||||
|
// Special case: load specific recipe
|
||||||
|
const response = await fetch(`/api/recipe/${pageState.customFilter.recipeId}`);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to load recipe: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const recipe = await response.json();
|
||||||
|
|
||||||
|
// Return in expected format
|
||||||
|
return {
|
||||||
|
items: [recipe],
|
||||||
|
totalItems: 1,
|
||||||
|
totalPages: 1,
|
||||||
|
currentPage: 1,
|
||||||
|
hasMore: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add custom filter for Lora if present
|
||||||
|
if (pageState.customFilter?.active && pageState.customFilter?.loraHash) {
|
||||||
|
params.append('lora_hash', pageState.customFilter.loraHash);
|
||||||
|
params.append('bypass_filters', 'true');
|
||||||
|
} else {
|
||||||
|
// Normal filtering logic
|
||||||
|
|
||||||
|
// Add search filter if present
|
||||||
|
if (pageState.filters?.search) {
|
||||||
|
params.append('search', pageState.filters.search);
|
||||||
|
|
||||||
|
// Add search option parameters
|
||||||
|
if (pageState.searchOptions) {
|
||||||
|
params.append('search_title', pageState.searchOptions.title.toString());
|
||||||
|
params.append('search_tags', pageState.searchOptions.tags.toString());
|
||||||
|
params.append('search_lora_name', pageState.searchOptions.loraName.toString());
|
||||||
|
params.append('search_lora_model', pageState.searchOptions.loraModel.toString());
|
||||||
|
params.append('fuzzy', 'true');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add base model filters
|
||||||
|
if (pageState.filters?.baseModel && pageState.filters.baseModel.length) {
|
||||||
|
params.append('base_models', pageState.filters.baseModel.join(','));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add tag filters
|
||||||
|
if (pageState.filters?.tags && pageState.filters.tags.length) {
|
||||||
|
params.append('tags', pageState.filters.tags.join(','));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch recipes
|
||||||
|
const response = await fetch(`/api/recipes?${params.toString()}`);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to load recipes: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
return {
|
||||||
|
items: data.items,
|
||||||
|
totalItems: data.total,
|
||||||
|
totalPages: data.total_pages,
|
||||||
|
currentPage: page,
|
||||||
|
hasMore: page < data.total_pages
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching recipes:', error);
|
||||||
|
showToast(`Failed to fetch recipes: ${error.message}`, 'error');
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset and reload recipes using virtual scrolling
|
||||||
|
* @param {boolean} updateFolders - Whether to update folder tags
|
||||||
|
* @returns {Promise<Object>} The fetch result
|
||||||
|
*/
|
||||||
|
export async function resetAndReload(updateFolders = false) {
|
||||||
|
return resetAndReloadWithVirtualScroll({
|
||||||
|
modelType: 'recipe',
|
||||||
|
updateFolders,
|
||||||
|
fetchPageFunction: fetchRecipesPage
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refreshes the recipe list by first rebuilding the cache and then loading recipes
|
||||||
|
*/
|
||||||
|
export async function refreshRecipes() {
|
||||||
|
try {
|
||||||
|
state.loadingManager.showSimpleLoading('Refreshing recipes...');
|
||||||
|
|
||||||
|
// Call the API endpoint to rebuild the recipe cache
|
||||||
|
const response = await fetch('/api/recipes/scan');
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
throw new Error(data.error || 'Failed to refresh recipe cache');
|
||||||
|
}
|
||||||
|
|
||||||
|
// After successful cache rebuild, reload the recipes
|
||||||
|
await resetAndReload();
|
||||||
|
|
||||||
|
showToast('Refresh complete', 'success');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error refreshing recipes:', error);
|
||||||
|
showToast(error.message || 'Failed to refresh recipes', 'error');
|
||||||
|
} finally {
|
||||||
|
state.loadingManager.hide();
|
||||||
|
state.loadingManager.restoreProgressBar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load more recipes with pagination - updated to work with VirtualScroller
|
||||||
|
* @param {boolean} resetPage - Whether to reset to the first page
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
export async function loadMoreRecipes(resetPage = false) {
|
||||||
|
const pageState = getCurrentPageState();
|
||||||
|
|
||||||
|
// Use virtual scroller if available
|
||||||
|
if (state.virtualScroller) {
|
||||||
|
return loadMoreWithVirtualScroll({
|
||||||
|
modelType: 'recipe',
|
||||||
|
resetPage,
|
||||||
|
updateFolders: false,
|
||||||
|
fetchPageFunction: fetchRecipesPage
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a recipe card instance from recipe data
|
||||||
|
* @param {Object} recipe - Recipe data
|
||||||
|
* @returns {HTMLElement} Recipe card DOM element
|
||||||
|
*/
|
||||||
|
export function createRecipeCard(recipe) {
|
||||||
|
const recipeCard = new RecipeCard(recipe, (recipe) => {
|
||||||
|
if (window.recipeManager) {
|
||||||
|
window.recipeManager.showRecipeDetails(recipe);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return recipeCard.element;
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import { appCore } from './core.js';
|
import { appCore } from './core.js';
|
||||||
import { initializeInfiniteScroll } from './utils/infiniteScroll.js';
|
|
||||||
import { confirmDelete, closeDeleteModal, confirmExclude, closeExcludeModal } from './utils/modalUtils.js';
|
import { confirmDelete, closeDeleteModal, confirmExclude, closeExcludeModal } from './utils/modalUtils.js';
|
||||||
import { createPageControls } from './components/controls/index.js';
|
import { createPageControls } from './components/controls/index.js';
|
||||||
import { loadMoreCheckpoints } from './api/checkpointApi.js';
|
import { loadMoreCheckpoints } from './api/checkpointApi.js';
|
||||||
@@ -40,9 +39,6 @@ class CheckpointsPageManager {
|
|||||||
// Initialize context menu
|
// Initialize context menu
|
||||||
new CheckpointContextMenu();
|
new CheckpointContextMenu();
|
||||||
|
|
||||||
// Initialize infinite scroll
|
|
||||||
initializeInfiniteScroll('checkpoints');
|
|
||||||
|
|
||||||
// Initialize common page features
|
// Initialize common page features
|
||||||
appCore.initializePageFeatures();
|
appCore.initializePageFeatures();
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
// Recipe manager module
|
// Recipe manager module
|
||||||
import { appCore } from './core.js';
|
import { appCore } from './core.js';
|
||||||
import { ImportManager } from './managers/ImportManager.js';
|
import { ImportManager } from './managers/ImportManager.js';
|
||||||
import { RecipeCard } from './components/RecipeCard.js';
|
|
||||||
import { RecipeModal } from './components/RecipeModal.js';
|
import { RecipeModal } from './components/RecipeModal.js';
|
||||||
import { getCurrentPageState } from './state/index.js';
|
import { getCurrentPageState, state } from './state/index.js';
|
||||||
import { getSessionItem, removeSessionItem } from './utils/storageHelpers.js';
|
import { getSessionItem, removeSessionItem } from './utils/storageHelpers.js';
|
||||||
import { RecipeContextMenu } from './components/ContextMenu/index.js';
|
import { RecipeContextMenu } from './components/ContextMenu/index.js';
|
||||||
import { DuplicatesManager } from './components/DuplicatesManager.js';
|
import { DuplicatesManager } from './components/DuplicatesManager.js';
|
||||||
import { initializeInfiniteScroll } from './utils/infiniteScroll.js';
|
import { initializeInfiniteScroll, refreshVirtualScroll } from './utils/infiniteScroll.js';
|
||||||
|
import { resetAndReload, refreshRecipes } from './api/recipeApi.js';
|
||||||
|
|
||||||
class RecipeManager {
|
class RecipeManager {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -27,8 +27,8 @@ class RecipeManager {
|
|||||||
this.pageState.isLoading = false;
|
this.pageState.isLoading = false;
|
||||||
this.pageState.hasMore = true;
|
this.pageState.hasMore = true;
|
||||||
|
|
||||||
// Custom filter state
|
// Custom filter state - move to pageState for compatibility with virtual scrolling
|
||||||
this.customFilter = {
|
this.pageState.customFilter = {
|
||||||
active: false,
|
active: false,
|
||||||
loraName: null,
|
loraName: null,
|
||||||
loraHash: null,
|
loraHash: null,
|
||||||
@@ -49,13 +49,10 @@ class RecipeManager {
|
|||||||
// Check for custom filter parameters in session storage
|
// Check for custom filter parameters in session storage
|
||||||
this._checkCustomFilter();
|
this._checkCustomFilter();
|
||||||
|
|
||||||
// Load initial set of recipes
|
|
||||||
await this.loadRecipes();
|
|
||||||
|
|
||||||
// Expose necessary functions to the page
|
// Expose necessary functions to the page
|
||||||
this._exposeGlobalFunctions();
|
this._exposeGlobalFunctions();
|
||||||
|
|
||||||
// Initialize common page features (lazy loading, infinite scroll)
|
// Initialize common page features
|
||||||
appCore.initializePageFeatures();
|
appCore.initializePageFeatures();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,7 +84,7 @@ class RecipeManager {
|
|||||||
|
|
||||||
// Set custom filter if any parameter is present
|
// Set custom filter if any parameter is present
|
||||||
if (filterLoraName || filterLoraHash || viewRecipeId) {
|
if (filterLoraName || filterLoraHash || viewRecipeId) {
|
||||||
this.customFilter = {
|
this.pageState.customFilter = {
|
||||||
active: true,
|
active: true,
|
||||||
loraName: filterLoraName,
|
loraName: filterLoraName,
|
||||||
loraHash: filterLoraHash,
|
loraHash: filterLoraHash,
|
||||||
@@ -108,11 +105,11 @@ class RecipeManager {
|
|||||||
// Update text based on filter type
|
// Update text based on filter type
|
||||||
let filterText = '';
|
let filterText = '';
|
||||||
|
|
||||||
if (this.customFilter.recipeId) {
|
if (this.pageState.customFilter.recipeId) {
|
||||||
filterText = 'Viewing specific recipe';
|
filterText = 'Viewing specific recipe';
|
||||||
} else if (this.customFilter.loraName) {
|
} else if (this.pageState.customFilter.loraName) {
|
||||||
// Format with Lora name
|
// Format with Lora name
|
||||||
const loraName = this.customFilter.loraName;
|
const loraName = this.pageState.customFilter.loraName;
|
||||||
const displayName = loraName.length > 25 ?
|
const displayName = loraName.length > 25 ?
|
||||||
loraName.substring(0, 22) + '...' :
|
loraName.substring(0, 22) + '...' :
|
||||||
loraName;
|
loraName;
|
||||||
@@ -125,8 +122,8 @@ class RecipeManager {
|
|||||||
// Update indicator text and show it
|
// Update indicator text and show it
|
||||||
textElement.innerHTML = filterText;
|
textElement.innerHTML = filterText;
|
||||||
// Add title attribute to show the lora name as a tooltip
|
// Add title attribute to show the lora name as a tooltip
|
||||||
if (this.customFilter.loraName) {
|
if (this.pageState.customFilter.loraName) {
|
||||||
textElement.setAttribute('title', this.customFilter.loraName);
|
textElement.setAttribute('title', this.pageState.customFilter.loraName);
|
||||||
}
|
}
|
||||||
indicator.classList.remove('hidden');
|
indicator.classList.remove('hidden');
|
||||||
|
|
||||||
@@ -149,7 +146,7 @@ class RecipeManager {
|
|||||||
|
|
||||||
_clearCustomFilter() {
|
_clearCustomFilter() {
|
||||||
// Reset custom filter
|
// Reset custom filter
|
||||||
this.customFilter = {
|
this.pageState.customFilter = {
|
||||||
active: false,
|
active: false,
|
||||||
loraName: null,
|
loraName: null,
|
||||||
loraHash: null,
|
loraHash: null,
|
||||||
@@ -167,8 +164,8 @@ class RecipeManager {
|
|||||||
removeSessionItem('lora_to_recipe_filterLoraHash');
|
removeSessionItem('lora_to_recipe_filterLoraHash');
|
||||||
removeSessionItem('viewRecipeId');
|
removeSessionItem('viewRecipeId');
|
||||||
|
|
||||||
// Reload recipes without custom filter
|
// Reset and refresh the virtual scroller
|
||||||
this.loadRecipes();
|
refreshVirtualScroll();
|
||||||
}
|
}
|
||||||
|
|
||||||
initEventListeners() {
|
initEventListeners() {
|
||||||
@@ -177,105 +174,21 @@ class RecipeManager {
|
|||||||
if (sortSelect) {
|
if (sortSelect) {
|
||||||
sortSelect.addEventListener('change', () => {
|
sortSelect.addEventListener('change', () => {
|
||||||
this.pageState.sortBy = sortSelect.value;
|
this.pageState.sortBy = sortSelect.value;
|
||||||
this.loadRecipes();
|
refreshVirtualScroll();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This method is kept for compatibility but now uses virtual scrolling
|
||||||
async loadRecipes(resetPage = true) {
|
async loadRecipes(resetPage = true) {
|
||||||
try {
|
// Skip loading if in duplicates mode
|
||||||
// Skip loading if in duplicates mode
|
const pageState = getCurrentPageState();
|
||||||
const pageState = getCurrentPageState();
|
if (pageState.duplicatesMode) {
|
||||||
if (pageState.duplicatesMode) {
|
return;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Show loading indicator
|
if (resetPage) {
|
||||||
document.body.classList.add('loading');
|
refreshVirtualScroll();
|
||||||
this.pageState.isLoading = true;
|
|
||||||
|
|
||||||
// Reset to first page if requested
|
|
||||||
if (resetPage) {
|
|
||||||
this.pageState.currentPage = 1;
|
|
||||||
// Clear grid if resetting
|
|
||||||
const grid = document.getElementById('recipeGrid');
|
|
||||||
if (grid) grid.innerHTML = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have a specific recipe ID to load
|
|
||||||
if (this.customFilter.active && this.customFilter.recipeId) {
|
|
||||||
await this._loadSpecificRecipe(this.customFilter.recipeId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build query parameters
|
|
||||||
const params = new URLSearchParams({
|
|
||||||
page: this.pageState.currentPage,
|
|
||||||
page_size: this.pageState.pageSize || 20,
|
|
||||||
sort_by: this.pageState.sortBy
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add custom filter for Lora if present
|
|
||||||
if (this.customFilter.active && this.customFilter.loraHash) {
|
|
||||||
params.append('lora_hash', this.customFilter.loraHash);
|
|
||||||
|
|
||||||
// Skip other filters when using custom filter
|
|
||||||
params.append('bypass_filters', 'true');
|
|
||||||
} else {
|
|
||||||
// Normal filtering logic
|
|
||||||
|
|
||||||
// Add search filter if present
|
|
||||||
if (this.pageState.filters.search) {
|
|
||||||
params.append('search', this.pageState.filters.search);
|
|
||||||
|
|
||||||
// Add search option parameters
|
|
||||||
if (this.pageState.searchOptions) {
|
|
||||||
params.append('search_title', this.pageState.searchOptions.title.toString());
|
|
||||||
params.append('search_tags', this.pageState.searchOptions.tags.toString());
|
|
||||||
params.append('search_lora_name', this.pageState.searchOptions.loraName.toString());
|
|
||||||
params.append('search_lora_model', this.pageState.searchOptions.loraModel.toString());
|
|
||||||
params.append('fuzzy', 'true');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add base model filters
|
|
||||||
if (this.pageState.filters.baseModel && this.pageState.filters.baseModel.length) {
|
|
||||||
params.append('base_models', this.pageState.filters.baseModel.join(','));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add tag filters
|
|
||||||
if (this.pageState.filters.tags && this.pageState.filters.tags.length) {
|
|
||||||
params.append('tags', this.pageState.filters.tags.join(','));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch recipes
|
|
||||||
const response = await fetch(`/api/recipes?${params.toString()}`);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Failed to load recipes: ${response.statusText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
// Update recipes grid
|
|
||||||
this.updateRecipesGrid(data, resetPage);
|
|
||||||
|
|
||||||
// Update pagination state based on current page and total pages
|
|
||||||
this.pageState.hasMore = data.page < data.total_pages;
|
|
||||||
|
|
||||||
// Increment the page number AFTER successful loading
|
|
||||||
if (data.items.length > 0) {
|
|
||||||
this.pageState.currentPage++;
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error loading recipes:', error);
|
|
||||||
appCore.showToast('Failed to load recipes', 'error');
|
|
||||||
} finally {
|
|
||||||
// Hide loading indicator
|
|
||||||
document.body.classList.remove('loading');
|
|
||||||
this.pageState.isLoading = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,95 +196,7 @@ class RecipeManager {
|
|||||||
* Refreshes the recipe list by first rebuilding the cache and then loading recipes
|
* Refreshes the recipe list by first rebuilding the cache and then loading recipes
|
||||||
*/
|
*/
|
||||||
async refreshRecipes() {
|
async refreshRecipes() {
|
||||||
try {
|
return refreshRecipes();
|
||||||
// Call the new endpoint to rebuild the recipe cache
|
|
||||||
const response = await fetch('/api/recipes/scan');
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const data = await response.json();
|
|
||||||
throw new Error(data.error || 'Failed to refresh recipe cache');
|
|
||||||
}
|
|
||||||
|
|
||||||
// After successful cache rebuild, load the recipes
|
|
||||||
await this.loadRecipes(true);
|
|
||||||
|
|
||||||
appCore.showToast('Refresh complete', 'success');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error refreshing recipes:', error);
|
|
||||||
appCore.showToast(error.message || 'Failed to refresh recipes', 'error');
|
|
||||||
|
|
||||||
// Still try to load recipes even if scan failed
|
|
||||||
await this.loadRecipes(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async _loadSpecificRecipe(recipeId) {
|
|
||||||
try {
|
|
||||||
// Fetch specific recipe by ID
|
|
||||||
const response = await fetch(`/api/recipe/${recipeId}`);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Failed to load recipe: ${response.statusText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const recipe = await response.json();
|
|
||||||
|
|
||||||
// Create a data structure that matches the expected format
|
|
||||||
const recipeData = {
|
|
||||||
items: [recipe],
|
|
||||||
total: 1,
|
|
||||||
page: 1,
|
|
||||||
page_size: 1,
|
|
||||||
total_pages: 1
|
|
||||||
};
|
|
||||||
|
|
||||||
// Update grid with single recipe
|
|
||||||
this.updateRecipesGrid(recipeData, true);
|
|
||||||
|
|
||||||
// Pagination not needed for single recipe
|
|
||||||
this.pageState.hasMore = false;
|
|
||||||
|
|
||||||
// Show recipe details modal
|
|
||||||
setTimeout(() => {
|
|
||||||
this.showRecipeDetails(recipe);
|
|
||||||
}, 300);
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error loading specific recipe:', error);
|
|
||||||
appCore.showToast('Failed to load recipe details', 'error');
|
|
||||||
|
|
||||||
// Clear the filter and show all recipes
|
|
||||||
this._clearCustomFilter();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
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 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);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
showRecipeDetails(recipe) {
|
showRecipeDetails(recipe) {
|
||||||
@@ -397,7 +222,7 @@ class RecipeManager {
|
|||||||
|
|
||||||
exitDuplicateMode() {
|
exitDuplicateMode() {
|
||||||
this.duplicatesManager.exitDuplicateMode();
|
this.duplicatesManager.exitDuplicateMode();
|
||||||
initializeInfiniteScroll();
|
initializeInfiniteScroll('recipes');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,13 +11,18 @@ async function getCardCreator(pageType) {
|
|||||||
if (pageType === 'loras') {
|
if (pageType === 'loras') {
|
||||||
return createLoraCard;
|
return createLoraCard;
|
||||||
} else if (pageType === 'recipes') {
|
} else if (pageType === 'recipes') {
|
||||||
try {
|
// Import the RecipeCard module
|
||||||
const { createRecipeCard } = await import('../components/RecipeCard.js');
|
const { RecipeCard } = await import('../components/RecipeCard.js');
|
||||||
return createRecipeCard;
|
|
||||||
} catch (err) {
|
// Return a wrapper function that creates a recipe card element
|
||||||
console.error('Failed to load recipe card creator:', err);
|
return (recipe) => {
|
||||||
return null;
|
const recipeCard = new RecipeCard(recipe, (recipe) => {
|
||||||
}
|
if (window.recipeManager) {
|
||||||
|
window.recipeManager.showRecipeDetails(recipe);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return recipeCard.element;
|
||||||
|
};
|
||||||
} else if (pageType === 'checkpoints') {
|
} else if (pageType === 'checkpoints') {
|
||||||
return createCheckpointCard;
|
return createCheckpointCard;
|
||||||
}
|
}
|
||||||
@@ -29,13 +34,9 @@ async function getDataFetcher(pageType) {
|
|||||||
if (pageType === 'loras') {
|
if (pageType === 'loras') {
|
||||||
return fetchLorasPage;
|
return fetchLorasPage;
|
||||||
} else if (pageType === 'recipes') {
|
} else if (pageType === 'recipes') {
|
||||||
try {
|
// Import the recipeApi module and use the fetchRecipesPage function
|
||||||
const { fetchRecipesPage } = await import('../api/recipeApi.js');
|
const { fetchRecipesPage } = await import('../api/recipeApi.js');
|
||||||
return fetchRecipesPage;
|
return fetchRecipesPage;
|
||||||
} catch (err) {
|
|
||||||
console.error('Failed to load recipe data fetcher:', err);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} else if (pageType === 'checkpoints') {
|
} else if (pageType === 'checkpoints') {
|
||||||
return fetchCheckpointsPage;
|
return fetchCheckpointsPage;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user