mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-25 15:15:44 -03:00
feat: Refactor duplicates management with user preference for notification visibility and modular banner component, fixes #359
This commit is contained in:
@@ -3,7 +3,7 @@ import { showToast } from '../utils/uiHelpers.js';
|
|||||||
import { state, getCurrentPageState } from '../state/index.js';
|
import { state, getCurrentPageState } from '../state/index.js';
|
||||||
import { formatDate } from '../utils/formatters.js';
|
import { formatDate } from '../utils/formatters.js';
|
||||||
import { resetAndReload} from '../api/modelApiFactory.js';
|
import { resetAndReload} from '../api/modelApiFactory.js';
|
||||||
import { LoadingManager } from '../managers/LoadingManager.js';
|
import { getShowDuplicatesNotification, setShowDuplicatesNotification } from '../utils/storageHelpers.js';
|
||||||
|
|
||||||
export class ModelDuplicatesManager {
|
export class ModelDuplicatesManager {
|
||||||
constructor(pageManager, modelType = 'loras') {
|
constructor(pageManager, modelType = 'loras') {
|
||||||
@@ -12,13 +12,21 @@ export class ModelDuplicatesManager {
|
|||||||
this.inDuplicateMode = false;
|
this.inDuplicateMode = false;
|
||||||
this.selectedForDeletion = new Set();
|
this.selectedForDeletion = new Set();
|
||||||
this.modelType = modelType; // Use the provided modelType or default to 'loras'
|
this.modelType = modelType; // Use the provided modelType or default to 'loras'
|
||||||
|
|
||||||
// Verification tracking
|
// Verification tracking
|
||||||
this.verifiedGroups = new Set(); // Track which groups have been verified
|
this.verifiedGroups = new Set(); // Track which groups have been verified
|
||||||
this.mismatchedFiles = new Map(); // Map file paths to actual hashes for mismatched files
|
this.mismatchedFiles = new Map(); // Map file paths to actual hashes for mismatched files
|
||||||
|
|
||||||
// Loading manager for verification process
|
// Badge visibility preference
|
||||||
this.loadingManager = new LoadingManager();
|
this.showBadge = getShowDuplicatesNotification(); // Default to true (show badge)
|
||||||
|
|
||||||
|
// Event handler references for cleanup
|
||||||
|
this.badgeToggleHandler = null;
|
||||||
|
this.helpTooltipHandlers = {
|
||||||
|
mouseenter: null,
|
||||||
|
mouseleave: null,
|
||||||
|
click: null
|
||||||
|
};
|
||||||
|
|
||||||
// Bind methods
|
// Bind methods
|
||||||
this.renderModelCard = this.renderModelCard.bind(this);
|
this.renderModelCard = this.renderModelCard.bind(this);
|
||||||
@@ -66,7 +74,16 @@ export class ModelDuplicatesManager {
|
|||||||
const badge = document.getElementById('duplicatesBadge');
|
const badge = document.getElementById('duplicatesBadge');
|
||||||
if (!badge) return;
|
if (!badge) return;
|
||||||
|
|
||||||
|
// Check if badge should be hidden based on user preference
|
||||||
|
if (!this.showBadge && !this.inDuplicateMode) {
|
||||||
|
badge.style.display = 'none';
|
||||||
|
badge.textContent = '';
|
||||||
|
badge.classList.remove('pulse');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
|
badge.style.display = 'inline-flex';
|
||||||
badge.textContent = count;
|
badge.textContent = count;
|
||||||
badge.classList.add('pulse');
|
badge.classList.add('pulse');
|
||||||
} else {
|
} else {
|
||||||
@@ -136,6 +153,9 @@ export class ModelDuplicatesManager {
|
|||||||
|
|
||||||
// Setup help tooltip behavior
|
// Setup help tooltip behavior
|
||||||
this.setupHelpTooltip();
|
this.setupHelpTooltip();
|
||||||
|
|
||||||
|
// Setup badge toggle control
|
||||||
|
this.setupBadgeToggle();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable virtual scrolling if active
|
// Disable virtual scrolling if active
|
||||||
@@ -173,6 +193,9 @@ export class ModelDuplicatesManager {
|
|||||||
const pageState = getCurrentPageState();
|
const pageState = getCurrentPageState();
|
||||||
pageState.duplicatesMode = false;
|
pageState.duplicatesMode = false;
|
||||||
|
|
||||||
|
// Clean up event handlers before hiding banner
|
||||||
|
this.cleanupEventHandlers();
|
||||||
|
|
||||||
// Hide duplicates banner
|
// Hide duplicates banner
|
||||||
const banner = document.getElementById('duplicatesBanner');
|
const banner = document.getElementById('duplicatesBanner');
|
||||||
if (banner) {
|
if (banner) {
|
||||||
@@ -672,7 +695,11 @@ export class ModelDuplicatesManager {
|
|||||||
|
|
||||||
if (!helpIcon || !helpTooltip) return;
|
if (!helpIcon || !helpTooltip) return;
|
||||||
|
|
||||||
helpIcon.addEventListener('mouseenter', (e) => {
|
// Clean up existing handlers first
|
||||||
|
this.cleanupHelpTooltipHandlers();
|
||||||
|
|
||||||
|
// Create new handler functions and store references
|
||||||
|
this.helpTooltipHandlers.mouseenter = (e) => {
|
||||||
// Get the container's positioning context
|
// Get the container's positioning context
|
||||||
const bannerContent = helpIcon.closest('.banner-content');
|
const bannerContent = helpIcon.closest('.banner-content');
|
||||||
|
|
||||||
@@ -693,18 +720,22 @@ export class ModelDuplicatesManager {
|
|||||||
// Reposition relative to container if too close to right edge
|
// Reposition relative to container if too close to right edge
|
||||||
helpTooltip.style.left = `${bannerContent.offsetWidth - tooltipRect.width - 20}px`;
|
helpTooltip.style.left = `${bannerContent.offsetWidth - tooltipRect.width - 20}px`;
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
// Rest of the event listeners remain unchanged
|
this.helpTooltipHandlers.mouseleave = () => {
|
||||||
helpIcon.addEventListener('mouseleave', () => {
|
|
||||||
helpTooltip.style.display = 'none';
|
helpTooltip.style.display = 'none';
|
||||||
});
|
};
|
||||||
|
|
||||||
document.addEventListener('click', (e) => {
|
this.helpTooltipHandlers.click = (e) => {
|
||||||
if (!helpIcon.contains(e.target)) {
|
if (!helpIcon.contains(e.target)) {
|
||||||
helpTooltip.style.display = 'none';
|
helpTooltip.style.display = 'none';
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
|
// Add event listeners
|
||||||
|
helpIcon.addEventListener('mouseenter', this.helpTooltipHandlers.mouseenter);
|
||||||
|
helpIcon.addEventListener('mouseleave', this.helpTooltipHandlers.mouseleave);
|
||||||
|
document.addEventListener('click', this.helpTooltipHandlers.click);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle verify hashes button click
|
// Handle verify hashes button click
|
||||||
@@ -719,7 +750,7 @@ export class ModelDuplicatesManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Show loading state
|
// Show loading state
|
||||||
this.loadingManager.showSimpleLoading('Verifying hashes...');
|
state.loadingManager.showSimpleLoading('Verifying hashes...');
|
||||||
|
|
||||||
// Get file paths for all models in the group
|
// Get file paths for all models in the group
|
||||||
const filePaths = group.models.map(model => model.file_path);
|
const filePaths = group.models.map(model => model.file_path);
|
||||||
@@ -772,7 +803,87 @@ export class ModelDuplicatesManager {
|
|||||||
showToast('Failed to verify hashes: ' + error.message, 'error');
|
showToast('Failed to verify hashes: ' + error.message, 'error');
|
||||||
} finally {
|
} finally {
|
||||||
// Hide loading state
|
// Hide loading state
|
||||||
this.loadingManager.hide();
|
state.loadingManager.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add this new method for badge toggle setup
|
||||||
|
setupBadgeToggle() {
|
||||||
|
const toggleControl = document.getElementById('badgeToggleControl');
|
||||||
|
const toggleInput = document.getElementById('badgeToggleInput');
|
||||||
|
|
||||||
|
if (!toggleControl || !toggleInput) return;
|
||||||
|
|
||||||
|
// Clean up existing handler first
|
||||||
|
this.cleanupBadgeToggleHandler();
|
||||||
|
|
||||||
|
// Set initial state based on stored preference (default to true/checked)
|
||||||
|
toggleInput.checked = this.showBadge;
|
||||||
|
|
||||||
|
// Create and store the handler function
|
||||||
|
this.badgeToggleHandler = (e) => {
|
||||||
|
this.showBadge = e.target.checked;
|
||||||
|
setShowDuplicatesNotification(this.showBadge);
|
||||||
|
|
||||||
|
// Update badge visibility immediately if not in duplicate mode
|
||||||
|
if (!this.inDuplicateMode) {
|
||||||
|
this.updateDuplicatesBadge(this.duplicateGroups.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
showToast(
|
||||||
|
this.showBadge ? 'Duplicates notification will be shown' : 'Duplicates notification will be hidden',
|
||||||
|
'info'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add change event listener
|
||||||
|
toggleInput.addEventListener('change', this.badgeToggleHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up all event handlers
|
||||||
|
cleanupEventHandlers() {
|
||||||
|
this.cleanupBadgeToggleHandler();
|
||||||
|
this.cleanupHelpTooltipHandlers();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up badge toggle event handler
|
||||||
|
cleanupBadgeToggleHandler() {
|
||||||
|
if (this.badgeToggleHandler) {
|
||||||
|
const toggleInput = document.getElementById('badgeToggleInput');
|
||||||
|
if (toggleInput) {
|
||||||
|
toggleInput.removeEventListener('change', this.badgeToggleHandler);
|
||||||
|
}
|
||||||
|
this.badgeToggleHandler = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up help tooltip event handlers
|
||||||
|
cleanupHelpTooltipHandlers() {
|
||||||
|
const helpIcon = document.getElementById('duplicatesHelp');
|
||||||
|
|
||||||
|
if (helpIcon && this.helpTooltipHandlers.mouseenter) {
|
||||||
|
helpIcon.removeEventListener('mouseenter', this.helpTooltipHandlers.mouseenter);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (helpIcon && this.helpTooltipHandlers.mouseleave) {
|
||||||
|
helpIcon.removeEventListener('mouseleave', this.helpTooltipHandlers.mouseleave);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.helpTooltipHandlers.click) {
|
||||||
|
document.removeEventListener('click', this.helpTooltipHandlers.click);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset handler references
|
||||||
|
this.helpTooltipHandlers = {
|
||||||
|
mouseenter: null,
|
||||||
|
mouseleave: null,
|
||||||
|
click: null
|
||||||
|
};
|
||||||
|
|
||||||
|
// Hide tooltip if it's visible
|
||||||
|
const helpTooltip = document.getElementById('duplicatesHelpTooltip');
|
||||||
|
if (helpTooltip) {
|
||||||
|
helpTooltip.style.display = 'none';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -254,4 +254,20 @@ export function resetDismissedBanner(bannerId) {
|
|||||||
const dismissedBanners = getStorageItem('dismissed_banners', []);
|
const dismissedBanners = getStorageItem('dismissed_banners', []);
|
||||||
const updatedBanners = dismissedBanners.filter(id => id !== bannerId);
|
const updatedBanners = dismissedBanners.filter(id => id !== bannerId);
|
||||||
setStorageItem('dismissed_banners', updatedBanners);
|
setStorageItem('dismissed_banners', updatedBanners);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the show duplicates notification preference
|
||||||
|
* @returns {boolean} True if notification should be shown (default: true)
|
||||||
|
*/
|
||||||
|
export function getShowDuplicatesNotification() {
|
||||||
|
return getStorageItem('show_duplicates_notification', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the show duplicates notification preference
|
||||||
|
* @param {boolean} show - Whether to show the notification
|
||||||
|
*/
|
||||||
|
export function setShowDuplicatesNotification(show) {
|
||||||
|
setStorageItem('show_duplicates_notification', show);
|
||||||
}
|
}
|
||||||
@@ -30,27 +30,7 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% include 'components/controls.html' %}
|
{% include 'components/controls.html' %}
|
||||||
|
{% include 'components/duplicates_banner.html' %}
|
||||||
<!-- Duplicates banner (hidden by default) -->
|
|
||||||
<div id="duplicatesBanner" class="duplicates-banner" style="display: none;">
|
|
||||||
<div class="banner-content">
|
|
||||||
<i class="fas fa-exclamation-triangle"></i>
|
|
||||||
<span id="duplicatesCount">Found 0 duplicate groups</span>
|
|
||||||
<i class="fas fa-question-circle help-icon" id="duplicatesHelp" aria-label="Help information"></i>
|
|
||||||
<div class="banner-actions">
|
|
||||||
<button class="btn-delete-selected disabled" onclick="modelDuplicatesManager.deleteSelectedDuplicates()">
|
|
||||||
Delete Selected (<span id="duplicatesSelectedCount">0</span>)
|
|
||||||
</button>
|
|
||||||
<button class="btn-exit-mode" onclick="modelDuplicatesManager.exitDuplicateMode()">
|
|
||||||
<i class="fas fa-times"></i> Exit Mode
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="help-tooltip" id="duplicatesHelpTooltip">
|
|
||||||
<p>Identical hashes mean identical model files, even if they have different names or previews.</p>
|
|
||||||
<p>Keep only one version (preferably with better metadata/previews) and safely delete the others.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Checkpoint cards container -->
|
<!-- Checkpoint cards container -->
|
||||||
<div class="card-grid" id="modelGrid">
|
<div class="card-grid" id="modelGrid">
|
||||||
|
|||||||
27
templates/components/duplicates_banner.html
Normal file
27
templates/components/duplicates_banner.html
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<!-- Duplicates banner (hidden by default) -->
|
||||||
|
<div id="duplicatesBanner" class="duplicates-banner" style="display: none;">
|
||||||
|
<div class="banner-content">
|
||||||
|
<i class="fas fa-exclamation-triangle"></i>
|
||||||
|
<span id="duplicatesCount">Found 0 duplicate groups</span>
|
||||||
|
<i class="fas fa-question-circle help-icon" id="duplicatesHelp" aria-label="Help information"></i>
|
||||||
|
<div class="banner-actions">
|
||||||
|
<div class="setting-contro" id="badgeToggleControl">
|
||||||
|
<span>Show Duplicates Notification:</span>
|
||||||
|
<label class="toggle-switch">
|
||||||
|
<input type="checkbox" id="badgeToggleInput">
|
||||||
|
<span class="toggle-slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<button class="btn-delete-selected disabled" onclick="modelDuplicatesManager.deleteSelectedDuplicates()">
|
||||||
|
Delete Selected (<span id="duplicatesSelectedCount">0</span>)
|
||||||
|
</button>
|
||||||
|
<button class="btn-exit-mode" onclick="modelDuplicatesManager.exitDuplicateMode()">
|
||||||
|
<i class="fas fa-times"></i> Exit Mode
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="help-tooltip" id="duplicatesHelpTooltip">
|
||||||
|
<p>Identical hashes mean identical model files, even if they have different names or previews.</p>
|
||||||
|
<p>Keep only one version (preferably with better metadata/previews) and safely delete the others.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -30,27 +30,7 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% include 'components/controls.html' %}
|
{% include 'components/controls.html' %}
|
||||||
|
{% include 'components/duplicates_banner.html' %}
|
||||||
<!-- Duplicates banner (hidden by default) -->
|
|
||||||
<div id="duplicatesBanner" class="duplicates-banner" style="display: none;">
|
|
||||||
<div class="banner-content">
|
|
||||||
<i class="fas fa-exclamation-triangle"></i>
|
|
||||||
<span id="duplicatesCount">Found 0 duplicate groups</span>
|
|
||||||
<i class="fas fa-question-circle help-icon" id="duplicatesHelp" aria-label="Help information"></i>
|
|
||||||
<div class="banner-actions">
|
|
||||||
<button class="btn-delete-selected disabled" onclick="modelDuplicatesManager.deleteSelectedDuplicates()">
|
|
||||||
Delete Selected (<span id="duplicatesSelectedCount">0</span>)
|
|
||||||
</button>
|
|
||||||
<button class="btn-exit-mode" onclick="modelDuplicatesManager.exitDuplicateMode()">
|
|
||||||
<i class="fas fa-times"></i> Exit Mode
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="help-tooltip" id="duplicatesHelpTooltip">
|
|
||||||
<p>Identical hashes mean identical model files, even if they have different names or previews.</p>
|
|
||||||
<p>Keep only one version (preferably with better metadata/previews) and safely delete the others.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Embedding cards container -->
|
<!-- Embedding cards container -->
|
||||||
<div class="card-grid" id="modelGrid">
|
<div class="card-grid" id="modelGrid">
|
||||||
|
|||||||
@@ -16,27 +16,7 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
{% include 'components/controls.html' %}
|
{% include 'components/controls.html' %}
|
||||||
{% include 'components/alphabet_bar.html' %}
|
{% include 'components/alphabet_bar.html' %}
|
||||||
|
{% include 'components/duplicates_banner.html' %}
|
||||||
<!-- Duplicates banner (hidden by default) -->
|
|
||||||
<div id="duplicatesBanner" class="duplicates-banner" style="display: none;">
|
|
||||||
<div class="banner-content">
|
|
||||||
<i class="fas fa-exclamation-triangle"></i>
|
|
||||||
<span id="duplicatesCount">Found 0 duplicate groups</span>
|
|
||||||
<i class="fas fa-question-circle help-icon" id="duplicatesHelp" aria-label="Help information"></i>
|
|
||||||
<div class="banner-actions">
|
|
||||||
<button class="btn-delete-selected disabled" onclick="modelDuplicatesManager.deleteSelectedDuplicates()">
|
|
||||||
Delete Selected (<span id="duplicatesSelectedCount">0</span>)
|
|
||||||
</button>
|
|
||||||
<button class="btn-exit-mode" onclick="modelDuplicatesManager.exitDuplicateMode()">
|
|
||||||
<i class="fas fa-times"></i> Exit Mode
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="help-tooltip" id="duplicatesHelpTooltip">
|
|
||||||
<p>Identical hashes mean identical model files, even if they have different names or previews.</p>
|
|
||||||
<p>Keep only one version (preferably with better metadata/previews) and safely delete the others.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Lora卡片容器 -->
|
<!-- Lora卡片容器 -->
|
||||||
<div class="card-grid" id="modelGrid">
|
<div class="card-grid" id="modelGrid">
|
||||||
|
|||||||
Reference in New Issue
Block a user