mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-25 23:25:43 -03:00
feat: implement version mismatch handling and banner registration in UpdateService
This commit is contained in:
@@ -62,6 +62,12 @@ class BannerService {
|
|||||||
*/
|
*/
|
||||||
registerBanner(id, bannerConfig) {
|
registerBanner(id, bannerConfig) {
|
||||||
this.banners.set(id, bannerConfig);
|
this.banners.set(id, bannerConfig);
|
||||||
|
|
||||||
|
// If already initialized, render the banner immediately
|
||||||
|
if (this.initialized && !this.isBannerDismissed(id) && this.container) {
|
||||||
|
this.renderBanner(bannerConfig);
|
||||||
|
this.updateContainerVisibility();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -88,6 +94,12 @@ class BannerService {
|
|||||||
// Remove banner from DOM
|
// Remove banner from DOM
|
||||||
const bannerElement = document.querySelector(`[data-banner-id="${bannerId}"]`);
|
const bannerElement = document.querySelector(`[data-banner-id="${bannerId}"]`);
|
||||||
if (bannerElement) {
|
if (bannerElement) {
|
||||||
|
// Call onRemove callback if provided
|
||||||
|
const banner = this.banners.get(bannerId);
|
||||||
|
if (banner && typeof banner.onRemove === 'function') {
|
||||||
|
banner.onRemove(bannerElement);
|
||||||
|
}
|
||||||
|
|
||||||
bannerElement.style.animation = 'banner-slide-up 0.3s ease-in-out forwards';
|
bannerElement.style.animation = 'banner-slide-up 0.3s ease-in-out forwards';
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
bannerElement.remove();
|
bannerElement.remove();
|
||||||
@@ -122,12 +134,16 @@ class BannerService {
|
|||||||
bannerElement.className = 'banner-item';
|
bannerElement.className = 'banner-item';
|
||||||
bannerElement.setAttribute('data-banner-id', banner.id);
|
bannerElement.setAttribute('data-banner-id', banner.id);
|
||||||
|
|
||||||
const actionsHtml = banner.actions ? banner.actions.map(action =>
|
const actionsHtml = banner.actions ? banner.actions.map(action => {
|
||||||
`<a href="${action.url}" target="_blank" class="banner-action banner-action-${action.type}" rel="noopener noreferrer">
|
const actionAttribute = action.action ? `data-action="${action.action}"` : '';
|
||||||
|
const href = action.url ? `href="${action.url}"` : '#';
|
||||||
|
const target = action.url ? 'target="_blank" rel="noopener noreferrer"' : '';
|
||||||
|
|
||||||
|
return `<a ${href ? `href="${href}"` : ''} ${target} class="banner-action banner-action-${action.type}" ${actionAttribute}>
|
||||||
<i class="${action.icon}"></i>
|
<i class="${action.icon}"></i>
|
||||||
<span>${action.text}</span>
|
<span>${action.text}</span>
|
||||||
</a>`
|
</a>`;
|
||||||
).join('') : '';
|
}).join('') : '';
|
||||||
|
|
||||||
const dismissButtonHtml = banner.dismissible ?
|
const dismissButtonHtml = banner.dismissible ?
|
||||||
`<button class="banner-dismiss" onclick="bannerService.dismissBanner('${banner.id}')" title="Dismiss">
|
`<button class="banner-dismiss" onclick="bannerService.dismissBanner('${banner.id}')" title="Dismiss">
|
||||||
@@ -148,6 +164,11 @@ class BannerService {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
this.container.appendChild(bannerElement);
|
this.container.appendChild(bannerElement);
|
||||||
|
|
||||||
|
// Call onRegister callback if provided
|
||||||
|
if (typeof banner.onRegister === 'function') {
|
||||||
|
banner.onRegister(bannerElement);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
import { modalManager } from './ModalManager.js';
|
import { modalManager } from './ModalManager.js';
|
||||||
import { getStorageItem, setStorageItem } from '../utils/storageHelpers.js';
|
import {
|
||||||
|
getStorageItem,
|
||||||
|
setStorageItem,
|
||||||
|
getStoredVersionInfo,
|
||||||
|
setStoredVersionInfo,
|
||||||
|
isVersionMatch,
|
||||||
|
resetDismissedBanner
|
||||||
|
} from '../utils/storageHelpers.js';
|
||||||
|
import { bannerService } from './BannerService.js';
|
||||||
|
|
||||||
export class UpdateService {
|
export class UpdateService {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -17,6 +25,8 @@ export class UpdateService {
|
|||||||
this.lastCheckTime = parseInt(getStorageItem('last_update_check') || '0');
|
this.lastCheckTime = parseInt(getStorageItem('last_update_check') || '0');
|
||||||
this.isUpdating = false;
|
this.isUpdating = false;
|
||||||
this.nightlyMode = getStorageItem('nightly_updates', false);
|
this.nightlyMode = getStorageItem('nightly_updates', false);
|
||||||
|
this.currentVersionInfo = null;
|
||||||
|
this.versionMismatch = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
initialize() {
|
initialize() {
|
||||||
@@ -59,6 +69,9 @@ export class UpdateService {
|
|||||||
|
|
||||||
// Immediately update modal content with current values (even if from default)
|
// Immediately update modal content with current values (even if from default)
|
||||||
this.updateModalContent();
|
this.updateModalContent();
|
||||||
|
|
||||||
|
// Check version info for mismatch after loading basic info
|
||||||
|
this.checkVersionInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
updateNightlyWarning() {
|
updateNightlyWarning() {
|
||||||
@@ -424,6 +437,110 @@ export class UpdateService {
|
|||||||
// Ensure badge visibility is updated after manual check
|
// Ensure badge visibility is updated after manual check
|
||||||
this.updateBadgeVisibility();
|
this.updateBadgeVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async checkVersionInfo() {
|
||||||
|
try {
|
||||||
|
// Call API to get current version info
|
||||||
|
const response = await fetch('/api/version-info');
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
this.currentVersionInfo = data.version;
|
||||||
|
|
||||||
|
// Check if version matches stored version
|
||||||
|
this.versionMismatch = !isVersionMatch(this.currentVersionInfo);
|
||||||
|
|
||||||
|
if (this.versionMismatch) {
|
||||||
|
console.log('Version mismatch detected:', {
|
||||||
|
current: this.currentVersionInfo,
|
||||||
|
stored: getStoredVersionInfo()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reset dismissed status for version mismatch banner
|
||||||
|
resetDismissedBanner('version-mismatch');
|
||||||
|
|
||||||
|
// Register and show the version mismatch banner
|
||||||
|
this.registerVersionMismatchBanner();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to check version info:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerVersionMismatchBanner() {
|
||||||
|
// Get stored and current version for display
|
||||||
|
const storedVersion = getStoredVersionInfo() || 'unknown';
|
||||||
|
const currentVersion = this.currentVersionInfo || 'unknown';
|
||||||
|
|
||||||
|
bannerService.registerBanner('version-mismatch', {
|
||||||
|
id: 'version-mismatch',
|
||||||
|
title: 'Application Update Detected',
|
||||||
|
content: `Your browser is running an outdated version of LoRA Manager (${storedVersion}). The server has been updated to version ${currentVersion}. Please refresh to ensure proper functionality.`,
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
text: 'Refresh Now',
|
||||||
|
icon: 'fas fa-sync',
|
||||||
|
action: 'hardRefresh',
|
||||||
|
type: 'primary'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
dismissible: false,
|
||||||
|
priority: 10,
|
||||||
|
countdown: 15,
|
||||||
|
onRegister: (bannerElement) => {
|
||||||
|
// Add countdown element
|
||||||
|
const countdownEl = document.createElement('div');
|
||||||
|
countdownEl.className = 'banner-countdown';
|
||||||
|
countdownEl.innerHTML = `<span>Refreshing in <strong>15</strong> seconds...</span>`;
|
||||||
|
bannerElement.querySelector('.banner-content').appendChild(countdownEl);
|
||||||
|
|
||||||
|
// Start countdown
|
||||||
|
let seconds = 15;
|
||||||
|
const countdownInterval = setInterval(() => {
|
||||||
|
seconds--;
|
||||||
|
const strongEl = countdownEl.querySelector('strong');
|
||||||
|
if (strongEl) strongEl.textContent = seconds;
|
||||||
|
|
||||||
|
if (seconds <= 0) {
|
||||||
|
clearInterval(countdownInterval);
|
||||||
|
this.performHardRefresh();
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
// Store interval ID for cleanup
|
||||||
|
bannerElement.dataset.countdownInterval = countdownInterval;
|
||||||
|
|
||||||
|
// Add action button event handler
|
||||||
|
const actionBtn = bannerElement.querySelector('.banner-action[data-action="hardRefresh"]');
|
||||||
|
if (actionBtn) {
|
||||||
|
actionBtn.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
clearInterval(countdownInterval);
|
||||||
|
this.performHardRefresh();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRemove: (bannerElement) => {
|
||||||
|
// Clear any existing interval
|
||||||
|
const intervalId = bannerElement.dataset.countdownInterval;
|
||||||
|
if (intervalId) {
|
||||||
|
clearInterval(parseInt(intervalId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
performHardRefresh() {
|
||||||
|
// Update stored version info before refreshing
|
||||||
|
setStoredVersionInfo(this.currentVersionInfo);
|
||||||
|
|
||||||
|
// Force a hard refresh by adding cache-busting parameter
|
||||||
|
const cacheBuster = new Date().getTime();
|
||||||
|
window.location.href = window.location.pathname +
|
||||||
|
(window.location.search ? window.location.search + '&' : '?') +
|
||||||
|
`cache=${cacheBuster}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create and export singleton instance
|
// Create and export singleton instance
|
||||||
|
|||||||
@@ -213,4 +213,45 @@ export function getMapFromStorage(key) {
|
|||||||
console.error(`Error loading Map from localStorage (${key}):`, error);
|
console.error(`Error loading Map from localStorage (${key}):`, error);
|
||||||
return new Map();
|
return new Map();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get stored version info from localStorage
|
||||||
|
* @returns {string|null} The stored version string or null if not found
|
||||||
|
*/
|
||||||
|
export function getStoredVersionInfo() {
|
||||||
|
return getStorageItem('version_info', null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store version info to localStorage
|
||||||
|
* @param {string} versionInfo - The version info string to store
|
||||||
|
*/
|
||||||
|
export function setStoredVersionInfo(versionInfo) {
|
||||||
|
setStorageItem('version_info', versionInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if version info matches between stored and current
|
||||||
|
* @param {string} currentVersionInfo - The current version info from server
|
||||||
|
* @returns {boolean} True if versions match or no stored version exists
|
||||||
|
*/
|
||||||
|
export function isVersionMatch(currentVersionInfo) {
|
||||||
|
const storedVersion = getStoredVersionInfo();
|
||||||
|
// If we have no stored version yet, consider it a match
|
||||||
|
if (storedVersion === null) {
|
||||||
|
setStoredVersionInfo(currentVersionInfo);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return storedVersion === currentVersionInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the dismissed status of a specific banner
|
||||||
|
* @param {string} bannerId - The ID of the banner to un-dismiss
|
||||||
|
*/
|
||||||
|
export function resetDismissedBanner(bannerId) {
|
||||||
|
const dismissedBanners = getStorageItem('dismissed_banners', []);
|
||||||
|
const updatedBanners = dismissedBanners.filter(id => id !== bannerId);
|
||||||
|
setStorageItem('dismissed_banners', updatedBanners);
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user