mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 21:22:11 -03:00
update
This commit is contained in:
@@ -5,7 +5,6 @@ import { initializeCommonComponents } from './common.js';
|
||||
import { ImportManager } from './managers/ImportManager.js';
|
||||
import { RecipeCard } from './components/RecipeCard.js';
|
||||
import { RecipeModal } from './components/RecipeModal.js';
|
||||
import { SearchManager } from './managers/SearchManager.js';
|
||||
import { HeaderManager } from './components/Header.js';
|
||||
|
||||
class RecipeManager {
|
||||
@@ -30,18 +29,6 @@ class RecipeManager {
|
||||
|
||||
// Load initial set of recipes
|
||||
this.loadRecipes();
|
||||
|
||||
// Initialize search manager with Recipe-specific options
|
||||
const recipeSearchManager = new SearchManager({
|
||||
searchCallback: (query, options, recursive) => {
|
||||
// Recipe-specific search implementation
|
||||
fetchRecipes({
|
||||
search: query,
|
||||
search_options: options,
|
||||
recursive: recursive
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Set the current page for proper context
|
||||
document.body.dataset.page = 'recipes';
|
||||
|
||||
@@ -1,328 +0,0 @@
|
||||
import { appendLoraCards } from '../api/loraApi.js';
|
||||
import { state } from '../state/index.js';
|
||||
import { resetAndReload } from '../api/loraApi.js';
|
||||
import { showToast } from './uiHelpers.js';
|
||||
|
||||
export class SearchManager {
|
||||
constructor() {
|
||||
// Initialize search manager
|
||||
this.searchInput = document.getElementById('searchInput');
|
||||
this.searchOptionsToggle = document.getElementById('searchOptionsToggle');
|
||||
this.searchOptionsPanel = document.getElementById('searchOptionsPanel');
|
||||
this.recursiveSearchToggle = document.getElementById('recursiveSearchToggle');
|
||||
this.searchDebounceTimeout = null;
|
||||
this.currentSearchTerm = '';
|
||||
this.isSearching = false;
|
||||
|
||||
// Add clear button
|
||||
this.createClearButton();
|
||||
|
||||
// Add this instance to state
|
||||
state.searchManager = this;
|
||||
|
||||
if (this.searchInput) {
|
||||
this.searchInput.addEventListener('input', this.handleSearch.bind(this));
|
||||
// Update clear button visibility on input
|
||||
this.searchInput.addEventListener('input', () => {
|
||||
this.updateClearButtonVisibility();
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize search options
|
||||
this.initSearchOptions();
|
||||
|
||||
// Add global click handler to close panels when clicking outside
|
||||
document.addEventListener('click', (e) => {
|
||||
// Close search options panel when clicking outside
|
||||
if (this.searchOptionsPanel &&
|
||||
!this.searchOptionsPanel.contains(e.target) &&
|
||||
e.target !== this.searchOptionsToggle &&
|
||||
!this.searchOptionsToggle.contains(e.target)) {
|
||||
this.closeSearchOptionsPanel();
|
||||
}
|
||||
|
||||
// Close filter panel when clicking outside (if filterManager exists)
|
||||
const filterPanel = document.getElementById('filterPanel');
|
||||
const filterButton = document.getElementById('filterButton');
|
||||
if (filterPanel &&
|
||||
!filterPanel.contains(e.target) &&
|
||||
e.target !== filterButton &&
|
||||
!filterButton.contains(e.target) &&
|
||||
window.filterManager) {
|
||||
window.filterManager.closeFilterPanel();
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize panel positions
|
||||
this.updatePanelPositions();
|
||||
|
||||
// Add resize listener
|
||||
window.addEventListener('resize', this.updatePanelPositions.bind(this));
|
||||
}
|
||||
|
||||
initSearchOptions() {
|
||||
// Load recursive search state from localStorage
|
||||
state.searchOptions.recursive = localStorage.getItem('recursiveSearch') === 'true';
|
||||
|
||||
if (this.recursiveSearchToggle) {
|
||||
this.recursiveSearchToggle.checked = state.searchOptions.recursive;
|
||||
this.recursiveSearchToggle.addEventListener('change', (e) => {
|
||||
state.searchOptions.recursive = e.target.checked;
|
||||
localStorage.setItem('recursiveSearch', state.searchOptions.recursive);
|
||||
|
||||
// Rerun search if there's an active search term
|
||||
if (this.currentSearchTerm) {
|
||||
this.performSearch(this.currentSearchTerm);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Setup search options toggle
|
||||
if (this.searchOptionsToggle) {
|
||||
this.searchOptionsToggle.addEventListener('click', () => {
|
||||
this.toggleSearchOptionsPanel();
|
||||
});
|
||||
}
|
||||
|
||||
// Close button for search options panel
|
||||
const closeButton = document.getElementById('closeSearchOptions');
|
||||
if (closeButton) {
|
||||
closeButton.addEventListener('click', () => {
|
||||
this.closeSearchOptionsPanel();
|
||||
});
|
||||
}
|
||||
|
||||
// Setup search option tags
|
||||
const optionTags = document.querySelectorAll('.search-option-tag');
|
||||
optionTags.forEach(tag => {
|
||||
const option = tag.dataset.option;
|
||||
|
||||
// Initialize tag state from state
|
||||
tag.classList.toggle('active', state.searchOptions[option]);
|
||||
|
||||
tag.addEventListener('click', () => {
|
||||
// Check if clicking would deselect the last active option
|
||||
const activeOptions = document.querySelectorAll('.search-option-tag.active');
|
||||
if (activeOptions.length === 1 && activeOptions[0] === tag) {
|
||||
// Don't allow deselecting the last option and show toast
|
||||
showToast('At least one search option must be selected', 'info');
|
||||
return;
|
||||
}
|
||||
|
||||
tag.classList.toggle('active');
|
||||
state.searchOptions[option] = tag.classList.contains('active');
|
||||
|
||||
// Save to localStorage
|
||||
localStorage.setItem(`searchOption_${option}`, state.searchOptions[option]);
|
||||
|
||||
// Rerun search if there's an active search term
|
||||
if (this.currentSearchTerm) {
|
||||
this.performSearch(this.currentSearchTerm);
|
||||
}
|
||||
});
|
||||
|
||||
// Load option state from localStorage or use default
|
||||
const savedState = localStorage.getItem(`searchOption_${option}`);
|
||||
if (savedState !== null) {
|
||||
state.searchOptions[option] = savedState === 'true';
|
||||
tag.classList.toggle('active', state.searchOptions[option]);
|
||||
}
|
||||
});
|
||||
|
||||
// Ensure at least one search option is selected
|
||||
this.validateSearchOptions();
|
||||
}
|
||||
|
||||
// Add method to validate search options
|
||||
validateSearchOptions() {
|
||||
const hasActiveOption = Object.values(state.searchOptions)
|
||||
.some(value => value === true && value !== state.searchOptions.recursive);
|
||||
|
||||
// If no search options are active, activate at least one default option
|
||||
if (!hasActiveOption) {
|
||||
state.searchOptions.filename = true;
|
||||
localStorage.setItem('searchOption_filename', 'true');
|
||||
|
||||
// Update UI to match
|
||||
const fileNameTag = document.querySelector('.search-option-tag[data-option="filename"]');
|
||||
if (fileNameTag) {
|
||||
fileNameTag.classList.add('active');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toggleSearchOptionsPanel() {
|
||||
if (this.searchOptionsPanel) {
|
||||
const isHidden = this.searchOptionsPanel.classList.contains('hidden');
|
||||
if (isHidden) {
|
||||
// Update position before showing
|
||||
this.updatePanelPositions();
|
||||
this.searchOptionsPanel.classList.remove('hidden');
|
||||
this.searchOptionsToggle.classList.add('active');
|
||||
} else {
|
||||
this.closeSearchOptionsPanel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closeSearchOptionsPanel() {
|
||||
if (this.searchOptionsPanel) {
|
||||
this.searchOptionsPanel.classList.add('hidden');
|
||||
this.searchOptionsToggle.classList.remove('active');
|
||||
}
|
||||
}
|
||||
|
||||
createClearButton() {
|
||||
// Create clear button
|
||||
const clearButton = document.createElement('button');
|
||||
clearButton.className = 'search-clear';
|
||||
clearButton.innerHTML = '<i class="fas fa-times"></i>';
|
||||
clearButton.title = 'Clear search';
|
||||
|
||||
// Add click handler
|
||||
clearButton.addEventListener('click', () => {
|
||||
this.searchInput.value = '';
|
||||
this.currentSearchTerm = '';
|
||||
this.updateClearButtonVisibility();
|
||||
resetAndReload();
|
||||
});
|
||||
|
||||
// Insert after search input
|
||||
this.searchInput.parentNode.appendChild(clearButton);
|
||||
this.clearButton = clearButton;
|
||||
|
||||
// Set initial visibility
|
||||
this.updateClearButtonVisibility();
|
||||
}
|
||||
|
||||
updateClearButtonVisibility() {
|
||||
if (this.clearButton) {
|
||||
this.clearButton.classList.toggle('visible', this.searchInput.value.length > 0);
|
||||
}
|
||||
}
|
||||
|
||||
handleSearch(event) {
|
||||
if (this.searchDebounceTimeout) {
|
||||
clearTimeout(this.searchDebounceTimeout);
|
||||
}
|
||||
|
||||
this.searchDebounceTimeout = setTimeout(async () => {
|
||||
const searchTerm = event.target.value.trim().toLowerCase();
|
||||
|
||||
if (searchTerm !== this.currentSearchTerm && !this.isSearching) {
|
||||
this.currentSearchTerm = searchTerm;
|
||||
await this.performSearch(searchTerm);
|
||||
}
|
||||
}, 250);
|
||||
}
|
||||
|
||||
async performSearch(searchTerm) {
|
||||
const grid = document.getElementById('loraGrid');
|
||||
|
||||
if (!searchTerm) {
|
||||
state.currentPage = 1;
|
||||
await resetAndReload();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.isSearching = true;
|
||||
state.loadingManager.showSimpleLoading('Searching...');
|
||||
|
||||
// Store current scroll position
|
||||
const scrollPosition = window.pageYOffset || document.documentElement.scrollTop;
|
||||
|
||||
state.currentPage = 1;
|
||||
state.hasMore = true;
|
||||
|
||||
const url = new URL('/api/loras', window.location.origin);
|
||||
url.searchParams.set('page', '1');
|
||||
url.searchParams.set('page_size', '20');
|
||||
url.searchParams.set('sort_by', state.sortBy);
|
||||
url.searchParams.set('search', searchTerm);
|
||||
url.searchParams.set('fuzzy', 'true');
|
||||
|
||||
// Add search options
|
||||
url.searchParams.set('search_filename', state.searchOptions.filename.toString());
|
||||
url.searchParams.set('search_modelname', state.searchOptions.modelname.toString());
|
||||
url.searchParams.set('search_tags', state.searchOptions.tags.toString());
|
||||
|
||||
// Always send folder parameter if there is an active folder
|
||||
if (state.activeFolder) {
|
||||
url.searchParams.set('folder', state.activeFolder);
|
||||
// Add recursive parameter when recursive search is enabled
|
||||
url.searchParams.set('recursive', state.searchOptions.recursive.toString());
|
||||
}
|
||||
|
||||
const response = await fetch(url);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Search failed');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (searchTerm === this.currentSearchTerm) {
|
||||
grid.innerHTML = '';
|
||||
|
||||
if (data.items.length === 0) {
|
||||
grid.innerHTML = '<div class="no-results">No matching loras found</div>';
|
||||
state.hasMore = false;
|
||||
} else {
|
||||
appendLoraCards(data.items);
|
||||
state.hasMore = state.currentPage < data.total_pages;
|
||||
state.currentPage++;
|
||||
}
|
||||
|
||||
// Restore scroll position after content is loaded
|
||||
setTimeout(() => {
|
||||
window.scrollTo({
|
||||
top: scrollPosition,
|
||||
behavior: 'instant' // Use 'instant' to prevent animation
|
||||
});
|
||||
}, 10);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Search error:', error);
|
||||
showToast('Search failed', 'error');
|
||||
} finally {
|
||||
this.isSearching = false;
|
||||
state.loadingManager.hide();
|
||||
}
|
||||
}
|
||||
|
||||
updatePanelPositions() {
|
||||
const searchOptionsPanel = document.getElementById('searchOptionsPanel');
|
||||
const filterPanel = document.getElementById('filterPanel');
|
||||
|
||||
if (!searchOptionsPanel || !filterPanel) return;
|
||||
|
||||
// Get the header element
|
||||
const header = document.querySelector('.app-header');
|
||||
if (!header) return;
|
||||
|
||||
// Calculate the position based on the bottom of the header
|
||||
const headerRect = header.getBoundingClientRect();
|
||||
const topPosition = headerRect.bottom + 5; // Add 5px padding
|
||||
|
||||
// Set the positions
|
||||
searchOptionsPanel.style.top = `${topPosition}px`;
|
||||
filterPanel.style.top = `${topPosition}px`;
|
||||
|
||||
// Adjust panel horizontal position based on the search container
|
||||
const searchContainer = document.querySelector('.header-search');
|
||||
if (searchContainer) {
|
||||
const searchRect = searchContainer.getBoundingClientRect();
|
||||
|
||||
// Position the search options panel aligned with the search container
|
||||
searchOptionsPanel.style.right = `${window.innerWidth - searchRect.right}px`;
|
||||
|
||||
// Position the filter panel aligned with the filter button
|
||||
const filterButton = document.getElementById('filterButton');
|
||||
if (filterButton) {
|
||||
const filterRect = filterButton.getBoundingClientRect();
|
||||
filterPanel.style.right = `${window.innerWidth - filterRect.right}px`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user