mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-22 05:32:12 -03:00
Delay DOM creation in LoadingManager constructor to first use time, ensuring window.i18n is ready before translate() is called. This eliminates the 'i18n not available' console warning during module initialization while maintaining correct translations for cancel button and loading status text.
367 lines
13 KiB
JavaScript
367 lines
13 KiB
JavaScript
import { translate } from '../utils/i18nHelpers.js';
|
|
import { formatFileSize } from '../utils/formatters.js';
|
|
|
|
// Loading management
|
|
export class LoadingManager {
|
|
constructor() {
|
|
if (LoadingManager.instance) {
|
|
return LoadingManager.instance;
|
|
}
|
|
|
|
// Delay DOM creation until first use to ensure i18n is ready
|
|
this._initialized = false;
|
|
this.overlay = null;
|
|
this.loadingContent = null;
|
|
this.progressBar = null;
|
|
this.statusText = null;
|
|
this.cancelButton = null;
|
|
this.onCancelCallback = null;
|
|
this.detailsContainer = null;
|
|
|
|
LoadingManager.instance = this;
|
|
}
|
|
|
|
_ensureInitialized() {
|
|
if (this._initialized) return;
|
|
|
|
this.overlay = document.getElementById('loading-overlay');
|
|
|
|
if (!this.overlay) {
|
|
this.overlay = document.createElement('div');
|
|
this.overlay.id = 'loading-overlay';
|
|
this.overlay.style.display = 'none';
|
|
document.body.appendChild(this.overlay);
|
|
}
|
|
|
|
this.loadingContent = this.overlay.querySelector('.loading-content');
|
|
if (!this.loadingContent) {
|
|
this.loadingContent = document.createElement('div');
|
|
this.loadingContent.className = 'loading-content';
|
|
this.overlay.appendChild(this.loadingContent);
|
|
}
|
|
|
|
this.progressBar = this.loadingContent.querySelector('.progress-bar');
|
|
if (!this.progressBar) {
|
|
this.progressBar = document.createElement('div');
|
|
this.progressBar.className = 'progress-bar';
|
|
this.progressBar.setAttribute('role', 'progressbar');
|
|
this.progressBar.setAttribute('aria-valuemin', '0');
|
|
this.progressBar.setAttribute('aria-valuemax', '100');
|
|
this.progressBar.setAttribute('aria-valuenow', '0');
|
|
this.progressBar.style.width = '0%';
|
|
this.loadingContent.appendChild(this.progressBar);
|
|
}
|
|
|
|
this.statusText = this.loadingContent.querySelector('.loading-status');
|
|
if (!this.statusText) {
|
|
this.statusText = document.createElement('div');
|
|
this.statusText.className = 'loading-status';
|
|
this.loadingContent.appendChild(this.statusText);
|
|
}
|
|
|
|
this.cancelButton = this.loadingContent.querySelector('.loading-cancel');
|
|
if (!this.cancelButton) {
|
|
this.cancelButton = document.createElement('button');
|
|
this.cancelButton.className = 'loading-cancel secondary-btn';
|
|
this.cancelButton.style.display = 'none';
|
|
this.cancelButton.style.margin = 'var(--space-2) auto 0';
|
|
this.cancelButton.textContent = translate('common.actions.cancel', {}, 'Cancel');
|
|
this.loadingContent.appendChild(this.cancelButton);
|
|
}
|
|
|
|
this.cancelButton.onclick = () => {
|
|
if (this.onCancelCallback) {
|
|
this.onCancelCallback();
|
|
this.cancelButton.disabled = true;
|
|
this.cancelButton.textContent = translate('common.status.loading', {}, 'Loading...');
|
|
}
|
|
};
|
|
|
|
this._initialized = true;
|
|
}
|
|
|
|
show(message = 'Loading...', progress = 0) {
|
|
this._ensureInitialized();
|
|
this.overlay.style.display = 'flex';
|
|
this.setProgress(progress);
|
|
this.setStatus(message);
|
|
|
|
// Remove any existing details container
|
|
this.removeDetailsContainer();
|
|
}
|
|
|
|
hide() {
|
|
if (!this._initialized) return;
|
|
this.overlay.style.display = 'none';
|
|
this.reset();
|
|
this.removeDetailsContainer();
|
|
}
|
|
|
|
setProgress(percent) {
|
|
if (!this._initialized) return;
|
|
this.progressBar.style.width = `${percent}%`;
|
|
this.progressBar.setAttribute('aria-valuenow', percent);
|
|
}
|
|
|
|
setStatus(message) {
|
|
if (!this._initialized) return;
|
|
this.statusText.textContent = message;
|
|
}
|
|
|
|
reset() {
|
|
if (!this._initialized) return;
|
|
this.setProgress(0);
|
|
this.setStatus('');
|
|
this.removeDetailsContainer();
|
|
this.hideCancelButton();
|
|
this.progressBar.style.display = 'block';
|
|
}
|
|
|
|
showCancelButton(onCancel) {
|
|
this._ensureInitialized();
|
|
if (this.cancelButton) {
|
|
this.onCancelCallback = onCancel;
|
|
this.cancelButton.style.display = 'flex';
|
|
this.cancelButton.disabled = false;
|
|
this.cancelButton.textContent = translate('common.actions.cancel', {}, 'Cancel');
|
|
}
|
|
}
|
|
|
|
hideCancelButton() {
|
|
if (!this._initialized) return;
|
|
if (this.cancelButton) {
|
|
this.cancelButton.style.display = 'none';
|
|
this.onCancelCallback = null;
|
|
}
|
|
}
|
|
|
|
// Create a details container for enhanced progress display
|
|
createDetailsContainer() {
|
|
this._ensureInitialized();
|
|
// Remove existing container if any
|
|
this.removeDetailsContainer();
|
|
|
|
// Create new container
|
|
this.detailsContainer = document.createElement('div');
|
|
this.detailsContainer.className = 'progress-details-container';
|
|
|
|
// Insert after the main progress bar
|
|
if (this.loadingContent) {
|
|
this.loadingContent.appendChild(this.detailsContainer);
|
|
}
|
|
|
|
return this.detailsContainer;
|
|
}
|
|
|
|
// Remove details container
|
|
removeDetailsContainer() {
|
|
if (this.detailsContainer) {
|
|
this.detailsContainer.remove();
|
|
this.detailsContainer = null;
|
|
}
|
|
}
|
|
|
|
// Show enhanced progress for downloads
|
|
showDownloadProgress(totalItems = 1) {
|
|
this.show(translate('modals.download.status.preparing', {}, 'Preparing download...'), 0);
|
|
this.progressBar.style.display = 'none';
|
|
|
|
// Create details container
|
|
const detailsContainer = this.createDetailsContainer();
|
|
|
|
// Create current item progress
|
|
const currentItemContainer = document.createElement('div');
|
|
currentItemContainer.className = 'current-item-progress';
|
|
|
|
const currentItemLabel = document.createElement('div');
|
|
currentItemLabel.className = 'current-item-label';
|
|
currentItemLabel.textContent = translate('modals.download.progress.currentFile', {}, 'Current file:');
|
|
|
|
const currentItemBar = document.createElement('div');
|
|
currentItemBar.className = 'current-item-bar-container';
|
|
|
|
const currentItemProgress = document.createElement('div');
|
|
currentItemProgress.className = 'current-item-bar';
|
|
currentItemProgress.style.width = '0%';
|
|
|
|
const currentItemPercent = document.createElement('span');
|
|
currentItemPercent.className = 'current-item-percent';
|
|
currentItemPercent.textContent = '0%';
|
|
|
|
currentItemBar.appendChild(currentItemProgress);
|
|
currentItemContainer.appendChild(currentItemLabel);
|
|
currentItemContainer.appendChild(currentItemBar);
|
|
currentItemContainer.appendChild(currentItemPercent);
|
|
|
|
// Create overall progress elements if multiple items
|
|
let overallLabel = null;
|
|
if (totalItems > 1) {
|
|
overallLabel = document.createElement('div');
|
|
overallLabel.className = 'overall-progress-label';
|
|
overallLabel.textContent = `Overall progress (0/${totalItems} complete):`;
|
|
detailsContainer.appendChild(overallLabel);
|
|
}
|
|
|
|
// Add current item progress to container
|
|
detailsContainer.appendChild(currentItemContainer);
|
|
|
|
// Create transfer stats container
|
|
const transferStats = document.createElement('div');
|
|
transferStats.className = 'download-transfer-stats';
|
|
|
|
const bytesDetail = document.createElement('div');
|
|
bytesDetail.className = 'download-transfer-bytes';
|
|
bytesDetail.textContent = translate(
|
|
'modals.download.progress.transferredUnknown',
|
|
{},
|
|
'Transferred: --'
|
|
);
|
|
|
|
const speedDetail = document.createElement('div');
|
|
speedDetail.className = 'download-transfer-speed';
|
|
speedDetail.textContent = translate(
|
|
'modals.download.progress.speed',
|
|
{ speed: '--' },
|
|
'Speed: --'
|
|
);
|
|
|
|
transferStats.appendChild(bytesDetail);
|
|
transferStats.appendChild(speedDetail);
|
|
detailsContainer.appendChild(transferStats);
|
|
|
|
const formatMetricSize = (value) => {
|
|
if (value === undefined || value === null || isNaN(value)) {
|
|
return '--';
|
|
}
|
|
if (value < 1) {
|
|
return '0 B';
|
|
}
|
|
return formatFileSize(value);
|
|
};
|
|
|
|
const updateTransferStats = (metrics = {}) => {
|
|
const { bytesDownloaded, totalBytes, bytesPerSecond } = metrics;
|
|
|
|
if (bytesDetail) {
|
|
const formattedDownloaded = formatMetricSize(bytesDownloaded);
|
|
const formattedTotal = formatMetricSize(totalBytes);
|
|
|
|
if (formattedDownloaded === '--' && formattedTotal === '--') {
|
|
bytesDetail.textContent = translate(
|
|
'modals.download.progress.transferredUnknown',
|
|
{},
|
|
'Transferred: --'
|
|
);
|
|
} else if (formattedTotal === '--') {
|
|
bytesDetail.textContent = translate(
|
|
'modals.download.progress.transferredSimple',
|
|
{ downloaded: formattedDownloaded },
|
|
`Transferred: ${formattedDownloaded}`
|
|
);
|
|
} else {
|
|
bytesDetail.textContent = translate(
|
|
'modals.download.progress.transferred',
|
|
{ downloaded: formattedDownloaded, total: formattedTotal },
|
|
`Transferred: ${formattedDownloaded} / ${formattedTotal}`
|
|
);
|
|
}
|
|
}
|
|
|
|
if (speedDetail) {
|
|
const formattedSpeed = formatMetricSize(bytesPerSecond);
|
|
const displaySpeed = formattedSpeed === '--' ? '--' : `${formattedSpeed}/s`;
|
|
speedDetail.textContent = translate(
|
|
'modals.download.progress.speed',
|
|
{ speed: displaySpeed },
|
|
`Speed: ${displaySpeed}`
|
|
);
|
|
}
|
|
};
|
|
|
|
// Initialize transfer stats with empty data
|
|
updateTransferStats();
|
|
|
|
// Return update function
|
|
return (currentProgress, currentIndex = 0, currentName = '', metrics = {}) => {
|
|
// Update current item progress
|
|
currentItemProgress.style.width = `${currentProgress}%`;
|
|
currentItemPercent.textContent = `${Math.floor(currentProgress)}%`;
|
|
|
|
// Update current item label if name provided
|
|
if (currentName) {
|
|
currentItemLabel.textContent = translate(
|
|
'modals.download.progress.downloading',
|
|
{ name: currentName },
|
|
`Downloading: ${currentName}`
|
|
);
|
|
}
|
|
|
|
// Update overall label if multiple items
|
|
if (totalItems > 1 && overallLabel) {
|
|
overallLabel.textContent = `Overall progress (${currentIndex}/${totalItems} complete):`;
|
|
|
|
// Calculate and update overall progress
|
|
const overallProgress = Math.floor((currentIndex + currentProgress / 100) / totalItems * 100);
|
|
this.setProgress(overallProgress);
|
|
} else {
|
|
// Single item, just update main progress
|
|
this.setProgress(currentProgress);
|
|
}
|
|
|
|
updateTransferStats(metrics);
|
|
};
|
|
}
|
|
|
|
async showWithProgress(callback, options = {}) {
|
|
const { initialMessage = 'Processing...', completionMessage = 'Complete' } = options;
|
|
|
|
try {
|
|
this.show(initialMessage);
|
|
await callback(this);
|
|
this.setProgress(100);
|
|
this.setStatus(completionMessage);
|
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
} finally {
|
|
this.hide();
|
|
}
|
|
}
|
|
|
|
// Enhanced progress display without callback pattern
|
|
showEnhancedProgress(message = 'Processing...') {
|
|
this.show(message, 0);
|
|
|
|
// Return update functions
|
|
return {
|
|
updateProgress: (percent, currentItem = '', statusMessage = '') => {
|
|
this.setProgress(percent);
|
|
if (statusMessage) {
|
|
this.setStatus(statusMessage);
|
|
}
|
|
},
|
|
|
|
showCancelButton: (onCancel) => {
|
|
this.showCancelButton(onCancel);
|
|
},
|
|
|
|
complete: async (completionMessage = 'Complete') => {
|
|
this.setProgress(100);
|
|
this.setStatus(completionMessage);
|
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
this.hide();
|
|
}
|
|
};
|
|
}
|
|
|
|
showSimpleLoading(message = 'Loading...') {
|
|
this._ensureInitialized();
|
|
this.overlay.style.display = 'flex';
|
|
this.progressBar.style.display = 'none';
|
|
this.setStatus(message);
|
|
}
|
|
|
|
restoreProgressBar() {
|
|
if (!this._initialized) return;
|
|
this.progressBar.style.display = 'block';
|
|
}
|
|
}
|