update notification

This commit is contained in:
Broque Thomas 2026-03-04 15:01:54 -08:00
parent 48439af5b7
commit 6bbd52fda7
2 changed files with 108 additions and 7 deletions

View file

@ -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 = `
<div class="version-update-banner-icon">&#x2B06;</div>
<div class="version-update-banner-text">
<strong>New update available</strong>
<span>Your version: ${updateInfo.current_sha || 'unknown'} &rarr; Latest: ${updateInfo.latest_sha || 'unknown'}</span>
</div>
`;
container.appendChild(banner);
}
// Create sections
versionData.sections.forEach(section => {
const sectionDiv = document.createElement('div');

View file

@ -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;