fix(ui): update notification inline in header title area, no full-page banner
Instead of a fixed banner that covers the top of the page, the update
notification now replaces only the header title area (the centered title):
- .header-title content is swapped in-place with an animated pill:
⬆️ v1.x.x [Aggiorna] ✕
- Pulsing animation (header-update-pulse) draws attention without being
intrusive; camera and Gemini buttons stay exactly where they are
- [Aggiorna] button does window.location.reload()
- [✕] dismisses: for a release update stores publishedAt so it won't reappear;
for a server deploy simply restores title (reappears next 5-min check)
- Auto-restores after 60 s without marking as seen
- Removed the old fixed position:fixed banner entirely
This commit is contained in:
@@ -135,6 +135,41 @@ body {
|
|||||||
letter-spacing: 0.03em;
|
letter-spacing: 0.03em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Update available indicator — replaces title content, same absolute centering */
|
||||||
|
.header-update-pill {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
animation: header-update-pulse 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
.header-update-pill span {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 600;
|
||||||
|
opacity: 0.95;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.header-update-btn {
|
||||||
|
background: #fbbf24;
|
||||||
|
color: #1e293b;
|
||||||
|
border: none;
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 3px 12px;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-weight: 800;
|
||||||
|
cursor: pointer;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
flex-shrink: 0;
|
||||||
|
transition: background 0.15s, transform 0.1s;
|
||||||
|
}
|
||||||
|
.header-update-btn:active {
|
||||||
|
background: #f59e0b;
|
||||||
|
transform: scale(0.96);
|
||||||
|
}
|
||||||
|
@keyframes header-update-pulse {
|
||||||
|
0%, 100% { opacity: 1; }
|
||||||
|
50% { opacity: 0.7; }
|
||||||
|
}
|
||||||
|
|
||||||
.header-btn {
|
.header-btn {
|
||||||
background: rgba(255,255,255,0.2);
|
background: rgba(255,255,255,0.2);
|
||||||
border: none;
|
border: none;
|
||||||
|
|||||||
+30
-34
@@ -81,9 +81,9 @@ function reportError(payload) {
|
|||||||
const _loadedVersion = (document.querySelector('.header-version')?.textContent?.trim() || '').replace(/^v/, '');
|
const _loadedVersion = (document.querySelector('.header-version')?.textContent?.trim() || '').replace(/^v/, '');
|
||||||
|
|
||||||
function _checkWebappUpdate() {
|
function _checkWebappUpdate() {
|
||||||
const STORAGE_KEY = '_evershelf_update_checked_at'; // last-checked timestamp
|
const STORAGE_KEY = '_evershelf_update_checked_at';
|
||||||
const SEEN_KEY = '_evershelf_update_seen_ts'; // published_at of last-dismissed release
|
const SEEN_KEY = '_evershelf_update_seen_ts';
|
||||||
const TTL_MS = 5 * 60 * 1000; // re-check every 5 min
|
const TTL_MS = 5 * 60 * 1000;
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const lastCheck = parseInt(localStorage.getItem(STORAGE_KEY) || '0', 10);
|
const lastCheck = parseInt(localStorage.getItem(STORAGE_KEY) || '0', 10);
|
||||||
if (now - lastCheck < TTL_MS) return;
|
if (now - lastCheck < TTL_MS) return;
|
||||||
@@ -93,9 +93,10 @@ function _checkWebappUpdate() {
|
|||||||
.then(r => r.ok ? r.json() : null)
|
.then(r => r.ok ? r.json() : null)
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (!data) return;
|
if (!data) return;
|
||||||
if (document.getElementById('_evershelf_update_banner')) return;
|
// Already showing — don't stack
|
||||||
|
if (document.getElementById('_header_update_pill')) return;
|
||||||
|
|
||||||
// ── Check 1: server has a different version deployed since this page loaded ──
|
// ── Check 1: server has a newer version deployed since this page loaded ──
|
||||||
const serverVer = (data.webapp_version || '').replace(/^v/, '');
|
const serverVer = (data.webapp_version || '').replace(/^v/, '');
|
||||||
const deployChanged = serverVer && _loadedVersion && serverVer !== _loadedVersion;
|
const deployChanged = serverVer && _loadedVersion && serverVer !== _loadedVersion;
|
||||||
|
|
||||||
@@ -109,40 +110,35 @@ function _checkWebappUpdate() {
|
|||||||
|
|
||||||
if (!deployChanged && !releaseNewer) return;
|
if (!deployChanged && !releaseNewer) return;
|
||||||
|
|
||||||
const banner = document.createElement('div');
|
// ── Show update indicator inside the header title area ──
|
||||||
banner.id = '_evershelf_update_banner';
|
const titleEl = document.querySelector('.header-title');
|
||||||
banner.style.cssText = [
|
if (!titleEl) return;
|
||||||
'position:fixed;top:0;left:0;right:0;z-index:99999',
|
const originalHTML = titleEl.innerHTML;
|
||||||
'background:#1e293b;color:#fbbf24',
|
|
||||||
'padding:10px 16px;font-size:13px',
|
|
||||||
'display:flex;align-items:center;justify-content:space-between',
|
|
||||||
'border-bottom:2px solid #fbbf24',
|
|
||||||
'box-shadow:0 2px 8px rgba(0,0,0,.4)',
|
|
||||||
].join(';');
|
|
||||||
|
|
||||||
let msgHtml, dismissAction;
|
const versionLabel = deployChanged
|
||||||
|
? (serverVer ? `v${serverVer}` : 'Nuova versione')
|
||||||
|
: (latestTag ? `v${latestTag}` : 'Nuova versione');
|
||||||
|
|
||||||
|
let dismissAction;
|
||||||
if (deployChanged) {
|
if (deployChanged) {
|
||||||
// Server files updated — simple reload prompt
|
dismissAction = () => { titleEl.innerHTML = originalHTML; };
|
||||||
msgHtml = `<span>🔄 Nuova versione dell'app disponibile sul server.</span>`;
|
|
||||||
dismissAction = () => banner.remove(); // just hide, will reappear next check
|
|
||||||
} else {
|
} else {
|
||||||
const releaseUrl = data.html_url || 'https://github.com/dadaloop82/EverShelf/releases/latest';
|
dismissAction = () => { localStorage.setItem(SEEN_KEY, publishedAt); titleEl.innerHTML = originalHTML; };
|
||||||
const versionText = /^\d+\.\d+/.test(latestTag) ? ` <strong>v${latestTag}</strong>` : '';
|
|
||||||
msgHtml = `<span>⬆️ EverShelf${versionText} disponibile. ` +
|
|
||||||
`<a href="${releaseUrl}" target="_blank" rel="noopener" ` +
|
|
||||||
`style="color:#64748b;font-size:0.9em;text-decoration:underline">novità</a></span>`;
|
|
||||||
dismissAction = () => { localStorage.setItem(SEEN_KEY, publishedAt); banner.remove(); };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
banner.innerHTML = msgHtml +
|
titleEl.innerHTML =
|
||||||
`<button onclick="window.location.reload()" ` +
|
`<span class="header-update-pill" id="_header_update_pill">` +
|
||||||
`style="background:#fbbf24;border:none;color:#1e293b;font-weight:700;padding:5px 14px;border-radius:6px;cursor:pointer;font-size:13px;margin:0 8px">Ricarica</button>` +
|
`<span>⬆️ ${versionLabel}</span>` +
|
||||||
`<button id="_evershelf_banner_close" ` +
|
`<button class="header-update-btn" onclick="window.location.reload()">Aggiorna</button>` +
|
||||||
`style="background:none;border:none;color:#94a3b8;font-size:18px;cursor:pointer;padding:0 4px">✕</button>`;
|
`<button style="background:none;border:none;color:rgba(255,255,255,0.6);font-size:1rem;cursor:pointer;padding:0 2px;line-height:1" ` +
|
||||||
document.body.prepend(banner);
|
`id="_header_update_close">✕</button>` +
|
||||||
document.getElementById('_evershelf_banner_close').onclick = dismissAction;
|
`</span>`;
|
||||||
// Auto-dismiss after 30 s without marking as seen
|
document.getElementById('_header_update_close').onclick = (e) => {
|
||||||
setTimeout(() => { if (banner.isConnected) banner.remove(); }, 30000);
|
e.stopPropagation();
|
||||||
|
dismissAction();
|
||||||
|
};
|
||||||
|
// Auto-restore after 60 s without marking as seen
|
||||||
|
setTimeout(() => { if (document.getElementById('_header_update_pill')) dismissAction(); }, 60000);
|
||||||
})
|
})
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user