mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-22 05:32:12 -03:00
feat(bulk): implement bulk context menu for model operations and remove bulk operations panel
This commit is contained in:
@@ -176,11 +176,6 @@
|
||||
background: linear-gradient(45deg, #4a90e2, #357abd);
|
||||
}
|
||||
|
||||
/* Remove old node-color-indicator styles */
|
||||
.node-color-indicator {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.send-all-item {
|
||||
border-top: 1px solid var(--border-color);
|
||||
font-weight: 500;
|
||||
@@ -217,4 +212,22 @@
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Bulk Context Menu Header */
|
||||
.bulk-context-header {
|
||||
padding: 10px 12px;
|
||||
background: var(--lora-accent);
|
||||
color: var(--lora-text);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
border-radius: var(--border-radius-xs) var(--border-radius-xs) 0 0;
|
||||
}
|
||||
|
||||
.bulk-context-header i {
|
||||
width: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
123
static/js/components/ContextMenu/BulkContextMenu.js
Normal file
123
static/js/components/ContextMenu/BulkContextMenu.js
Normal file
@@ -0,0 +1,123 @@
|
||||
import { BaseContextMenu } from './BaseContextMenu.js';
|
||||
import { state } from '../../state/index.js';
|
||||
import { bulkManager } from '../../managers/BulkManager.js';
|
||||
import { translate, updateElementText } from '../../utils/i18nHelpers.js';
|
||||
import { MODEL_TYPES } from '../../api/apiConfig.js';
|
||||
|
||||
export class BulkContextMenu extends BaseContextMenu {
|
||||
constructor() {
|
||||
super('bulkContextMenu', '.model-card.selected');
|
||||
this.setupBulkMenuItems();
|
||||
}
|
||||
|
||||
init() {
|
||||
// Override parent init to handle bulk-specific context menu logic
|
||||
document.addEventListener('click', () => this.hideMenu());
|
||||
|
||||
document.addEventListener('contextmenu', (e) => {
|
||||
const card = e.target.closest('.model-card');
|
||||
if (!card || !state.bulkMode) {
|
||||
this.hideMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
// Show bulk menu only if right-clicking on a selected card
|
||||
if (card.classList.contains('selected')) {
|
||||
e.preventDefault();
|
||||
this.showMenu(e.clientX, e.clientY, card);
|
||||
} else {
|
||||
this.hideMenu();
|
||||
}
|
||||
});
|
||||
|
||||
// Handle menu item clicks
|
||||
this.menu.addEventListener('click', (e) => {
|
||||
const menuItem = e.target.closest('.context-menu-item');
|
||||
if (!menuItem || !this.currentCard) return;
|
||||
|
||||
const action = menuItem.dataset.action;
|
||||
if (!action) return;
|
||||
|
||||
this.handleMenuAction(action, menuItem);
|
||||
this.hideMenu();
|
||||
});
|
||||
}
|
||||
|
||||
setupBulkMenuItems() {
|
||||
if (!this.menu) return;
|
||||
|
||||
// Update menu items visibility based on current model type
|
||||
this.updateMenuItemsForModelType();
|
||||
|
||||
// Update selected count in header
|
||||
this.updateSelectedCountHeader();
|
||||
}
|
||||
|
||||
updateMenuItemsForModelType() {
|
||||
const currentModelType = state.currentPageType;
|
||||
const config = bulkManager.actionConfig[currentModelType];
|
||||
|
||||
if (!config) return;
|
||||
|
||||
// Update button visibility based on model type
|
||||
const sendToWorkflowItem = this.menu.querySelector('[data-action="send-to-workflow"]');
|
||||
const copyAllItem = this.menu.querySelector('[data-action="copy-all"]');
|
||||
const refreshAllItem = this.menu.querySelector('[data-action="refresh-all"]');
|
||||
const moveAllItem = this.menu.querySelector('[data-action="move-all"]');
|
||||
const deleteAllItem = this.menu.querySelector('[data-action="delete-all"]');
|
||||
|
||||
if (sendToWorkflowItem) {
|
||||
sendToWorkflowItem.style.display = config.sendToWorkflow ? 'flex' : 'none';
|
||||
}
|
||||
if (copyAllItem) {
|
||||
copyAllItem.style.display = config.copyAll ? 'flex' : 'none';
|
||||
}
|
||||
if (refreshAllItem) {
|
||||
refreshAllItem.style.display = config.refreshAll ? 'flex' : 'none';
|
||||
}
|
||||
if (moveAllItem) {
|
||||
moveAllItem.style.display = config.moveAll ? 'flex' : 'none';
|
||||
}
|
||||
if (deleteAllItem) {
|
||||
deleteAllItem.style.display = config.deleteAll ? 'flex' : 'none';
|
||||
}
|
||||
}
|
||||
|
||||
updateSelectedCountHeader() {
|
||||
const headerElement = this.menu.querySelector('.bulk-context-header');
|
||||
if (headerElement) {
|
||||
updateElementText(headerElement, 'loras.bulkOperations.selected', { count: state.selectedModels.size });
|
||||
}
|
||||
}
|
||||
|
||||
showMenu(x, y, card) {
|
||||
this.updateMenuItemsForModelType();
|
||||
this.updateSelectedCountHeader();
|
||||
super.showMenu(x, y, card);
|
||||
}
|
||||
|
||||
handleMenuAction(action, menuItem) {
|
||||
switch (action) {
|
||||
case 'send-to-workflow':
|
||||
bulkManager.sendAllModelsToWorkflow();
|
||||
break;
|
||||
case 'copy-all':
|
||||
bulkManager.copyAllModelsSyntax();
|
||||
break;
|
||||
case 'refresh-all':
|
||||
bulkManager.refreshAllMetadata();
|
||||
break;
|
||||
case 'move-all':
|
||||
window.moveManager.showMoveModal('bulk');
|
||||
break;
|
||||
case 'delete-all':
|
||||
bulkManager.showBulkDeleteModal();
|
||||
break;
|
||||
case 'clear':
|
||||
bulkManager.clearSelection();
|
||||
break;
|
||||
default:
|
||||
console.warn(`Unknown bulk action: ${action}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import { initializeInfiniteScroll } from './utils/infiniteScroll.js';
|
||||
import { migrateStorageItems } from './utils/storageHelpers.js';
|
||||
import { i18n } from './i18n/index.js';
|
||||
import { onboardingManager } from './managers/OnboardingManager.js';
|
||||
import { BulkContextMenu } from './components/ContextMenu/BulkContextMenu.js';
|
||||
|
||||
// Core application class
|
||||
export class AppCore {
|
||||
@@ -55,6 +56,10 @@ export class AppCore {
|
||||
// Initialize the bulk manager
|
||||
bulkManager.initialize();
|
||||
|
||||
// Initialize bulk context menu
|
||||
const bulkContextMenu = new BulkContextMenu();
|
||||
bulkManager.setBulkContextMenu(bulkContextMenu);
|
||||
|
||||
// Initialize the example images manager
|
||||
exampleImagesManager.initialize();
|
||||
// Initialize the help manager
|
||||
|
||||
@@ -2,18 +2,14 @@ import { state, getCurrentPageState } from '../state/index.js';
|
||||
import { showToast, copyToClipboard, sendLoraToWorkflow } from '../utils/uiHelpers.js';
|
||||
import { updateCardsForBulkMode } from '../components/shared/ModelCard.js';
|
||||
import { modalManager } from './ModalManager.js';
|
||||
import { moveManager } from './MoveManager.js';
|
||||
import { getModelApiClient } from '../api/modelApiFactory.js';
|
||||
import { MODEL_TYPES, MODEL_CONFIG } from '../api/apiConfig.js';
|
||||
import { updateElementText } from '../utils/i18nHelpers.js';
|
||||
|
||||
export class BulkManager {
|
||||
constructor() {
|
||||
this.bulkBtn = document.getElementById('bulkOperationsBtn');
|
||||
this.bulkPanel = document.getElementById('bulkOperationsPanel');
|
||||
this.isStripVisible = false;
|
||||
|
||||
this.stripMaxThumbnails = 50;
|
||||
// Remove bulk panel references since we're using context menu now
|
||||
this.bulkContextMenu = null; // Will be set by core initialization
|
||||
|
||||
// Model type specific action configurations
|
||||
this.actionConfig = {
|
||||
@@ -46,41 +42,13 @@ export class BulkManager {
|
||||
this.setupGlobalKeyboardListeners();
|
||||
}
|
||||
|
||||
setBulkContextMenu(bulkContextMenu) {
|
||||
this.bulkContextMenu = bulkContextMenu;
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
// Bulk operations button listeners
|
||||
const sendToWorkflowBtn = this.bulkPanel?.querySelector('[data-action="send-to-workflow"]');
|
||||
const copyAllBtn = this.bulkPanel?.querySelector('[data-action="copy-all"]');
|
||||
const refreshAllBtn = this.bulkPanel?.querySelector('[data-action="refresh-all"]');
|
||||
const moveAllBtn = this.bulkPanel?.querySelector('[data-action="move-all"]');
|
||||
const deleteAllBtn = this.bulkPanel?.querySelector('[data-action="delete-all"]');
|
||||
const clearBtn = this.bulkPanel?.querySelector('[data-action="clear"]');
|
||||
|
||||
if (sendToWorkflowBtn) {
|
||||
sendToWorkflowBtn.addEventListener('click', () => this.sendAllModelsToWorkflow());
|
||||
}
|
||||
if (copyAllBtn) {
|
||||
copyAllBtn.addEventListener('click', () => this.copyAllModelsSyntax());
|
||||
}
|
||||
if (refreshAllBtn) {
|
||||
refreshAllBtn.addEventListener('click', () => this.refreshAllMetadata());
|
||||
}
|
||||
if (moveAllBtn) {
|
||||
moveAllBtn.addEventListener('click', () => {
|
||||
moveManager.showMoveModal('bulk');
|
||||
});
|
||||
}
|
||||
if (deleteAllBtn) {
|
||||
deleteAllBtn.addEventListener('click', () => this.showBulkDeleteModal());
|
||||
}
|
||||
if (clearBtn) {
|
||||
clearBtn.addEventListener('click', () => this.clearSelection());
|
||||
}
|
||||
|
||||
// Selected count click listener
|
||||
const selectedCount = document.getElementById('selectedCount');
|
||||
if (selectedCount) {
|
||||
selectedCount.addEventListener('click', () => this.toggleThumbnailStrip());
|
||||
}
|
||||
// Only setup bulk mode toggle button listener now
|
||||
// Context menu actions are handled by BulkContextMenu
|
||||
}
|
||||
|
||||
setupGlobalKeyboardListeners() {
|
||||
@@ -115,60 +83,15 @@ export class BulkManager {
|
||||
|
||||
this.bulkBtn.classList.toggle('active', state.bulkMode);
|
||||
|
||||
if (state.bulkMode) {
|
||||
this.bulkPanel.classList.remove('hidden');
|
||||
this.updateActionButtonsVisibility();
|
||||
setTimeout(() => {
|
||||
this.bulkPanel.classList.add('visible');
|
||||
}, 10);
|
||||
} else {
|
||||
this.bulkPanel.classList.remove('visible');
|
||||
setTimeout(() => {
|
||||
this.bulkPanel.classList.add('hidden');
|
||||
}, 400);
|
||||
this.hideThumbnailStrip();
|
||||
}
|
||||
|
||||
updateCardsForBulkMode(state.bulkMode);
|
||||
|
||||
if (!state.bulkMode) {
|
||||
this.clearSelection();
|
||||
|
||||
// TODO:
|
||||
document.querySelectorAll('.model-card').forEach(card => {
|
||||
const actions = card.querySelectorAll('.card-actions, .card-button');
|
||||
actions.forEach(action => action.style.display = 'flex');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
updateActionButtonsVisibility() {
|
||||
const currentModelType = state.currentPageType;
|
||||
const config = this.actionConfig[currentModelType];
|
||||
|
||||
if (!config) return;
|
||||
|
||||
// Update button visibility based on model type
|
||||
const sendToWorkflowBtn = this.bulkPanel?.querySelector('[data-action="send-to-workflow"]');
|
||||
const copyAllBtn = this.bulkPanel?.querySelector('[data-action="copy-all"]');
|
||||
const refreshAllBtn = this.bulkPanel?.querySelector('[data-action="refresh-all"]');
|
||||
const moveAllBtn = this.bulkPanel?.querySelector('[data-action="move-all"]');
|
||||
const deleteAllBtn = this.bulkPanel?.querySelector('[data-action="delete-all"]');
|
||||
|
||||
if (sendToWorkflowBtn) {
|
||||
sendToWorkflowBtn.style.display = config.sendToWorkflow ? 'block' : 'none';
|
||||
}
|
||||
if (copyAllBtn) {
|
||||
copyAllBtn.style.display = config.copyAll ? 'block' : 'none';
|
||||
}
|
||||
if (refreshAllBtn) {
|
||||
refreshAllBtn.style.display = config.refreshAll ? 'block' : 'none';
|
||||
}
|
||||
if (moveAllBtn) {
|
||||
moveAllBtn.style.display = config.moveAll ? 'block' : 'none';
|
||||
}
|
||||
if (deleteAllBtn) {
|
||||
deleteAllBtn.style.display = config.deleteAll ? 'block' : 'none';
|
||||
// Hide context menu when exiting bulk mode
|
||||
if (this.bulkContextMenu) {
|
||||
this.bulkContextMenu.hideMenu();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,27 +100,10 @@ export class BulkManager {
|
||||
card.classList.remove('selected');
|
||||
});
|
||||
state.selectedModels.clear();
|
||||
this.updateSelectedCount();
|
||||
this.hideThumbnailStrip();
|
||||
}
|
||||
|
||||
updateSelectedCount() {
|
||||
const countElement = document.getElementById('selectedCount');
|
||||
|
||||
if (countElement) {
|
||||
// Use i18nHelpers.js to update the count text
|
||||
updateElementText(countElement, 'loras.bulkOperations.selected', { count: state.selectedModels.size });
|
||||
|
||||
const existingCaret = countElement.querySelector('.dropdown-caret');
|
||||
if (existingCaret) {
|
||||
existingCaret.className = `fas fa-caret-${this.isStripVisible ? 'down' : 'up'} dropdown-caret`;
|
||||
existingCaret.style.visibility = state.selectedModels.size > 0 ? 'visible' : 'hidden';
|
||||
} else {
|
||||
const caretIcon = document.createElement('i');
|
||||
caretIcon.className = `fas fa-caret-${this.isStripVisible ? 'down' : 'up'} dropdown-caret`;
|
||||
caretIcon.style.visibility = state.selectedModels.size > 0 ? 'visible' : 'hidden';
|
||||
countElement.appendChild(caretIcon);
|
||||
}
|
||||
// Update context menu header if visible
|
||||
if (this.bulkContextMenu) {
|
||||
this.bulkContextMenu.updateSelectedCountHeader();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,10 +128,9 @@ export class BulkManager {
|
||||
});
|
||||
}
|
||||
|
||||
this.updateSelectedCount();
|
||||
|
||||
if (this.isStripVisible) {
|
||||
this.updateThumbnailStrip();
|
||||
// Update context menu header if visible
|
||||
if (this.bulkContextMenu) {
|
||||
this.bulkContextMenu.updateSelectedCountHeader();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,8 +182,6 @@ export class BulkManager {
|
||||
card.classList.remove('selected');
|
||||
}
|
||||
});
|
||||
|
||||
this.updateSelectedCount();
|
||||
}
|
||||
|
||||
async copyAllModelsSyntax() {
|
||||
@@ -413,115 +316,6 @@ export class BulkManager {
|
||||
showToast('toast.models.deleteFailedGeneral', {}, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
toggleThumbnailStrip() {
|
||||
if (state.selectedModels.size === 0) return;
|
||||
|
||||
const existing = document.querySelector('.selected-thumbnails-strip');
|
||||
if (existing) {
|
||||
this.hideThumbnailStrip();
|
||||
} else {
|
||||
this.showThumbnailStrip();
|
||||
}
|
||||
}
|
||||
|
||||
showThumbnailStrip() {
|
||||
const strip = document.createElement('div');
|
||||
strip.className = 'selected-thumbnails-strip';
|
||||
|
||||
const thumbnailContainer = document.createElement('div');
|
||||
thumbnailContainer.className = 'thumbnails-container';
|
||||
strip.appendChild(thumbnailContainer);
|
||||
|
||||
this.bulkPanel.parentNode.insertBefore(strip, this.bulkPanel);
|
||||
|
||||
this.updateThumbnailStrip();
|
||||
|
||||
this.isStripVisible = true;
|
||||
this.updateSelectedCount();
|
||||
|
||||
setTimeout(() => strip.classList.add('visible'), 10);
|
||||
}
|
||||
|
||||
hideThumbnailStrip() {
|
||||
const strip = document.querySelector('.selected-thumbnails-strip');
|
||||
if (strip && this.isStripVisible) {
|
||||
strip.classList.remove('visible');
|
||||
|
||||
this.isStripVisible = false;
|
||||
|
||||
const countElement = document.getElementById('selectedCount');
|
||||
if (countElement) {
|
||||
const caret = countElement.querySelector('.dropdown-caret');
|
||||
if (caret) {
|
||||
caret.className = 'fas fa-caret-up dropdown-caret';
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if (strip.parentNode) {
|
||||
strip.parentNode.removeChild(strip);
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
updateThumbnailStrip() {
|
||||
const container = document.querySelector('.thumbnails-container');
|
||||
if (!container) return;
|
||||
|
||||
container.innerHTML = '';
|
||||
|
||||
const selectedModels = Array.from(state.selectedModels);
|
||||
|
||||
if (selectedModels.length > this.stripMaxThumbnails) {
|
||||
const counter = document.createElement('div');
|
||||
counter.className = 'strip-counter';
|
||||
counter.textContent = `Showing ${this.stripMaxThumbnails} of ${selectedModels.length} selected`;
|
||||
container.appendChild(counter);
|
||||
}
|
||||
|
||||
const thumbnailsToShow = selectedModels.slice(0, this.stripMaxThumbnails);
|
||||
const metadataCache = this.getMetadataCache();
|
||||
|
||||
thumbnailsToShow.forEach(filepath => {
|
||||
const metadata = metadataCache.get(filepath);
|
||||
if (!metadata) return;
|
||||
|
||||
const thumbnail = document.createElement('div');
|
||||
thumbnail.className = 'selected-thumbnail';
|
||||
thumbnail.dataset.filepath = filepath;
|
||||
|
||||
if (metadata.isVideo) {
|
||||
thumbnail.innerHTML = `
|
||||
<video autoplay loop muted playsinline>
|
||||
<source src="${metadata.previewUrl}" type="video/mp4">
|
||||
</video>
|
||||
<span class="thumbnail-name" title="${metadata.modelName}">${metadata.modelName}</span>
|
||||
<button class="thumbnail-remove"><i class="fas fa-times"></i></button>
|
||||
`;
|
||||
} else {
|
||||
thumbnail.innerHTML = `
|
||||
<img src="${metadata.previewUrl}" alt="${metadata.modelName}">
|
||||
<span class="thumbnail-name" title="${metadata.modelName}">${metadata.modelName}</span>
|
||||
<button class="thumbnail-remove"><i class="fas fa-times"></i></button>
|
||||
`;
|
||||
}
|
||||
|
||||
thumbnail.addEventListener('click', (e) => {
|
||||
if (!e.target.closest('.thumbnail-remove')) {
|
||||
this.deselectItem(filepath);
|
||||
}
|
||||
});
|
||||
|
||||
thumbnail.querySelector('.thumbnail-remove').addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
this.deselectItem(filepath);
|
||||
});
|
||||
|
||||
container.appendChild(thumbnail);
|
||||
});
|
||||
}
|
||||
|
||||
deselectItem(filepath) {
|
||||
const card = document.querySelector(`.model-card[data-filepath="${filepath}"]`);
|
||||
@@ -530,13 +324,6 @@ export class BulkManager {
|
||||
}
|
||||
|
||||
state.selectedModels.delete(filepath);
|
||||
|
||||
this.updateSelectedCount();
|
||||
this.updateThumbnailStrip();
|
||||
|
||||
if (state.selectedModels.size === 0) {
|
||||
this.hideThumbnailStrip();
|
||||
}
|
||||
}
|
||||
|
||||
selectAllVisibleModels() {
|
||||
|
||||
@@ -44,6 +44,34 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="bulkContextMenu" class="context-menu">
|
||||
<div class="bulk-context-header">
|
||||
<i class="fas fa-th-large"></i>
|
||||
<span>{{ t('loras.bulkOperations.selected', {'count': 0}) }}</span>
|
||||
</div>
|
||||
<div class="context-menu-separator"></div>
|
||||
<div class="context-menu-item" data-action="send-to-workflow">
|
||||
<i class="fas fa-paper-plane"></i> <span>{{ t('loras.bulkOperations.sendToWorkflow') }}</span>
|
||||
</div>
|
||||
<div class="context-menu-item" data-action="copy-all">
|
||||
<i class="fas fa-copy"></i> <span>{{ t('loras.bulkOperations.copyAll') }}</span>
|
||||
</div>
|
||||
<div class="context-menu-item" data-action="refresh-all">
|
||||
<i class="fas fa-sync-alt"></i> <span>{{ t('loras.bulkOperations.refreshAll') }}</span>
|
||||
</div>
|
||||
<div class="context-menu-item" data-action="move-all">
|
||||
<i class="fas fa-folder-open"></i> <span>{{ t('loras.bulkOperations.moveAll') }}</span>
|
||||
</div>
|
||||
<div class="context-menu-separator"></div>
|
||||
<div class="context-menu-item delete-item" data-action="delete-all">
|
||||
<i class="fas fa-trash"></i> <span>{{ t('loras.bulkOperations.deleteAll') }}</span>
|
||||
</div>
|
||||
<div class="context-menu-separator"></div>
|
||||
<div class="context-menu-item" data-action="clear">
|
||||
<i class="fas fa-times"></i> <span>{{ t('loras.bulkOperations.clear') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="nsfwLevelSelector" class="nsfw-level-selector">
|
||||
<div class="nsfw-level-header">
|
||||
<h3>{{ t('modals.contentRating.title') }}</h3>
|
||||
|
||||
@@ -98,33 +98,4 @@
|
||||
<!-- Breadcrumbs will be populated by JavaScript -->
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add bulk operations panel (initially hidden) -->
|
||||
<div id="bulkOperationsPanel" class="bulk-operations-panel hidden">
|
||||
<div class="bulk-operations-header">
|
||||
<span id="selectedCount" class="selectable-count" title="{{ t('loras.bulkOperations.viewSelected') }}">
|
||||
0 {{ t('loras.bulkOperations.selectedSuffix') }} <i class="fas fa-caret-down dropdown-caret"></i>
|
||||
</span>
|
||||
<div class="bulk-operations-actions">
|
||||
<button data-action="send-to-workflow" title="{{ t('loras.bulkOperations.sendToWorkflow') }}">
|
||||
<i class="fas fa-arrow-right"></i> <span>{{ t('loras.bulkOperations.sendToWorkflow') }}</span>
|
||||
</button>
|
||||
<button data-action="copy-all" title="{{ t('loras.bulkOperations.copyAll') }}">
|
||||
<i class="fas fa-copy"></i> <span>{{ t('loras.bulkOperations.copyAll') }}</span>
|
||||
</button>
|
||||
<button data-action="refresh-all" title="{{ t('loras.bulkOperations.refreshAll') }}">
|
||||
<i class="fas fa-sync-alt"></i> <span>{{ t('loras.bulkOperations.refreshAll') }}</span>
|
||||
</button>
|
||||
<button data-action="move-all" title="{{ t('loras.bulkOperations.moveAll') }}">
|
||||
<i class="fas fa-folder-open"></i> <span>{{ t('loras.bulkOperations.moveAll') }}</span>
|
||||
</button>
|
||||
<button data-action="delete-all" title="{{ t('loras.bulkOperations.deleteAll') }}" class="danger-btn">
|
||||
<i class="fas fa-trash"></i> <span>{{ t('loras.bulkOperations.deleteAll') }}</span>
|
||||
</button>
|
||||
<button data-action="clear" title="{{ t('loras.bulkOperations.clear') }}">
|
||||
<i class="fas fa-times"></i> <span>{{ t('loras.bulkOperations.clear') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -16,8 +16,6 @@
|
||||
<div class="card-grid" id="modelGrid">
|
||||
<!-- Cards will be dynamically inserted here -->
|
||||
</div>
|
||||
|
||||
<!-- Bulk operations panel will be inserted here by JavaScript -->
|
||||
{% endblock %}
|
||||
|
||||
{% block overlay %}
|
||||
|
||||
Reference in New Issue
Block a user