mobile ux

This commit is contained in:
Mateusz Gruszczyński
2026-05-08 19:00:26 +02:00
parent 80cea1dbc3
commit f245f082d5
5 changed files with 354 additions and 34 deletions

View File

@@ -238,12 +238,14 @@
}
function trackerFavicon(domain){
if(!trackerFaviconsEnabled || !domain) return '<i class="fa-solid fa-bullseye"></i>';
const src=`https://${encodeURIComponent(domain).replace(/%2E/g,'.')}/favicon.ico`;
// Note: Favicony trackerów idą przez lokalny cache backendu, więc przeglądarka nie odpytuje tysięcy domen bezpośrednio.
const src=`/api/trackers/favicon/${encodeURIComponent(domain)}`;
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(Number(trackerSummary.pending||0)) return `<div class="tracker-filter-empty"><span class="spinner-border spinner-border-xs"></span> Scanning cache: ${esc(trackerSummary.cached||0)}/${esc(trackerSummary.scanned||0)}</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>';
}
@@ -261,22 +263,24 @@
}
async function refreshTrackerSummary(force=false){
const hashes=[...torrents.keys()].sort();
const sig=`${hashes.length}:${hashes.slice(0,2000).join(',')}:${trackerFaviconsEnabled?1:0}`;
if(!force && sig===trackerSummarySignature) return;
const sig=`${hashes.length}:${hashes[0]||''}:${hashes[hashes.length-1]||''}:${trackerFaviconsEnabled?1:0}`;
if(!force && sig===trackerSummarySignature && !Number(trackerSummary.pending||0)) return;
trackerSummarySignature=sig;
if(!hashes.length){ trackerSummary={hashes:{},trackers:[],scanned:0,errors:[]}; trackerSummaryStatus='empty'; renderTrackerFilters(); return; }
trackerSummaryStatus='loading';
if(!hashes.length){ trackerSummary={hashes:{},trackers:[],scanned:0,errors:[],pending:0,cached:0}; trackerSummaryStatus='empty'; renderTrackerFilters(); return; }
trackerSummaryStatus=(trackerSummary.trackers||[]).length?'ready':'loading';
renderTrackerFilters();
try{
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';
// Note: Nie wysyłamy 13k hashy w URL; backend bierze lokalny snapshot i doczytuje cache małymi porcjami.
const j=await (await fetch('/api/trackers/summary?scan_limit=200')).json();
if(!j.ok && !j.summary) throw new Error(j.error||'Tracker summary failed');
trackerSummary=j.summary||{hashes:{},trackers:[],scanned:0,errors:[],pending:0,cached:0};
trackerSummaryStatus=(trackerSummary.trackers||[]).length?'ready':Number(trackerSummary.pending||0)?'loading':'empty';
renderTrackerFilters();
scheduleRender(true);
if(Number(trackerSummary.pending||0)>0){
clearTimeout(trackerSummaryTimer);
trackerSummaryTimer=setTimeout(()=>refreshTrackerSummary(true).catch(()=>{}), 3500);
}
}catch(e){ trackerSummaryStatus='error'; renderTrackerFilters(); console.warn('Tracker summary failed', e); }
}
function scheduleTrackerSummary(force=false){

View File

@@ -223,8 +223,9 @@ body {
display: grid;
grid-template-columns: var(--sidebar) 1fr;
}
/* Note: Sidebar filters are denser so large tracker lists fit better on one screen. */
.sidebar {
padding: 0.65rem;
padding: 0.5rem;
overflow: auto;
background: rgba(var(--bs-secondary-bg-rgb), 0.9);
}
@@ -232,10 +233,10 @@ body {
width: 100%;
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: 0.15rem 0.55rem;
gap: 0.1rem 0.45rem;
align-items: center;
margin-bottom: 0.2rem;
padding: 0.45rem 0.6rem;
margin-bottom: 0.12rem;
padding: 0.34rem 0.5rem;
border: 0;
border-radius: 0.55rem;
background: transparent;
@@ -863,9 +864,9 @@ body.mobile-mode .main-grid {
}
.label-filters .label-filter,
.tracker-filters .tracker-filter {
font-size: 0.82rem;
padding: 0.34rem 0.5rem;
margin-bottom: 0.15rem;
font-size: 0.78rem;
margin-bottom: 0.08rem;
padding: 0.26rem 0.44rem;
}
.label-filters .label-filter i,
.tracker-filters .tracker-filter i {
@@ -883,9 +884,9 @@ body.mobile-mode .main-grid {
.tracker-favicon {
border-radius: 0.2rem;
flex: 0 0 auto;
height: 16px;
height: 14px;
object-fit: contain;
width: 16px;
width: 14px;
}
.tracker-favicon:not(.d-none) + .tracker-fallback-icon {
@@ -896,9 +897,9 @@ body.mobile-mode .main-grid {
align-items: center;
color: var(--bs-secondary-color);
display: flex;
font-size: 0.78rem;
gap: 0.35rem;
padding: 0.25rem 0.5rem;
font-size: 0.76rem;
gap: 0.3rem;
padding: 0.2rem 0.44rem;
}
/* Note: Empty tracker state uses the same sidebar spacing as regular filter rows. */