Update icon beside the search box after the update dialog is dismissed

Shown as soon as an update is offered, so Later, a backdrop tap or a failed
download all leave a one-tap way to update; it targets the newest build and
disappears while updating. The search form and box now shrink so the icon
never pushes Search off a phone screen.
This commit is contained in:
Jonathan Sykes
2026-10-03 01:26:52 +08:00
parent 65f5325a8e
commit 6581509ddd
3 changed files with 45 additions and 2 deletions

View File

@@ -10519,6 +10519,7 @@ document.querySelectorAll('.chip').forEach((c) => {
// Mini now-playing bar
$('miniPlayBtn').addEventListener('click', (e) => { e.stopPropagation(); Player.toggle(); });
$('updateIconBtn').addEventListener('click', updateFromIcon);
$('miniBar').addEventListener('click', () => {
setHomeOverPlayer(false);
hideMiniBar();
@@ -11102,16 +11103,37 @@ async function hardReloadUI() {
let _updateBannerShown = false;
let _updating = false;
let _updateToast = null;
// After "Later" (or a failed download) an update icon sits beside the search
// box until the user takes the update; it always targets the newest build.
function setUpdateIcon(on) {
const b = $('updateIconBtn');
if (b) b.classList.toggle('hidden', !on);
}
async function updateFromIcon() {
if (_updating) return;
setUpdateIcon(false);
let target = null;
try { target = await fetchServerBuild(); } catch { /* offline */ }
if (!target || target === (RUNNING_BUILD || _knownBuildTag)) {
if (!target) { toast('Offline — try the update again when connected'); setUpdateIcon(true); }
return;
}
await applyUpdate(target);
}
function showUpdateBanner(target) {
// Only show the dialog once per page load
if (_updateBannerShown) return;
_updateBannerShown = true;
// However the dialog is dismissed (Later, backdrop), the icon remains.
setUpdateIcon(true);
// Show a modal dialog instead of a fleeting toast so the user can't miss it.
const body = document.createElement('p');
body.textContent = 'A new version of YT Player is available. Click "Refresh UI" to reload the page with the latest build. All your playlists and history are stored locally and will be preserved.';
showModal('⬆ Update available', body, [
{ label: 'Later', onClick: closeModal },
{ label: 'Later', onClick: () => { closeModal(); setUpdateIcon(true); } },
{ label: 'Refresh UI', primary: true, onClick: async () => {
closeModal();
await applyUpdate(target);
@@ -11185,7 +11207,8 @@ async function applyUpdate(target, { quiet = false } = {}) {
_updating = false;
writeUpdateState(null);
if (_updateToast) { _updateToast.remove(); _updateToast = null; }
toast('⚠ Update didn’t download (' + (err && err.message ? err.message : 'connection problem') + '). It will be offered again.', { duration: 6000 });
toast('⚠ Update didn’t download (' + (err && err.message ? err.message : 'connection problem') + '). Tap the update icon to try again.', { duration: 6000 });
setUpdateIcon(true);
// Let the next poll re-offer it.
_updateBannerShown = false;
}

View File

@@ -100,6 +100,10 @@
placeholder="Search YouTube… (no login, no ads)"
autocomplete="off"
/>
<!-- Shown after "Later" on the update dialog: one tap updates -->
<button id="updateIconBtn" type="button" class="update-icon-btn hidden" title="Update available — tap to update" aria-label="Update available — tap to update">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M20 12a8 8 0 1 1-2.34-5.66M20 4v4.5h-4.5"/><path d="M12 8v5M9.5 11l2.5 2.5 2.5-2.5"/></svg>
</button>
<button type="submit" class="search-btn">Search</button>
<div id="searchRecent" class="search-recent hidden" role="listbox" aria-label="Recent searches"></div>
</form>

View File

@@ -4287,3 +4287,19 @@ body.landscape-fs .player-stage { touch-action: none; } /* fullscreen (real or t
.player-pane.home-over #artFallback { visibility: hidden; }
.player-pane.home-over .related-panel { display: none; }
.player-pane.home-over > :not(.player-stage):not(#serviceMode) { display: none !important; }
/* Pending-update icon beside the search box (shown after the update dialog) */
.update-icon-btn {
flex: none; width: 44px; height: 44px; padding: 0; align-self: center;
display: grid; place-items: center; cursor: pointer;
border-radius: 50%; border: 1px solid var(--accent);
background: color-mix(in srgb, var(--accent) 16%, var(--bg-2)); color: var(--accent-bright);
box-shadow: 0 0 0 0 var(--accent-glow); animation: updPulse 2.4s ease-in-out infinite;
}
.update-icon-btn svg { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; }
.update-icon-btn.hidden { display: none; }
@keyframes updPulse { 0%, 100% { box-shadow: 0 0 0 0 var(--accent-glow); } 50% { box-shadow: 0 0 0 6px transparent; } }
@media (prefers-reduced-motion: reduce) { .update-icon-btn { animation: none; } }
/* The search box gives up width for the update icon instead of pushing Search off screen */
.search-form { min-width: 0; }
.search-form #searchInput { flex: 1 1 auto; min-width: 0; }