mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-25 15:15:44 -03:00
Add interactive thumbnail strip for bulk LoRA selection
This commit is contained in:
283
static/css/components/bulk.css
Normal file
283
static/css/components/bulk.css
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
/* Bulk Operations Styles */
|
||||||
|
.bulk-operations-panel {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateY(100px) translateX(-50%);
|
||||||
|
background: var(--card-bg);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--border-radius-base);
|
||||||
|
padding: 12px 16px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
z-index: var(--z-overlay);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: 300px;
|
||||||
|
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulk-operations-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
gap: 20px; /* Increase space between count and buttons */
|
||||||
|
}
|
||||||
|
|
||||||
|
#selectedCount {
|
||||||
|
font-weight: 500;
|
||||||
|
background: var(--bg-color);
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-radius: var(--border-radius-xs);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
min-width: 80px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulk-operations-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulk-operations-actions button {
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-radius: var(--border-radius-xs);
|
||||||
|
background: var(--bg-color);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulk-operations-actions button:hover {
|
||||||
|
background: var(--lora-accent);
|
||||||
|
color: white;
|
||||||
|
border-color: var(--lora-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style for selected cards */
|
||||||
|
.lora-card.selected {
|
||||||
|
box-shadow: 0 0 0 2px var(--lora-accent);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lora-card.selected::after {
|
||||||
|
content: "✓";
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
background: var(--lora-accent);
|
||||||
|
color: white;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 14px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update bulk operations button to match others when active */
|
||||||
|
#bulkOperationsBtn.active {
|
||||||
|
background: var(--lora-accent);
|
||||||
|
color: white;
|
||||||
|
border-color: var(--lora-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.bulk-operations-panel {
|
||||||
|
width: calc(100% - 40px);
|
||||||
|
left: 20px;
|
||||||
|
transform: none;
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulk-operations-actions {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulk-operations-panel.visible {
|
||||||
|
transform: translateY(0) translateX(-50%);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Thumbnail Strip Styles */
|
||||||
|
.selected-thumbnails-strip {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 80px; /* Position above the bulk operations panel */
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%) translateY(20px);
|
||||||
|
background: var(--card-bg);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--border-radius-base);
|
||||||
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
||||||
|
z-index: calc(var(--z-overlay) - 1); /* Just below the bulk panel z-index */
|
||||||
|
padding: 16px;
|
||||||
|
max-width: 80%;
|
||||||
|
width: auto;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
opacity: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-thumbnails-strip.visible {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(-50%) translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumbnails-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding-bottom: 8px; /* Space for scrollbar */
|
||||||
|
max-width: 100%;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-thumbnail {
|
||||||
|
position: relative;
|
||||||
|
width: 80px;
|
||||||
|
min-width: 80px; /* Prevent shrinking */
|
||||||
|
border-radius: var(--border-radius-xs);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
overflow: hidden;
|
||||||
|
cursor: pointer;
|
||||||
|
background: var(--bg-color);
|
||||||
|
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-thumbnail:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-thumbnail img,
|
||||||
|
.selected-thumbnail video {
|
||||||
|
width: 100%;
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
object-fit: cover;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumbnail-name {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
color: white;
|
||||||
|
font-size: 10px;
|
||||||
|
padding: 3px 5px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumbnail-remove {
|
||||||
|
position: absolute;
|
||||||
|
top: 3px;
|
||||||
|
right: 3px;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 10px;
|
||||||
|
opacity: 0.7;
|
||||||
|
transition: opacity 0.2s ease, background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumbnail-remove:hover {
|
||||||
|
opacity: 1;
|
||||||
|
background: var(--lora-error);
|
||||||
|
}
|
||||||
|
|
||||||
|
.strip-close-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 5px;
|
||||||
|
right: 5px;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--text-color);
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
opacity: 0.7;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.strip-close-btn:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style the selectedCount to indicate it's clickable */
|
||||||
|
.selectable-count {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selectable-count:hover {
|
||||||
|
background: var(--lora-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-caret {
|
||||||
|
font-size: 12px;
|
||||||
|
visibility: hidden; /* Will be shown via JS when items are selected */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scrollbar styling for the thumbnails container */
|
||||||
|
.thumbnails-container::-webkit-scrollbar {
|
||||||
|
height: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumbnails-container::-webkit-scrollbar-track {
|
||||||
|
background: var(--bg-color);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumbnails-container::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--border-color);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumbnails-container::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--lora-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile optimizations */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.selected-thumbnails-strip {
|
||||||
|
width: calc(100% - 40px);
|
||||||
|
max-width: none;
|
||||||
|
left: 20px;
|
||||||
|
transform: translateY(20px);
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-thumbnails-strip.visible {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-thumbnail {
|
||||||
|
width: 70px;
|
||||||
|
min-width: 70px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -387,91 +387,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Bulk Operations Styles */
|
|
||||||
.bulk-operations-panel {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 20px;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateY(100px) translateX(-50%);
|
|
||||||
background: var(--card-bg);
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
border-radius: var(--border-radius-base);
|
|
||||||
padding: 12px 16px;
|
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
||||||
z-index: var(--z-overlay);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
min-width: 300px;
|
|
||||||
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bulk-operations-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
gap: 20px; /* Increase space between count and buttons */
|
|
||||||
}
|
|
||||||
|
|
||||||
#selectedCount {
|
|
||||||
font-weight: 500;
|
|
||||||
background: var(--bg-color);
|
|
||||||
padding: 6px 12px;
|
|
||||||
border-radius: var(--border-radius-xs);
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
min-width: 80px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bulk-operations-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bulk-operations-actions button {
|
|
||||||
padding: 6px 12px;
|
|
||||||
border-radius: var(--border-radius-xs);
|
|
||||||
background: var(--bg-color);
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
color: var(--text-color);
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 14px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bulk-operations-actions button:hover {
|
|
||||||
background: var(--lora-accent);
|
|
||||||
color: white;
|
|
||||||
border-color: var(--lora-accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Style for selected cards */
|
|
||||||
.lora-card.selected {
|
|
||||||
box-shadow: 0 0 0 2px var(--lora-accent);
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lora-card.selected::after {
|
|
||||||
content: "✓";
|
|
||||||
position: absolute;
|
|
||||||
top: 10px;
|
|
||||||
right: 10px;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
background: var(--lora-accent);
|
|
||||||
color: white;
|
|
||||||
border-radius: 50%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 14px;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Standardize button widths in controls */
|
/* Standardize button widths in controls */
|
||||||
.control-group button {
|
.control-group button {
|
||||||
min-width: 100px;
|
min-width: 100px;
|
||||||
@@ -480,43 +395,3 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Update bulk operations button to match others when active */
|
|
||||||
#bulkOperationsBtn.active {
|
|
||||||
background: var(--lora-accent);
|
|
||||||
color: white;
|
|
||||||
border-color: var(--lora-accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.bulk-operations-panel {
|
|
||||||
width: calc(100% - 40px);
|
|
||||||
left: 20px;
|
|
||||||
transform: none;
|
|
||||||
border-radius: var(--border-radius-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.bulk-operations-actions {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.bulk-operations-panel.visible {
|
|
||||||
transform: translateY(0) translateX(-50%);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Remove the page overlay */
|
|
||||||
.bulk-mode-overlay {
|
|
||||||
display: none; /* Hide the overlay completely */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Remove card scaling in bulk mode but keep the transition for other properties */
|
|
||||||
.lora-card {
|
|
||||||
transition: box-shadow 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Remove the transform scale from bulk mode cards */
|
|
||||||
.bulk-mode .lora-card {
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
@import 'components/lora-modal.css';
|
@import 'components/lora-modal.css';
|
||||||
@import 'components/support-modal.css';
|
@import 'components/support-modal.css';
|
||||||
@import 'components/search-filter.css';
|
@import 'components/search-filter.css';
|
||||||
|
@import 'components/bulk.css';
|
||||||
|
|
||||||
.initialization-notice {
|
.initialization-notice {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export class BulkManager {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.bulkBtn = document.getElementById('bulkOperationsBtn');
|
this.bulkBtn = document.getElementById('bulkOperationsBtn');
|
||||||
this.bulkPanel = document.getElementById('bulkOperationsPanel');
|
this.bulkPanel = document.getElementById('bulkOperationsPanel');
|
||||||
|
this.isStripVisible = false; // Track strip visibility state
|
||||||
|
|
||||||
// Initialize selected loras set in state if not already there
|
// Initialize selected loras set in state if not already there
|
||||||
if (!state.selectedLoras) {
|
if (!state.selectedLoras) {
|
||||||
@@ -21,6 +22,12 @@ export class BulkManager {
|
|||||||
initialize() {
|
initialize() {
|
||||||
// Add event listeners if needed
|
// Add event listeners if needed
|
||||||
// (Already handled via onclick attributes in HTML, but could be moved here)
|
// (Already handled via onclick attributes in HTML, but could be moved here)
|
||||||
|
|
||||||
|
// Add event listeners for the selected count to toggle thumbnail strip
|
||||||
|
const selectedCount = document.getElementById('selectedCount');
|
||||||
|
if (selectedCount) {
|
||||||
|
selectedCount.addEventListener('click', () => this.toggleThumbnailStrip());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleBulkMode() {
|
toggleBulkMode() {
|
||||||
@@ -44,6 +51,9 @@ export class BulkManager {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.bulkPanel.classList.add('hidden');
|
this.bulkPanel.classList.add('hidden');
|
||||||
}, 400); // Match this with the transition duration in CSS
|
}, 400); // Match this with the transition duration in CSS
|
||||||
|
|
||||||
|
// Hide thumbnail strip if it's visible
|
||||||
|
this.hideThumbnailStrip();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update all cards
|
// Update all cards
|
||||||
@@ -61,13 +71,29 @@ export class BulkManager {
|
|||||||
});
|
});
|
||||||
state.selectedLoras.clear();
|
state.selectedLoras.clear();
|
||||||
this.updateSelectedCount();
|
this.updateSelectedCount();
|
||||||
|
|
||||||
|
// Hide thumbnail strip if it's visible
|
||||||
|
this.hideThumbnailStrip();
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSelectedCount() {
|
updateSelectedCount() {
|
||||||
const countElement = document.getElementById('selectedCount');
|
const countElement = document.getElementById('selectedCount');
|
||||||
|
|
||||||
if (countElement) {
|
if (countElement) {
|
||||||
|
// Set text content without the icon
|
||||||
countElement.textContent = `${state.selectedLoras.size} selected `;
|
countElement.textContent = `${state.selectedLoras.size} selected `;
|
||||||
|
|
||||||
|
// Re-add the caret icon with proper direction
|
||||||
|
const caretIcon = document.createElement('i');
|
||||||
|
// Use down arrow if strip is visible, up arrow if not
|
||||||
|
caretIcon.className = `fas fa-caret-${this.isStripVisible ? 'down' : 'up'} dropdown-caret`;
|
||||||
|
caretIcon.style.visibility = state.selectedLoras.size > 0 ? 'visible' : 'hidden';
|
||||||
|
countElement.appendChild(caretIcon);
|
||||||
|
|
||||||
|
// If there are no selections, hide the thumbnail strip
|
||||||
|
if (state.selectedLoras.size === 0) {
|
||||||
|
this.hideThumbnailStrip();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,11 +110,31 @@ export class BulkManager {
|
|||||||
// Cache the metadata for this lora
|
// Cache the metadata for this lora
|
||||||
state.loraMetadataCache.set(filepath, {
|
state.loraMetadataCache.set(filepath, {
|
||||||
fileName: card.dataset.file_name,
|
fileName: card.dataset.file_name,
|
||||||
usageTips: card.dataset.usage_tips
|
usageTips: card.dataset.usage_tips,
|
||||||
|
previewUrl: this.getCardPreviewUrl(card),
|
||||||
|
isVideo: this.isCardPreviewVideo(card),
|
||||||
|
modelName: card.dataset.name
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateSelectedCount();
|
this.updateSelectedCount();
|
||||||
|
|
||||||
|
// Update thumbnail strip if it's visible
|
||||||
|
if (this.isStripVisible) {
|
||||||
|
this.updateThumbnailStrip();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper method to get preview URL from a card
|
||||||
|
getCardPreviewUrl(card) {
|
||||||
|
const img = card.querySelector('img');
|
||||||
|
const video = card.querySelector('video source');
|
||||||
|
return img ? img.src : (video ? video.src : '/loras_static/images/no-preview.png');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper method to check if preview is a video
|
||||||
|
isCardPreviewVideo(card) {
|
||||||
|
return card.querySelector('video') !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply selection state to cards after they are refreshed
|
// Apply selection state to cards after they are refreshed
|
||||||
@@ -103,7 +149,10 @@ export class BulkManager {
|
|||||||
// Update the cache with latest data
|
// Update the cache with latest data
|
||||||
state.loraMetadataCache.set(filepath, {
|
state.loraMetadataCache.set(filepath, {
|
||||||
fileName: card.dataset.file_name,
|
fileName: card.dataset.file_name,
|
||||||
usageTips: card.dataset.usage_tips
|
usageTips: card.dataset.usage_tips,
|
||||||
|
previewUrl: this.getCardPreviewUrl(card),
|
||||||
|
isVideo: this.isCardPreviewVideo(card),
|
||||||
|
modelName: card.dataset.name
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
card.classList.remove('selected');
|
card.classList.remove('selected');
|
||||||
@@ -155,6 +204,131 @@ export class BulkManager {
|
|||||||
showToast('Copy failed', 'error');
|
showToast('Copy failed', 'error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create and show the thumbnail strip of selected LoRAs
|
||||||
|
toggleThumbnailStrip() {
|
||||||
|
// If no items are selected, do nothing
|
||||||
|
if (state.selectedLoras.size === 0) return;
|
||||||
|
|
||||||
|
const existing = document.querySelector('.selected-thumbnails-strip');
|
||||||
|
if (existing) {
|
||||||
|
this.hideThumbnailStrip();
|
||||||
|
} else {
|
||||||
|
this.showThumbnailStrip();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showThumbnailStrip() {
|
||||||
|
// Create the thumbnail strip container
|
||||||
|
const strip = document.createElement('div');
|
||||||
|
strip.className = 'selected-thumbnails-strip';
|
||||||
|
|
||||||
|
// Create a container for the thumbnails (for scrolling)
|
||||||
|
const thumbnailContainer = document.createElement('div');
|
||||||
|
thumbnailContainer.className = 'thumbnails-container';
|
||||||
|
strip.appendChild(thumbnailContainer);
|
||||||
|
|
||||||
|
// Position the strip above the bulk operations panel
|
||||||
|
this.bulkPanel.parentNode.insertBefore(strip, this.bulkPanel);
|
||||||
|
|
||||||
|
// Populate the thumbnails
|
||||||
|
this.updateThumbnailStrip();
|
||||||
|
|
||||||
|
// Update strip visibility state and caret direction
|
||||||
|
this.isStripVisible = true;
|
||||||
|
this.updateSelectedCount(); // Update caret
|
||||||
|
|
||||||
|
// Add animation class after a short delay to trigger transition
|
||||||
|
setTimeout(() => strip.classList.add('visible'), 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
hideThumbnailStrip() {
|
||||||
|
const strip = document.querySelector('.selected-thumbnails-strip');
|
||||||
|
if (strip) {
|
||||||
|
strip.classList.remove('visible');
|
||||||
|
|
||||||
|
// Update strip visibility state and caret direction
|
||||||
|
this.isStripVisible = false;
|
||||||
|
this.updateSelectedCount(); // Update caret
|
||||||
|
|
||||||
|
// Wait for animation to complete before removing
|
||||||
|
setTimeout(() => {
|
||||||
|
if (strip.parentNode) {
|
||||||
|
strip.parentNode.removeChild(strip);
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateThumbnailStrip() {
|
||||||
|
const container = document.querySelector('.thumbnails-container');
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
// Clear existing thumbnails
|
||||||
|
container.innerHTML = '';
|
||||||
|
|
||||||
|
// Add a thumbnail for each selected LoRA
|
||||||
|
for (const filepath of state.selectedLoras) {
|
||||||
|
const metadata = state.loraMetadataCache.get(filepath);
|
||||||
|
if (!metadata) continue;
|
||||||
|
|
||||||
|
const thumbnail = document.createElement('div');
|
||||||
|
thumbnail.className = 'selected-thumbnail';
|
||||||
|
thumbnail.dataset.filepath = filepath;
|
||||||
|
|
||||||
|
// Create the visual element (image or video)
|
||||||
|
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>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add click handler for deselection
|
||||||
|
thumbnail.addEventListener('click', (e) => {
|
||||||
|
if (!e.target.closest('.thumbnail-remove')) {
|
||||||
|
this.deselectItem(filepath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add click handler for the remove button
|
||||||
|
thumbnail.querySelector('.thumbnail-remove').addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
this.deselectItem(filepath);
|
||||||
|
});
|
||||||
|
|
||||||
|
container.appendChild(thumbnail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deselectItem(filepath) {
|
||||||
|
// Find and deselect the corresponding card if it's in the DOM
|
||||||
|
const card = document.querySelector(`.lora-card[data-filepath="${filepath}"]`);
|
||||||
|
if (card) {
|
||||||
|
card.classList.remove('selected');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove from the selection set
|
||||||
|
state.selectedLoras.delete(filepath);
|
||||||
|
|
||||||
|
// Update UI
|
||||||
|
this.updateSelectedCount();
|
||||||
|
this.updateThumbnailStrip();
|
||||||
|
|
||||||
|
// Hide the strip if no more selections
|
||||||
|
if (state.selectedLoras.size === 0) {
|
||||||
|
this.hideThumbnailStrip();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a singleton instance
|
// Create a singleton instance
|
||||||
|
|||||||
@@ -72,7 +72,9 @@
|
|||||||
<!-- Add bulk operations panel (initially hidden) -->
|
<!-- Add bulk operations panel (initially hidden) -->
|
||||||
<div id="bulkOperationsPanel" class="bulk-operations-panel hidden">
|
<div id="bulkOperationsPanel" class="bulk-operations-panel hidden">
|
||||||
<div class="bulk-operations-header">
|
<div class="bulk-operations-header">
|
||||||
<span id="selectedCount">0 selected</span>
|
<span id="selectedCount" class="selectable-count" title="Click to view selected items">
|
||||||
|
0 selected <i class="fas fa-caret-down dropdown-caret"></i>
|
||||||
|
</span>
|
||||||
<div class="bulk-operations-actions">
|
<div class="bulk-operations-actions">
|
||||||
<button onclick="bulkManager.copyAllLorasSyntax()" title="Copy all selected LoRAs syntax">
|
<button onclick="bulkManager.copyAllLorasSyntax()" title="Copy all selected LoRAs syntax">
|
||||||
<i class="fas fa-copy"></i> Copy All
|
<i class="fas fa-copy"></i> Copy All
|
||||||
|
|||||||
Reference in New Issue
Block a user