mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-06 12:52:26 +00:00
Merge pull request #122 from PR0M3TH3AN/codex/implement-video-visibility-logic-based-on-indicators
Hide non-owner videos when CDN and WebTorrent are offline
This commit is contained in:
5 files changed
+140
-14
No files matched your search
@@ -3,6 +3,7 @@
|
||||
--color-card: #1e293b;
|
||||
--color-primary: #fe0032;
|
||||
--color-secondary: #ff93a5;
|
||||
--color-card-critical: #3f0b13;
|
||||
--color-text: #f8fafc;
|
||||
--color-muted: #94a3b8;
|
||||
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
||||
@@ -74,6 +75,19 @@ header img {
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.video-card[data-owner-is-viewer="false"][data-url-health-state="offline"][data-stream-health-state="unhealthy"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.video-card[data-owner-is-viewer="true"][data-url-health-state="offline"][data-stream-health-state="unhealthy"] {
|
||||
background-color: var(--color-card-critical);
|
||||
box-shadow: 0 0 0 1px rgba(239, 68, 68, 0.35), var(--shadow-md);
|
||||
}
|
||||
|
||||
.video-card[data-owner-is-viewer="true"][data-url-health-state="offline"][data-stream-health-state="unhealthy"]:hover {
|
||||
box-shadow: 0 0 0 1px rgba(239, 68, 68, 0.45), var(--shadow-lg);
|
||||
}
|
||||
|
||||
.video-card--enter {
|
||||
opacity: 0;
|
||||
animation: video-card-fade-in 220ms ease-out forwards;
|
||||
|
||||
@@ -3243,6 +3243,10 @@ class bitvidApp {
|
||||
const hadMargin = badgeEl.classList.contains("mt-3");
|
||||
|
||||
badgeEl.dataset.urlHealthState = status;
|
||||
const cardEl = badgeEl.closest(".video-card");
|
||||
if (cardEl) {
|
||||
cardEl.dataset.urlHealthState = status;
|
||||
}
|
||||
badgeEl.setAttribute("aria-live", "polite");
|
||||
badgeEl.setAttribute("role", status === "offline" ? "alert" : "status");
|
||||
badgeEl.textContent = message;
|
||||
@@ -3677,6 +3681,34 @@ class bitvidApp {
|
||||
template.innerHTML = cardHtml.trim();
|
||||
const cardEl = template.content.firstElementChild;
|
||||
if (cardEl) {
|
||||
cardEl.dataset.ownerIsViewer = canEdit ? "true" : "false";
|
||||
if (typeof video.pubkey === "string" && video.pubkey) {
|
||||
cardEl.dataset.ownerPubkey = video.pubkey;
|
||||
} else if (cardEl.dataset.ownerPubkey) {
|
||||
delete cardEl.dataset.ownerPubkey;
|
||||
}
|
||||
|
||||
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";
|
||||
if (cardEl.dataset.streamHealthReason) {
|
||||
delete cardEl.dataset.streamHealthReason;
|
||||
}
|
||||
} else {
|
||||
cardEl.dataset.streamHealthState = "unhealthy";
|
||||
cardEl.dataset.streamHealthReason = magnetProvided
|
||||
? "unsupported"
|
||||
: "missing-source";
|
||||
}
|
||||
|
||||
const thumbnailEl = cardEl.querySelector("[data-video-thumbnail]");
|
||||
if (thumbnailEl) {
|
||||
const markThumbnailAsLoaded = () => {
|
||||
|
||||
@@ -360,6 +360,13 @@ async function loadUserVideos(pubkey) {
|
||||
"duration-300"
|
||||
);
|
||||
|
||||
cardEl.dataset.ownerIsViewer = canEdit ? "true" : "false";
|
||||
if (typeof video.pubkey === "string" && video.pubkey) {
|
||||
cardEl.dataset.ownerPubkey = video.pubkey;
|
||||
} else if (cardEl.dataset.ownerPubkey) {
|
||||
delete cardEl.dataset.ownerPubkey;
|
||||
}
|
||||
|
||||
const rawMagnet =
|
||||
typeof video.magnet === "string" ? video.magnet : "";
|
||||
const trimmedMagnet = rawMagnet ? rawMagnet.trim() : "";
|
||||
@@ -433,6 +440,27 @@ async function loadUserVideos(pubkey) {
|
||||
delete cardEl.dataset.torrentSupported;
|
||||
}
|
||||
|
||||
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";
|
||||
if (cardEl.dataset.streamHealthReason) {
|
||||
delete cardEl.dataset.streamHealthReason;
|
||||
}
|
||||
} else {
|
||||
cardEl.dataset.streamHealthState = "unhealthy";
|
||||
cardEl.dataset.streamHealthReason = magnetProvided
|
||||
? "unsupported"
|
||||
: "missing-source";
|
||||
}
|
||||
|
||||
if (magnetProvided) {
|
||||
cardEl.dataset.magnet = playbackMagnet;
|
||||
} else if (cardEl.dataset.magnet) {
|
||||
|
||||
+38
-14
@@ -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,9 +456,9 @@ 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;
|
||||
if (Number.isFinite(peers)) {
|
||||
badge.dataset.streamHealthPeers = String(peers);
|
||||
badge.dataset.streamHealthState = normalizedState;
|
||||
if (hasPeerCount) {
|
||||
badge.dataset.streamHealthPeers = peersTextValue;
|
||||
} else if (badge.dataset.streamHealthPeers) {
|
||||
delete badge.dataset.streamHealthPeers;
|
||||
}
|
||||
@@ -446,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;
|
||||
}
|
||||
|
||||
@@ -523,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);
|
||||
|
||||
@@ -467,6 +467,34 @@ class SubscriptionsManager {
|
||||
t.innerHTML = cardHtml.trim();
|
||||
const cardEl = t.content.firstElementChild;
|
||||
if (cardEl) {
|
||||
cardEl.dataset.ownerIsViewer = canEdit ? "true" : "false";
|
||||
if (typeof video.pubkey === "string" && video.pubkey) {
|
||||
cardEl.dataset.ownerPubkey = video.pubkey;
|
||||
} else if (cardEl.dataset.ownerPubkey) {
|
||||
delete cardEl.dataset.ownerPubkey;
|
||||
}
|
||||
|
||||
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";
|
||||
if (cardEl.dataset.streamHealthReason) {
|
||||
delete cardEl.dataset.streamHealthReason;
|
||||
}
|
||||
} else {
|
||||
cardEl.dataset.streamHealthState = "unhealthy";
|
||||
cardEl.dataset.streamHealthReason = magnetProvided
|
||||
? "unsupported"
|
||||
: "missing-source";
|
||||
}
|
||||
|
||||
// Leave the data-play-* attributes empty in the literal markup so we can
|
||||
// assign the raw URL/magnet strings post-parsing without HTML entity
|
||||
// escaping, mirroring the approach in app.js. The URL is encoded so that
|
||||
|
||||
Reference in new issue
Block a user