diff --git a/web_interface/templates/v3/base.html b/web_interface/templates/v3/base.html index 841ed4c6..f8a08295 100644 --- a/web_interface/templates/v3/base.html +++ b/web_interface/templates/v3/base.html @@ -352,15 +352,14 @@ } }); - // Set data-loaded on tab containers after HTMX settles their content, - // preventing repeated re-fetches on every tab switch. - // Scoped to elements with hx-trigger="revealed" (tab containers only) so - // modals and plugin config panels that legitimately reload are unaffected. + // Mark tab containers as loaded once their content settles, so switching + // away and back doesn't re-fetch. Scoped to the "loadtab" trigger (tab + // containers only) so modals and plugin config panels can still reload. document.body.addEventListener('htmx:afterSettle', function(event) { if (event.detail && event.detail.target) { var target = event.detail.target; var trigger = target.getAttribute('hx-trigger') || ''; - if (trigger.includes('revealed')) { + if (trigger.includes('loadtab')) { target.setAttribute('data-loaded', 'true'); } } @@ -1030,7 +1029,7 @@
-
+
@@ -1098,7 +1097,7 @@
-
+
@@ -1116,7 +1115,7 @@
@@ -1167,7 +1166,7 @@
-
+
@@ -1183,7 +1182,7 @@
-
+
@@ -1198,7 +1197,7 @@
-
+
@@ -1210,7 +1209,7 @@
-
+
@@ -1225,7 +1224,7 @@
-
@@ -1242,7 +1241,7 @@
-
+
@@ -1257,7 +1256,7 @@
-
+
@@ -1269,7 +1268,7 @@
-
+
@@ -1281,7 +1280,7 @@
-
+
@@ -1861,28 +1860,53 @@ }, loadTabContent(tab) { - // Try to load content for the active tab - if (typeof htmx !== 'undefined') { - const contentId = tab + '-content'; - const contentEl = document.getElementById(contentId); - if (contentEl && !contentEl.hasAttribute('data-loaded')) { - // Trigger HTMX load - htmx.trigger(contentEl, 'revealed'); + const contentEl = document.getElementById(tab + '-content'); + // data-loaded: already fetched. data-loading: a fetch is queued or in + // flight. Both guard against re-entry so a panel loads exactly once, even + // if the tab is reopened before an in-progress (or polling) load settles. + if (!contentEl || contentEl.hasAttribute('data-loaded') || contentEl.hasAttribute('data-loading')) return; + const url = contentEl.getAttribute('hx-get'); + if (!url) return; + + contentEl.setAttribute('data-loading', 'true'); + + // htmx.ajax issues the request and swaps the response into the panel + // directly, so it works even before htmx has wired up the element's + // hx-trigger listeners. data-loaded is stamped on success so the panel + // loads once; the activeTab check drops loads for a tab the user navigated + // away from while htmx was still loading (avoids fetching hidden panels). + const swap = contentEl.getAttribute('hx-swap') || 'innerHTML'; + const load = () => { + if (this.activeTab !== tab || contentEl.hasAttribute('data-loaded')) { + contentEl.removeAttribute('data-loading'); + return; } - } else { - // HTMX is still loading asynchronously — retry when it signals ready, - // or fall back to direct fetch if it fails to load entirely. - const self = this; - function onReady() { window.removeEventListener('htmx-load-failed', onFailed); self.loadTabContent(tab); } - function onFailed() { - window.removeEventListener('htmx:ready', onReady); + return htmx.ajax('GET', url, { target: contentEl, swap: swap }) + .then(() => contentEl.setAttribute('data-loaded', 'true')) + .catch(() => {}) // leave unstamped on failure so it can retry + .finally(() => contentEl.removeAttribute('data-loading')); + }; + + if (typeof htmx !== 'undefined') { + load(); + return; + } + + // htmx is loaded from a CDN and may not be ready yet. Poll until it is, + // then load; if it never arrives, fall back to a direct fetch. + let tries = 0; + const timer = setInterval(() => { + if (typeof htmx !== 'undefined') { + clearInterval(timer); + load(); + } else if (++tries > 100) { // ~10s + clearInterval(timer); + contentEl.removeAttribute('data-loading'); if (tab === 'overview' && typeof loadOverviewDirect === 'function') loadOverviewDirect(); else if (tab === 'wifi' && typeof loadWifiDirect === 'function') loadWifiDirect(); else if (tab === 'plugins' && typeof loadPluginsDirect === 'function') loadPluginsDirect(); } - window.addEventListener('htmx:ready', onReady, { once: true }); - window.addEventListener('htmx-load-failed', onFailed, { once: true }); - } + }, 100); }, async loadInstalledPlugins() {