mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-24 06:32:12 -03:00
Add bulk operation
This commit is contained in:
106
static/js/managers/BulkManager.js
Normal file
106
static/js/managers/BulkManager.js
Normal file
@@ -0,0 +1,106 @@
|
||||
import { state } from '../state/index.js';
|
||||
import { showToast } from '../utils/uiHelpers.js';
|
||||
import { updateCardsForBulkMode } from '../components/LoraCard.js';
|
||||
|
||||
export class BulkManager {
|
||||
constructor() {
|
||||
this.bulkBtn = document.getElementById('bulkOperationsBtn');
|
||||
this.bulkPanel = document.getElementById('bulkOperationsPanel');
|
||||
|
||||
// Initialize selected loras set in state if not already there
|
||||
if (!state.selectedLoras) {
|
||||
state.selectedLoras = new Set();
|
||||
}
|
||||
}
|
||||
|
||||
initialize() {
|
||||
// Add event listeners if needed
|
||||
// (Already handled via onclick attributes in HTML, but could be moved here)
|
||||
}
|
||||
|
||||
toggleBulkMode() {
|
||||
// Toggle the state
|
||||
state.bulkMode = !state.bulkMode;
|
||||
|
||||
// Update UI
|
||||
this.bulkBtn.classList.toggle('active', state.bulkMode);
|
||||
|
||||
// Important: Remove the hidden class when entering bulk mode
|
||||
if (state.bulkMode) {
|
||||
this.bulkPanel.classList.remove('hidden');
|
||||
// Use setTimeout to ensure the DOM updates before adding visible class
|
||||
// This helps with the transition animation
|
||||
setTimeout(() => {
|
||||
this.bulkPanel.classList.add('visible');
|
||||
}, 10);
|
||||
} else {
|
||||
this.bulkPanel.classList.remove('visible');
|
||||
// Add hidden class back after transition completes
|
||||
setTimeout(() => {
|
||||
this.bulkPanel.classList.add('hidden');
|
||||
}, 400); // Match this with the transition duration in CSS
|
||||
}
|
||||
|
||||
// Update all cards
|
||||
updateCardsForBulkMode(state.bulkMode);
|
||||
|
||||
// Clear selection if exiting bulk mode
|
||||
if (!state.bulkMode) {
|
||||
this.clearSelection();
|
||||
}
|
||||
}
|
||||
|
||||
clearSelection() {
|
||||
document.querySelectorAll('.lora-card.selected').forEach(card => {
|
||||
card.classList.remove('selected');
|
||||
});
|
||||
state.selectedLoras.clear();
|
||||
this.updateSelectedCount();
|
||||
}
|
||||
|
||||
updateSelectedCount() {
|
||||
const selectedCards = document.querySelectorAll('.lora-card.selected');
|
||||
const countElement = document.getElementById('selectedCount');
|
||||
|
||||
if (countElement) {
|
||||
countElement.textContent = `${selectedCards.length} selected`;
|
||||
}
|
||||
|
||||
// Update state with selected loras
|
||||
state.selectedLoras.clear();
|
||||
selectedCards.forEach(card => {
|
||||
state.selectedLoras.add(card.dataset.filepath);
|
||||
});
|
||||
}
|
||||
|
||||
toggleCardSelection(card) {
|
||||
card.classList.toggle('selected');
|
||||
this.updateSelectedCount();
|
||||
}
|
||||
|
||||
async copyAllLorasSyntax() {
|
||||
const selectedCards = document.querySelectorAll('.lora-card.selected');
|
||||
if (selectedCards.length === 0) {
|
||||
showToast('No LoRAs selected', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
const loraSyntaxes = [];
|
||||
selectedCards.forEach(card => {
|
||||
const usageTips = JSON.parse(card.dataset.usage_tips || '{}');
|
||||
const strength = usageTips.strength || 1;
|
||||
loraSyntaxes.push(`<lora:${card.dataset.file_name}:${strength}>`);
|
||||
});
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(loraSyntaxes.join(', '));
|
||||
showToast(`Copied ${selectedCards.length} LoRA syntaxes to clipboard`, 'success');
|
||||
} catch (err) {
|
||||
console.error('Copy failed:', err);
|
||||
showToast('Copy failed', 'error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create a singleton instance
|
||||
export const bulkManager = new BulkManager();
|
||||
@@ -5,11 +5,13 @@ import { modalManager } from './ModalManager.js';
|
||||
class MoveManager {
|
||||
constructor() {
|
||||
this.currentFilePath = null;
|
||||
this.bulkFilePaths = null;
|
||||
this.modal = document.getElementById('moveModal');
|
||||
this.loraRootSelect = document.getElementById('moveLoraRoot');
|
||||
this.folderBrowser = document.getElementById('moveFolderBrowser');
|
||||
this.newFolderInput = document.getElementById('moveNewFolder');
|
||||
this.pathDisplay = document.getElementById('moveTargetPathDisplay');
|
||||
this.modalTitle = document.getElementById('moveModalTitle');
|
||||
|
||||
this.initializeEventListeners();
|
||||
}
|
||||
@@ -43,7 +45,24 @@ class MoveManager {
|
||||
}
|
||||
|
||||
async showMoveModal(filePath) {
|
||||
this.currentFilePath = filePath;
|
||||
// Reset state
|
||||
this.currentFilePath = null;
|
||||
this.bulkFilePaths = null;
|
||||
|
||||
// Handle bulk mode
|
||||
if (filePath === 'bulk') {
|
||||
const selectedPaths = Array.from(state.selectedLoras);
|
||||
if (selectedPaths.length === 0) {
|
||||
showToast('No LoRAs selected', 'warning');
|
||||
return;
|
||||
}
|
||||
this.bulkFilePaths = selectedPaths;
|
||||
this.modalTitle.textContent = `Move ${selectedPaths.length} LoRAs`;
|
||||
} else {
|
||||
// Single file mode
|
||||
this.currentFilePath = filePath;
|
||||
this.modalTitle.textContent = "Move Model";
|
||||
}
|
||||
|
||||
// 清除之前的选择
|
||||
this.folderBrowser.querySelectorAll('.folder-item').forEach(item => {
|
||||
@@ -105,36 +124,81 @@ class MoveManager {
|
||||
targetPath = `${targetPath}/${newFolder}`;
|
||||
}
|
||||
|
||||
try {
|
||||
if (this.bulkFilePaths) {
|
||||
// Bulk move mode
|
||||
await this.moveBulkModels(this.bulkFilePaths, targetPath);
|
||||
} else {
|
||||
// Single move mode
|
||||
await this.moveSingleModel(this.currentFilePath, targetPath);
|
||||
}
|
||||
|
||||
modalManager.closeModal('moveModal');
|
||||
await resetAndReload(true);
|
||||
|
||||
// If we were in bulk mode, exit it after successful move
|
||||
if (this.bulkFilePaths && state.bulkMode) {
|
||||
toggleBulkMode();
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error moving model(s):', error);
|
||||
showToast('Failed to move model(s): ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async moveSingleModel(filePath, targetPath) {
|
||||
// show toast if current path is same as target path
|
||||
if (this.currentFilePath.substring(0, this.currentFilePath.lastIndexOf('/')) === targetPath) {
|
||||
if (filePath.substring(0, filePath.lastIndexOf('/')) === targetPath) {
|
||||
showToast('Model is already in the selected folder', 'info');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/move_model', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
file_path: this.currentFilePath,
|
||||
target_path: targetPath
|
||||
})
|
||||
});
|
||||
const response = await fetch('/api/move_model', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
file_path: filePath,
|
||||
target_path: targetPath
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to move model');
|
||||
}
|
||||
|
||||
showToast('Model moved successfully', 'success');
|
||||
modalManager.closeModal('moveModal');
|
||||
await resetAndReload(true);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error moving model:', error);
|
||||
showToast('Failed to move model: ' + error.message, 'error');
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to move model');
|
||||
}
|
||||
|
||||
showToast('Model moved successfully', 'success');
|
||||
}
|
||||
|
||||
async moveBulkModels(filePaths, targetPath) {
|
||||
// Filter out models already in the target path
|
||||
const movedPaths = filePaths.filter(path => {
|
||||
return path.substring(0, path.lastIndexOf('/')) !== targetPath;
|
||||
});
|
||||
|
||||
if (movedPaths.length === 0) {
|
||||
showToast('All selected models are already in the target folder', 'info');
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await fetch('/api/move_models_bulk', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
file_paths: movedPaths,
|
||||
target_path: targetPath
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to move models');
|
||||
}
|
||||
|
||||
showToast(`Successfully moved ${movedPaths.length} models`, 'success');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user