From 61d02748967aec395b87c5424934339217447415 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 30 Sep 2026 07:45:13 +0000 Subject: [PATCH] Show peer availability with stale markers and add Sharing settings --- frontend/app.js | 75 +++++++++++++++++++ frontend/index.html | 2 + frontend/p2p-core.js | 55 ++++++++++++++ frontend/p2p-core.test.js | 46 ++++++++++++ frontend/styles.css | 2 + frontend/sw.js | 1 + plans/INDEX.md | 2 +- ...015-availability-ui-and-settings-3b9397.md | 6 ++ 8 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 frontend/p2p-core.js create mode 100644 frontend/p2p-core.test.js rename plans/{active => done}/015-availability-ui-and-settings-3b9397.md (88%) diff --git a/frontend/app.js b/frontend/app.js index 595d5e2..60e1803 100755 --- a/frontend/app.js +++ b/frontend/app.js @@ -2434,10 +2434,42 @@ async function reportBroken(video) { } } +// P2P availability under the title (docs/p2p-architecture.md flow 5). +// Cached 60 s per video so the frequent updateNowPlayingActions() calls +// don't hammer the server. +const availCache = new Map(); // videoId -> { at, payload } +async function renderAvailability(id) { + const el = $('npAvail'); + if (!el) return; + const off = data.settings.p2pShare === false && data.settings.p2pReceive === false; + if (!WEB || !window.P2PCore || off || navigator.onLine === false || String(id).startsWith('edit_')) { + el.classList.add('hidden'); + return; + } + let hit = availCache.get(id); + if (!hit || Date.now() - hit.at > 60_000) { + hit = { at: Date.now(), payload: null }; + availCache.set(id, hit); + try { + const r = await fetch(`/api/p2p/holders?v=${encodeURIComponent(id)}`); + hit.payload = r.ok ? await r.json() : null; + } catch { /* offline / disabled — no line */ } + if (availCache.size > 200) availCache.delete(availCache.keys().next().value); + } + if (!current || !current.meta || current.meta.id !== id) return; // moved on meanwhile + const f = window.P2PCore.formatAvailability(hit.payload, Date.now()); + if (!f) { el.classList.add('hidden'); return; } + el.textContent = f.text; + el.title = f.title; + el.classList.toggle('stale', f.stale); + el.classList.remove('hidden'); +} + // Reflect cache state on the now-playing Save button. function updateNowPlayingActions() { if (!current || !current.meta) return; const id = current.meta.id; + renderAvailability(id); // Custom (edited) videos are already a saved-offline cut of a source video; // re-saving or re-editing them makes no sense, so hide those actions. const isCustom = !!current.meta.custom; @@ -7453,6 +7485,28 @@ async function renderSettings() { +
+
Sharing (peer-to-peer)
+ + +
+ This device… + … +
+
+
Offline cache