@@ -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() {