perf: make admin navigation responsive
This commit is contained in:
@@ -148,7 +148,7 @@ async function loadGlobalStats() {
|
||||
if (!bar) return;
|
||||
|
||||
try {
|
||||
const data = await api("/admin/api/stats");
|
||||
const data = await api("/admin/api/stats/summary");
|
||||
const t = document.getElementById("s-total");
|
||||
if (t) t.textContent = data.total.toLocaleString();
|
||||
const c = document.getElementById("s-content");
|
||||
@@ -161,6 +161,49 @@ async function loadGlobalStats() {
|
||||
}
|
||||
|
||||
|
||||
function installLoadingStates() {
|
||||
document.querySelectorAll("tbody:empty").forEach(tbody => {
|
||||
const columns = Math.max(1, tbody.closest("table")?.querySelectorAll("thead th").length || 5);
|
||||
tbody.setAttribute("aria-busy", "true");
|
||||
tbody.innerHTML = `<tr class="skeleton-row"><td colspan="${columns}"><span></span><span></span><span></span><span></span></td></tr>`;
|
||||
const observer = new MutationObserver(() => {
|
||||
if (!tbody.querySelector(".skeleton-row")) {
|
||||
tbody.removeAttribute("aria-busy");
|
||||
observer.disconnect();
|
||||
}
|
||||
});
|
||||
observer.observe(tbody, { childList: true });
|
||||
});
|
||||
["intel-signals-grid", "sourceTable", "statusTable"].forEach(id => {
|
||||
const node = document.getElementById(id);
|
||||
if (node && !node.children.length) node.classList.add("content-loading");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function installNavigationPrefetch() {
|
||||
const seen = new Set();
|
||||
const prefetch = link => {
|
||||
const href = link?.href;
|
||||
if (!href || seen.has(href) || link.origin !== location.origin) return;
|
||||
seen.add(href);
|
||||
const hint = document.createElement("link");
|
||||
hint.rel = "prefetch";
|
||||
hint.href = href;
|
||||
hint.as = "document";
|
||||
document.head.appendChild(hint);
|
||||
};
|
||||
document.querySelectorAll(".tabs a, .subnav a").forEach(link => {
|
||||
link.addEventListener("pointerenter", () => prefetch(link), { once: true });
|
||||
link.addEventListener("focus", () => prefetch(link), { once: true });
|
||||
link.addEventListener("click", () => document.documentElement.classList.add("navigating"));
|
||||
});
|
||||
const warm = () => document.querySelectorAll(".tabs a, .subnav a").forEach(prefetch);
|
||||
if ("requestIdleCallback" in window) requestIdleCallback(warm, { timeout: 2500 });
|
||||
else setTimeout(warm, 1200);
|
||||
}
|
||||
|
||||
|
||||
// common overlay close-on-backdrop wiring
|
||||
function wireOverlays() {
|
||||
document.querySelectorAll(".overlay").forEach(ov => {
|
||||
@@ -173,6 +216,10 @@ function wireOverlays() {
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
installChrome();
|
||||
installLoadingStates();
|
||||
installNavigationPrefetch();
|
||||
wireOverlays();
|
||||
loadGlobalStats();
|
||||
const loadStats = () => loadGlobalStats();
|
||||
if ("requestIdleCallback" in window) requestIdleCallback(loadStats, { timeout: 1800 });
|
||||
else setTimeout(loadStats, 500);
|
||||
});
|
||||
|
||||
@@ -13,6 +13,7 @@ const signalsById = new Map();
|
||||
function renderSignals(data) {
|
||||
const grid = document.getElementById("intel-signals-grid");
|
||||
const empty = document.getElementById("intel-signals-empty");
|
||||
grid.classList.remove("content-loading");
|
||||
|
||||
if (!data || data.length === 0) {
|
||||
grid.innerHTML = "";
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
async function loadStatsPage() {
|
||||
const data = await api("/admin/api/stats");
|
||||
|
||||
document.getElementById("sourceTable").classList.remove("content-loading");
|
||||
document.getElementById("statusTable").classList.remove("content-loading");
|
||||
|
||||
document.getElementById("sourceTable").innerHTML = data.bySource
|
||||
.map(r => `<tr><td>${escapeHtml(r.source)}</td><td style="text-align:right; padding-left:24px">${r.n.toLocaleString()}</td></tr>`).join("");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user