mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 13:12:12 -03:00
feat(recipes): add sync changes dropdown menu for recipe refresh
- Add syncChanges() function to recipeApi.js for quick refresh without cache rebuild - Implement dropdown menu UI in recipes page with quick refresh and full rebuild options - Add initDropdowns() method to RecipeManager for dropdown interaction handling - Update AGENTS.md with more precise instruction about running sync_translation_keys.py - Integrate sync changes functionality as default refresh behavior
This commit is contained in:
@@ -135,7 +135,7 @@ npm run test:coverage # Generate coverage report
|
|||||||
- ALWAYS use English for comments (per copilot-instructions.md)
|
- ALWAYS use English for comments (per copilot-instructions.md)
|
||||||
- Dual mode: ComfyUI plugin (folder_paths) vs standalone (settings.json)
|
- Dual mode: ComfyUI plugin (folder_paths) vs standalone (settings.json)
|
||||||
- Detection: `os.environ.get("LORA_MANAGER_STANDALONE", "0") == "1"`
|
- Detection: `os.environ.get("LORA_MANAGER_STANDALONE", "0") == "1"`
|
||||||
- Run `python scripts/sync_translation_keys.py` after UI string updates
|
- Run `python scripts/sync_translation_keys.py` after adding UI strings to `locales/en.json`
|
||||||
- Symlinks require normalized paths
|
- Symlinks require normalized paths
|
||||||
|
|
||||||
## Frontend UI Architecture
|
## Frontend UI Architecture
|
||||||
|
|||||||
@@ -259,6 +259,26 @@ export async function resetAndReload(updateFolders = false) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sync changes - quick refresh without rebuilding cache (similar to models page)
|
||||||
|
*/
|
||||||
|
export async function syncChanges() {
|
||||||
|
try {
|
||||||
|
state.loadingManager.showSimpleLoading('Syncing changes...');
|
||||||
|
|
||||||
|
// Simply reload the recipes without rebuilding cache
|
||||||
|
await resetAndReload();
|
||||||
|
|
||||||
|
showToast('toast.recipes.syncComplete', {}, 'success');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error syncing recipes:', error);
|
||||||
|
showToast('toast.recipes.syncFailed', { message: error.message }, 'error');
|
||||||
|
} finally {
|
||||||
|
state.loadingManager.hide();
|
||||||
|
state.loadingManager.restoreProgressBar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ 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 { refreshVirtualScroll } from './utils/infiniteScroll.js';
|
import { refreshVirtualScroll } from './utils/infiniteScroll.js';
|
||||||
import { refreshRecipes, RecipeSidebarApiClient } from './api/recipeApi.js';
|
import { refreshRecipes, syncChanges, RecipeSidebarApiClient } from './api/recipeApi.js';
|
||||||
import { sidebarManager } from './components/SidebarManager.js';
|
import { sidebarManager } from './components/SidebarManager.js';
|
||||||
|
|
||||||
class RecipePageControls {
|
class RecipePageControls {
|
||||||
@@ -27,7 +27,7 @@ class RecipePageControls {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshVirtualScroll();
|
await syncChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
getSidebarApiClient() {
|
getSidebarApiClient() {
|
||||||
@@ -236,6 +236,70 @@ class RecipeManager {
|
|||||||
refreshVirtualScroll();
|
refreshVirtualScroll();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize dropdown functionality for refresh button
|
||||||
|
this.initDropdowns();
|
||||||
|
}
|
||||||
|
|
||||||
|
initDropdowns() {
|
||||||
|
// Handle dropdown toggles
|
||||||
|
const dropdownToggles = document.querySelectorAll('.dropdown-toggle');
|
||||||
|
dropdownToggles.forEach(toggle => {
|
||||||
|
toggle.addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
const dropdownGroup = toggle.closest('.dropdown-group');
|
||||||
|
|
||||||
|
// Close all other open dropdowns first
|
||||||
|
document.querySelectorAll('.dropdown-group.active').forEach(group => {
|
||||||
|
if (group !== dropdownGroup) {
|
||||||
|
group.classList.remove('active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dropdownGroup.classList.toggle('active');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle quick refresh option (Sync Changes)
|
||||||
|
const quickRefreshOption = document.querySelector('[data-action="quick-refresh"]');
|
||||||
|
if (quickRefreshOption) {
|
||||||
|
quickRefreshOption.addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
this.pageControls.refreshModels(false);
|
||||||
|
this.closeDropdowns();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle full rebuild option (Rebuild Cache)
|
||||||
|
const fullRebuildOption = document.querySelector('[data-action="full-rebuild"]');
|
||||||
|
if (fullRebuildOption) {
|
||||||
|
fullRebuildOption.addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
this.pageControls.refreshModels(true);
|
||||||
|
this.closeDropdowns();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle main refresh button (default: sync changes)
|
||||||
|
const refreshBtn = document.querySelector('[data-action="refresh"]');
|
||||||
|
if (refreshBtn) {
|
||||||
|
refreshBtn.addEventListener('click', () => {
|
||||||
|
this.pageControls.refreshModels(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close dropdowns when clicking outside
|
||||||
|
document.addEventListener('click', (e) => {
|
||||||
|
if (!e.target.closest('.dropdown-group')) {
|
||||||
|
this.closeDropdowns();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
closeDropdowns() {
|
||||||
|
document.querySelectorAll('.dropdown-group.active').forEach(group => {
|
||||||
|
group.classList.remove('active');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// This method is kept for compatibility but now uses virtual scrolling
|
// This method is kept for compatibility but now uses virtual scrolling
|
||||||
|
|||||||
@@ -66,10 +66,20 @@
|
|||||||
</optgroup>
|
</optgroup>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div title="{{ t('recipes.controls.refresh.title') }}" class="control-group">
|
<div title="{{ t('recipes.controls.refresh.title') }}" class="control-group dropdown-group">
|
||||||
<button onclick="recipeManager.refreshRecipes()"><i class="fas fa-sync"></i> {{
|
<button data-action="refresh" class="dropdown-main"><i class="fas fa-sync"></i> <span>{{
|
||||||
t('common.actions.refresh')
|
t('common.actions.refresh') }}</span></button>
|
||||||
}}</button>
|
<button class="dropdown-toggle" aria-label="Show refresh options">
|
||||||
|
<i class="fas fa-caret-down"></i>
|
||||||
|
</button>
|
||||||
|
<div class="dropdown-menu">
|
||||||
|
<div class="dropdown-item" data-action="quick-refresh" title="{{ t('recipes.controls.refresh.quickTooltip', default='Sync changes - quick refresh without rebuilding cache') }}">
|
||||||
|
<i class="fas fa-bolt"></i> <span>{{ t('loras.controls.refresh.quick', default='Sync Changes') }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="dropdown-item" data-action="full-rebuild" title="{{ t('recipes.controls.refresh.fullTooltip', default='Rebuild cache - full rescan of all recipe files') }}">
|
||||||
|
<i class="fas fa-tools"></i> <span>{{ t('loras.controls.refresh.full', default='Rebuild Cache') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div title="{{ t('recipes.controls.import.title') }}" class="control-group">
|
<div title="{{ t('recipes.controls.import.title') }}" class="control-group">
|
||||||
<button onclick="importManager.showImportModal()"><i class="fas fa-file-import"></i> {{
|
<button onclick="importManager.showImportModal()"><i class="fas fa-file-import"></i> {{
|
||||||
|
|||||||
Reference in New Issue
Block a user