From 6bbd52fda7d2c68aa7bcf560c3cd7fab2d817df1 Mon Sep 17 00:00:00 2001 From: Broque Thomas <26755000+Nezreka@users.noreply.github.com> Date: Wed, 4 Mar 2026 15:01:54 -0800 Subject: [PATCH] update notification --- webui/static/script.js | 56 ++++++++++++++++++++++++++++++++++----- webui/static/style.css | 59 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 7 deletions(-) diff --git a/webui/static/script.js b/webui/static/script.js index 6dcb540a..71bbe046 100644 --- a/webui/static/script.js +++ b/webui/static/script.js @@ -12090,10 +12090,26 @@ async function checkForUpdates() { if (data.update_available) { const dismissed = localStorage.getItem('soulsync-update-dismissed'); if (dismissed !== data.latest_sha) { + // Add glow class btn.classList.add('update-available'); + // Add UPDATE badge if not already present + if (!btn.querySelector('.update-badge')) { + const badge = document.createElement('span'); + badge.className = 'update-badge'; + badge.textContent = 'UPDATE'; + btn.appendChild(badge); + } + // Show toast on first detection (not if already notified this session) + const notified = sessionStorage.getItem('soulsync-update-notified'); + if (notified !== data.latest_sha) { + sessionStorage.setItem('soulsync-update-notified', data.latest_sha); + showToast('A new SoulSync update is available!', 'info'); + } } } else { btn.classList.remove('update-available'); + const badge = btn.querySelector('.update-badge'); + if (badge) badge.remove(); } } catch (e) { console.debug('Update check failed:', e); @@ -12101,13 +12117,25 @@ async function checkForUpdates() { } async function showVersionInfo() { - // Dismiss update glow when user opens the modal (fire-and-forget, don't block modal) + // Check update status before dismissing so we can pass it to the modal + let updateInfo = null; const btn = document.querySelector('.version-button'); - if (btn && btn.classList.contains('update-available')) { + const hadUpdate = btn && btn.classList.contains('update-available'); + + // Dismiss update glow when user opens the modal + if (hadUpdate) { btn.classList.remove('update-available'); - fetch('/api/update-check').then(r => r.json()).then(data => { - if (data.latest_sha) localStorage.setItem('soulsync-update-dismissed', data.latest_sha); - }).catch(() => {}); + const badge = btn.querySelector('.update-badge'); + if (badge) badge.remove(); + try { + const updateRes = await fetch('/api/update-check'); + if (updateRes.ok) { + updateInfo = await updateRes.json(); + if (updateInfo.latest_sha) { + localStorage.setItem('soulsync-update-dismissed', updateInfo.latest_sha); + } + } + } catch (e) { /* ignore */ } } try { @@ -12123,7 +12151,7 @@ async function showVersionInfo() { console.log('Version data received:', versionData); // Populate modal content - populateVersionModal(versionData); + populateVersionModal(versionData, hadUpdate ? updateInfo : null); // Show modal const modalOverlay = document.getElementById('version-modal-overlay'); @@ -12143,7 +12171,7 @@ function closeVersionModal() { console.log('Version modal closed'); } -function populateVersionModal(versionData) { +function populateVersionModal(versionData, updateInfo) { const container = document.getElementById('version-content-container'); if (!container) { console.error('Version content container not found'); @@ -12160,6 +12188,20 @@ function populateVersionModal(versionData) { // Clear existing content container.innerHTML = ''; + // Show update banner if an update was available when modal was opened + if (updateInfo && updateInfo.update_available) { + const banner = document.createElement('div'); + banner.className = 'version-update-banner'; + banner.innerHTML = ` +
+
+ New update available + Your version: ${updateInfo.current_sha || 'unknown'} → Latest: ${updateInfo.latest_sha || 'unknown'} +
+ `; + container.appendChild(banner); + } + // Create sections versionData.sections.forEach(section => { const sectionDiv = document.createElement('div'); diff --git a/webui/static/style.css b/webui/static/style.css index 953b27d2..a00c69bf 100644 --- a/webui/static/style.css +++ b/webui/static/style.css @@ -872,6 +872,26 @@ body { color: rgb(var(--accent-light-rgb)); border-color: rgba(var(--accent-rgb), 0.4); animation: version-glow 2s ease-in-out infinite; + position: relative; +} + +.version-button.update-available .update-badge { + display: inline-block; + margin-left: 5px; + font-size: 7px; + font-weight: 700; + letter-spacing: 0.5px; + background: rgb(var(--accent-rgb)); + color: #fff; + padding: 1px 5px; + border-radius: 8px; + vertical-align: middle; + animation: badge-pulse 2s ease-in-out infinite; +} + +@keyframes badge-pulse { + 0%, 100% { opacity: 0.85; } + 50% { opacity: 1; } } @keyframes version-glow { @@ -885,6 +905,45 @@ body { } } +/* Update banner inside version modal */ +.version-update-banner { + display: flex; + align-items: center; + gap: 10px; + padding: 12px 16px; + margin: 0 0 20px 0; + background: rgba(var(--accent-rgb), 0.08); + border: 1px solid rgba(var(--accent-rgb), 0.25); + border-radius: 8px; + color: rgb(var(--accent-light-rgb)); + font-size: 11px; + font-weight: 500; + letter-spacing: 0.1px; +} + +.version-update-banner-icon { + font-size: 16px; + flex-shrink: 0; +} + +.version-update-banner-text { + flex: 1; +} + +.version-update-banner-text strong { + display: block; + font-size: 12px; + font-weight: 600; + margin-bottom: 2px; + color: #fff; +} + +.version-update-banner-text span { + color: rgba(255, 255, 255, 0.6); + font-size: 10px; + font-weight: 400; +} + /* Status Section - Premium Glassmorphic Design */ .status-section { height: fit-content;