Currently Listening and the Directory open at once, or with a spinner (#248)
Both fetched on every visit and drew only when the answer came: Currently Listening took 1.3 s. fetchPage keeps their answers by address, as the item lists are kept, sharing a request already on its way; both draw from what is kept at once and again only if the refresh changed them, and show the spinner with nothing kept. They are fetched ahead with the lists when the page loads, and when the pointer or a finger reaches their row. Removing an episode from Currently Listening forgets its kept copy, which would otherwise show it again until the refresh. The preload loop takes a failed fetch as done instead of leaving an unhandled rejection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -100,13 +100,23 @@ const KIND_WORD: Record<string,string>={All:'feed',Podcasts:'podcast',Blogs:'blo
|
||||
/// categories, and a category has a page of its own, its subcategories along the top of its
|
||||
/// grid. What a feed is (Podcasts, anything with audio or video, or Blogs, the rest) holds across
|
||||
/// every page, and the search box looks through the lot. One fetch draws all of it.
|
||||
async function renderDirectory(url: string,box: HTMLElement){
|
||||
async function renderDirectory(url: string,box: HTMLElement,counted: (n: number)=>void){
|
||||
// Each visit starts at the front, as going back to a tab does.
|
||||
dirCat=null; dirSub=null; dirShow=null;
|
||||
dirError='';
|
||||
try{ dirRows=await api(url)||[]; }catch(e){ dirRows=[]; dirError=why(e); }
|
||||
// Drawn at once from what was kept or fetched ahead, then again only if the catalogue changed
|
||||
// (#248); with nothing kept, a spinner until it comes.
|
||||
const kept=pages.get(url);
|
||||
if(kept){ dirRows=kept; drawDirectory(box); counted(dirRows.length); }
|
||||
else listLoading(box);
|
||||
try{
|
||||
const fresh=await fetchPage(url)||[];
|
||||
if(kept&&JSON.stringify(kept)===JSON.stringify(fresh)) return;
|
||||
if(!box.isConnected) return;
|
||||
dirRows=fresh;
|
||||
}catch(e){ if(kept) return; dirRows=[]; dirError=why(e); }
|
||||
drawDirectory(box);
|
||||
return dirRows.length;
|
||||
counted(dirRows.length);
|
||||
}
|
||||
|
||||
/// `focus` is the control to put the keyboard back on, since the whole page is redrawn.
|
||||
@@ -272,10 +282,9 @@ async function renderListed(v: {title: string, icon: string, url?: string, blurb
|
||||
syncTools(null);
|
||||
if(v===VIEWS[':directory']){
|
||||
$<HTMLInputElement>('#epSearch').placeholder='Find a feed…';
|
||||
box.innerHTML='<div id="directory"><p class="hint">Loading…</p></div>';
|
||||
box.innerHTML='<div id="directory"></div>';
|
||||
$('#count').textContent=v.title;
|
||||
const n=await renderDirectory(v.url!,$('#directory'));
|
||||
if(VIEWS[S.feed ?? '']===v) $('#count').textContent=`${v.title}: ${plural(n,'feed')}`;
|
||||
await renderDirectory(v.url!,$('#directory'),n=>{ if(VIEWS[S.feed ?? '']===v) $('#count').textContent=`${v.title}: ${plural(n,'feed')}`; });
|
||||
return;
|
||||
}
|
||||
$<HTMLInputElement>('#epSearch').placeholder='Search items…';
|
||||
@@ -284,19 +293,30 @@ async function renderListed(v: {title: string, icon: string, url?: string, blurb
|
||||
<div class="art">${v.icon}</div>
|
||||
<div class="meta"><h2>${v.title}</h2><div class="sub">${v.blurb}</div></div>
|
||||
</div>
|
||||
<div class="childlist" id="listening"><p class="hint">Loading…</p></div>`;
|
||||
<div class="childlist" id="listening"></div>`;
|
||||
$('#count').textContent=v.title;
|
||||
// The search box looks through these too, title and text, as it does a feed's items (#127).
|
||||
const n=await renderListening(S.q?`${v.url}&q=${encodeURIComponent(S.q)}`:v.url!,$('#listening',box));
|
||||
if(VIEWS[S.feed ?? '']===v) $('#count').textContent=`${v.title}: ${plural(n,'episode')}`;
|
||||
await renderListening(S.q?`${v.url}&q=${encodeURIComponent(S.q)}`:v.url!,$('#listening',box),
|
||||
n=>{ if(VIEWS[S.feed ?? '']===v) $('#count').textContent=`${v.title}: ${plural(n,'episode')}`; });
|
||||
}
|
||||
|
||||
/// Currently Listening: episodes you started and have not finished, across every feed you
|
||||
/// subscribe to. A row resumes the episode in the player bar on click -- a shortcut back to
|
||||
/// where you left off, not another way to browse. The one in the player pauses instead.
|
||||
async function renderListening(url: string,box: HTMLElement){
|
||||
let rows: Entry[]=[];
|
||||
try{ rows=(await api(url)).entries||[]; }catch{}
|
||||
async function renderListening(url: string,box: HTMLElement,counted: (n: number)=>void){
|
||||
// Drawn at once from what was kept or fetched ahead, then again only if it changed (#248): it
|
||||
// took over a second to answer. With nothing kept, a spinner until it comes.
|
||||
const kept: {entries?: Entry[]}|undefined=pages.get(url);
|
||||
if(kept){ drawListening(box,kept.entries||[]); counted((kept.entries||[]).length); }
|
||||
else listLoading(box);
|
||||
let fresh: {entries?: Entry[]};
|
||||
try{ fresh=await fetchPage(url); }catch{ if(kept) return; fresh={}; }
|
||||
if(kept&&JSON.stringify(kept)===JSON.stringify(fresh)) return;
|
||||
if(!box.isConnected) return;
|
||||
drawListening(box,fresh.entries||[]);
|
||||
counted((fresh.entries||[]).length);
|
||||
}
|
||||
function drawListening(box: HTMLElement,rows: Entry[]){
|
||||
box.innerHTML=rows.length?'':S.q?`<p class="hint">Nothing you are listening to mentions “${esc(S.q)}”.</p>`
|
||||
:'<p class="hint">Nothing in progress. Episodes you start and do not finish show up here.</p>';
|
||||
for(const e of rows){
|
||||
@@ -315,7 +335,6 @@ async function renderListening(url: string,box: HTMLElement){
|
||||
paintListenRow(el);
|
||||
box.appendChild(el);
|
||||
}
|
||||
return rows.length;
|
||||
}
|
||||
|
||||
/// One row's time left, progress and play button, taken from the player when it is the one in it.
|
||||
@@ -348,6 +367,8 @@ async function forget(e: Entry){
|
||||
await api(`/api/entries/${encodeURIComponent(e.feed_id)}/${encodeURIComponent(e.guid)}/position`,
|
||||
{method:'POST',body:JSON.stringify({secs:0})});
|
||||
}catch(err){ toast(why(err),true); }
|
||||
// The kept list still has it; drawn from that, it would show again until the refresh came.
|
||||
for(const k of [...pages.keys()]) if(k.startsWith(VIEWS[':listening'].url!)) pages.delete(k);
|
||||
if(S.feed===':listening') renderListed(VIEWS[':listening']);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user