mobile ux

This commit is contained in:
Mateusz Gruszczyński
2026-05-08 18:41:22 +02:00
parent d5d41bcec2
commit 80cea1dbc3
3 changed files with 66 additions and 6 deletions

View File

@@ -21,6 +21,7 @@
let titleSpeedEnabled = !!Number(window.PYTORRENT?.titleSpeedEnabled || 0);
let trackerFaviconsEnabled = !!Number(window.PYTORRENT?.trackerFaviconsEnabled || 0);
let trackerSummary = {hashes:{}, trackers:[], scanned:0, errors:[]};
let trackerSummaryStatus = 'idle';
let trackerSummarySignature = "";
let trackerSummaryTimer = null;
const BASE_TITLE = document.title || "pyTorrent";
@@ -240,13 +241,22 @@
const src=`https://${encodeURIComponent(domain).replace(/%2E/g,'.')}/favicon.ico`;
return `<img class="tracker-favicon" src="${esc(src)}" alt="" loading="lazy" onerror="this.classList.add('d-none')"><i class="fa-solid fa-bullseye tracker-fallback-icon"></i>`;
}
function trackerFilterPlaceholder(){
if(trackerSummaryStatus==='loading') return '<div class="tracker-filter-empty"><span class="spinner-border spinner-border-xs"></span> Loading trackers...</div>';
if(trackerSummaryStatus==='error') return '<div class="tracker-filter-empty text-warning"><i class="fa-solid fa-triangle-exclamation"></i> Tracker list unavailable</div>';
if(hasTorrentSnapshot && torrents.size) return '<div class="tracker-filter-empty">No trackers found</div>';
return '<div class="tracker-filter-empty">Waiting for torrents...</div>';
}
function renderTrackerFilters(){
const box=$('trackerFilters');
if(!box) return;
const trackers=trackerSummary.trackers || [];
if(activeFilter.startsWith('tracker:') && !trackers.some(t=>t.domain===activeFilter.slice(8))) activeFilter='all';
// Note: Tracker filters are appended below status and label filters, using read-only tracker summary data.
box.innerHTML=trackers.length?`<div class="small text-muted px-2 mb-1">Trackers</div>${trackers.map(t=>`<button class="filter tracker-filter ${activeFilter==='tracker:'+t.domain?'active':''}" data-filter="tracker:${esc(t.domain)}"><span>${trackerFavicon(t.domain)} ${esc(t.domain)}</span><span>${esc(t.count||0)}</span></button>`).join('')}`:'';
// Note: Tracker filter section is always visible, so an empty or failed tracker scan does not look like a missing feature.
const rows=trackers.length
? trackers.map(t=>`<button class="filter tracker-filter ${activeFilter==='tracker:'+t.domain?'active':''}" data-filter="tracker:${esc(t.domain)}"><span>${trackerFavicon(t.domain)} ${esc(t.domain)}</span><span>${esc(t.count||0)}</span></button>`).join('')
: trackerFilterPlaceholder();
box.innerHTML=`<div class="small text-muted px-2 mb-1">Trackers</div>${rows}`;
bindSidebarFilterClicks(box);
}
async function refreshTrackerSummary(force=false){
@@ -254,14 +264,20 @@
const sig=`${hashes.length}:${hashes.slice(0,2000).join(',')}:${trackerFaviconsEnabled?1:0}`;
if(!force && sig===trackerSummarySignature) return;
trackerSummarySignature=sig;
if(!hashes.length){ trackerSummary={hashes:{},trackers:[],scanned:0,errors:[]}; renderTrackerFilters(); return; }
if(!hashes.length){ trackerSummary={hashes:{},trackers:[],scanned:0,errors:[]}; trackerSummaryStatus='empty'; renderTrackerFilters(); return; }
trackerSummaryStatus='loading';
renderTrackerFilters();
try{
const j=await (await fetch(`/api/trackers/summary?limit=2000`)).json();
const qs=new URLSearchParams({limit:'2000'});
// Note: Browser sends currently visible torrent hashes, avoiding an empty cache race on the backend.
hashes.slice(0,2000).forEach(h=>qs.append('hash',h));
const j=await (await fetch(`/api/trackers/summary?${qs.toString()}`)).json();
if(!j.ok) throw new Error(j.error||'Tracker summary failed');
trackerSummary=j.summary||{hashes:{},trackers:[],scanned:0,errors:[]};
trackerSummaryStatus=(trackerSummary.trackers||[]).length?'ready':'empty';
renderTrackerFilters();
scheduleRender(true);
}catch(e){ console.warn('Tracker summary failed', e); }
}catch(e){ trackerSummaryStatus='error'; renderTrackerFilters(); console.warn('Tracker summary failed', e); }
}
function scheduleTrackerSummary(force=false){
clearTimeout(trackerSummaryTimer);