diff --git a/js/app.js b/js/app.js index 98b97656..e379a8d6 100644 --- a/js/app.js +++ b/js/app.js @@ -3688,13 +3688,25 @@ class bitvidApp { delete cardEl.dataset.ownerPubkey; } - cardEl.dataset.urlHealthState = trimmedUrl ? "checking" : "absent"; + if (trimmedUrl) { + cardEl.dataset.urlHealthState = "checking"; + if (cardEl.dataset.urlHealthReason) { + delete cardEl.dataset.urlHealthReason; + } + } else { + cardEl.dataset.urlHealthState = "offline"; + cardEl.dataset.urlHealthReason = "missing-source"; + } if (magnetProvided && magnetSupported) { cardEl.dataset.streamHealthState = "checking"; - } else if (magnetProvided) { - cardEl.dataset.streamHealthState = "unsupported"; + if (cardEl.dataset.streamHealthReason) { + delete cardEl.dataset.streamHealthReason; + } } else { - cardEl.dataset.streamHealthState = "absent"; + cardEl.dataset.streamHealthState = "unhealthy"; + cardEl.dataset.streamHealthReason = magnetProvided + ? "unsupported" + : "missing-source"; } const thumbnailEl = cardEl.querySelector("[data-video-thumbnail]"); diff --git a/js/channelProfile.js b/js/channelProfile.js index abeb30bd..c9a08660 100644 --- a/js/channelProfile.js +++ b/js/channelProfile.js @@ -440,13 +440,25 @@ async function loadUserVideos(pubkey) { delete cardEl.dataset.torrentSupported; } - cardEl.dataset.urlHealthState = trimmedUrl ? "checking" : "absent"; + if (trimmedUrl) { + cardEl.dataset.urlHealthState = "checking"; + if (cardEl.dataset.urlHealthReason) { + delete cardEl.dataset.urlHealthReason; + } + } else { + cardEl.dataset.urlHealthState = "offline"; + cardEl.dataset.urlHealthReason = "missing-source"; + } if (magnetProvided && magnetSupported) { cardEl.dataset.streamHealthState = "checking"; - } else if (magnetProvided) { - cardEl.dataset.streamHealthState = "unsupported"; + if (cardEl.dataset.streamHealthReason) { + delete cardEl.dataset.streamHealthReason; + } } else { - cardEl.dataset.streamHealthState = "absent"; + cardEl.dataset.streamHealthState = "unhealthy"; + cardEl.dataset.streamHealthReason = magnetProvided + ? "unsupported" + : "missing-source"; } if (magnetProvided) { diff --git a/js/gridHealth.js b/js/gridHealth.js index c1185a73..997ce041 100644 --- a/js/gridHealth.js +++ b/js/gridHealth.js @@ -358,10 +358,37 @@ function getIntersectionRect(entry) { } function setBadge(card, state, details) { + if (!(card instanceof HTMLElement)) { + return; + } + + const normalizedState = + typeof state === "string" && state ? state : "unknown"; + const peersValue = + details && Number.isFinite(details.peers) + ? Math.max(0, Number(details.peers)) + : 0; + const hasPeerCount = details ? Number.isFinite(details.peers) : false; + const peersTextValue = hasPeerCount ? String(peersValue) : ""; + + card.dataset.streamHealthState = normalizedState; + if (hasPeerCount) { + card.dataset.streamHealthPeers = peersTextValue; + } else if (card.dataset.streamHealthPeers) { + delete card.dataset.streamHealthPeers; + } + + if (details && typeof details.reason === "string" && details.reason) { + card.dataset.streamHealthReason = details.reason; + } else if (card.dataset.streamHealthReason) { + delete card.dataset.streamHealthReason; + } + const badge = card.querySelector(".torrent-health-badge"); if (!badge) { return; } + const hadMargin = badge.classList.contains("mt-3"); const baseClasses = [ @@ -409,22 +436,19 @@ function setBadge(card, state, details) { }, }; - const entry = map[state] || map.unknown; + const entry = map[normalizedState] || map.unknown; entry.classes.forEach((cls) => badge.classList.add(cls)); - const peers = - details && Number.isFinite(details.peers) - ? Math.max(0, Number(details.peers)) - : 0; - const peersText = state === "healthy" && peers > 0 ? ` (${peers})` : ""; + const peersText = + normalizedState === "healthy" && peersValue > 0 ? ` (${peersValue})` : ""; const iconPrefix = entry.icon ? `${entry.icon} ` : ""; badge.textContent = `${iconPrefix}WebTorrent${peersText}`; const tooltip = - state === "checking" || state === "unknown" + normalizedState === "checking" || normalizedState === "unknown" ? entry.aria : buildTooltip({ - peers, + peers: peersValue, checkedAt: details?.checkedAt, reason: details?.reason, }); @@ -432,24 +456,12 @@ function setBadge(card, state, details) { badge.setAttribute("title", tooltip); badge.setAttribute("aria-live", entry.role === "alert" ? "assertive" : "polite"); badge.setAttribute("role", entry.role); - badge.dataset.streamHealthState = state; - const hasPeerCount = Number.isFinite(peers); - const peersTextValue = hasPeerCount ? String(peers) : ""; + badge.dataset.streamHealthState = normalizedState; if (hasPeerCount) { badge.dataset.streamHealthPeers = peersTextValue; } else if (badge.dataset.streamHealthPeers) { delete badge.dataset.streamHealthPeers; } - - const cardEl = badge.closest(".video-card"); - if (cardEl) { - cardEl.dataset.streamHealthState = state; - if (hasPeerCount) { - cardEl.dataset.streamHealthPeers = peersTextValue; - } else if (cardEl.dataset.streamHealthPeers) { - delete cardEl.dataset.streamHealthPeers; - } - } } function handleCardVisible({ card, pendingByCard, priority = 0 }) { @@ -458,13 +470,13 @@ function handleCardVisible({ card, pendingByCard, priority = 0 }) { } const magnet = card.dataset.magnet || ""; if (!magnet) { - setBadge(card, "unknown"); + setBadge(card, "unhealthy", { reason: "missing-source" }); return; } const infoHash = infoHashFromMagnet(magnet); if (!infoHash) { - setBadge(card, "unknown"); + setBadge(card, "unhealthy", { reason: "invalid" }); return; } @@ -535,7 +547,7 @@ export function attachHealthBadges(container) { state.observedCards.add(card); state.observer.observe(card); if (!card.dataset.magnet) { - setBadge(card, "unknown"); + setBadge(card, "unhealthy", { reason: "missing-source" }); } }); processObserverEntries(state.observer.takeRecords(), state); diff --git a/js/subscriptions.js b/js/subscriptions.js index 216d1ab3..9a55fef9 100644 --- a/js/subscriptions.js +++ b/js/subscriptions.js @@ -474,13 +474,25 @@ class SubscriptionsManager { delete cardEl.dataset.ownerPubkey; } - cardEl.dataset.urlHealthState = trimmedUrl ? "checking" : "absent"; + if (trimmedUrl) { + cardEl.dataset.urlHealthState = "checking"; + if (cardEl.dataset.urlHealthReason) { + delete cardEl.dataset.urlHealthReason; + } + } else { + cardEl.dataset.urlHealthState = "offline"; + cardEl.dataset.urlHealthReason = "missing-source"; + } if (magnetProvided && magnetSupported) { cardEl.dataset.streamHealthState = "checking"; - } else if (magnetProvided) { - cardEl.dataset.streamHealthState = "unsupported"; + if (cardEl.dataset.streamHealthReason) { + delete cardEl.dataset.streamHealthReason; + } } else { - cardEl.dataset.streamHealthState = "absent"; + cardEl.dataset.streamHealthState = "unhealthy"; + cardEl.dataset.streamHealthReason = magnetProvided + ? "unsupported" + : "missing-source"; } // Leave the data-play-* attributes empty in the literal markup so we can