diff --git a/tests/ui/offline.spec.ts b/tests/ui/offline.spec.ts index 554ac1c..5b37e0e 100644 --- a/tests/ui/offline.spec.ts +++ b/tests/ui/offline.spec.ts @@ -147,16 +147,21 @@ test('an episode kept on the device plays from it offline, and its place waits t }; await open(); const keep = page.locator('#files [data-a="offline"]'); + const row = page.locator('.ep', { hasText: 'Listen on a Plane' }); await expect(keep).toHaveAttribute('aria-pressed', 'false'); + await expect(row.locator('.kept')).toHaveCount(0); await keep.click(); await expect(keep).toHaveAttribute('aria-pressed', 'true', { timeout: 15_000 }); expect(await cacheSize(page, 'ipx-episodes')).toBe(1); + // Its row in the list says so too (#413). + await expect(row.locator('.kept')).toHaveText('On this device'); await context.setOffline(true); await page.reload(); await open(); // Still kept, as the page reads it from the cache at start. await expect(keep).toHaveAttribute('aria-pressed', 'true'); + await expect(row.locator('.kept')).toBeVisible(); await page.locator('#files [data-a="play"]').click(); await expect.poll(() => page.evaluate(() => audio.currentTime), { timeout: 15_000 }).toBeGreaterThan(0.3); expect(await page.evaluate(() => audio.currentSrc)).toContain('?offline'); @@ -178,6 +183,7 @@ test('an episode kept on the device plays from it offline, and its place waits t await keep.click(); await expect(keep).toHaveAttribute('aria-pressed', 'false'); expect(await cacheSize(page, 'ipx-episodes')).toBe(0); + await expect(row.locator('.kept')).toHaveCount(0); }); test('a kept episode is answered in the byte ranges a player asks for', async ({ page, context }) => { diff --git a/web/CHANGELOG.md b/web/CHANGELOG.md index bb4c9a0..b3cd9bc 100644 --- a/web/CHANGELOG.md +++ b/web/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- An episode kept on this device says so in its row of the list, On this device, not only in its files (#413). - Keep an episode on this device to listen offline: the button beside Play on a downloaded episode keeps the file, without its dead air if DeadAir shortens it for you, and it then plays from the device whether you are online or not, with your place sent once the connection is back. Press it again to take it off; signing out takes everything off (#412). - iPX opens and reads without a connection: the lists it has fetched (All Subscriptions, Unread and the feeds it preloads or you opened) are kept on the device with the pictures in their unread items, up to 200 MB of them, and items read or flagged and places in an episode while offline are sent when the connection is back. Signing out clears it all (#389). - An open page, or the home-screen app, takes up a new version of iPX by itself once it reconnects after a deploy, back on the item you had open; while something is playing, a dialog is open or a search is typed, it offers a reload instead (#388). diff --git a/web/app.css b/web/app.css index dbd9e80..fa2faed 100644 --- a/web/app.css +++ b/web/app.css @@ -1072,6 +1072,8 @@ body.playing .eq i:nth-child(3){animation-delay:-.6s} .ep.read .t{color:var(--dim);font-weight:500} .ep .line{display:flex;gap:9px;align-items:center;flex-wrap:wrap;color:var(--faint);font-size:11.5px} .ep .line:empty{display:none} +.ep .kept{display:inline-flex;align-items:center;gap:4px;color:var(--dim)} +.ep .kept svg{width:1em;height:1em} .ep .fd,.ep .size,.ep .date{color:var(--dim);font-size:12.5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} .ep .file{display:flex;gap:6px;align-items:center;position:relative;color:var(--faint);font-size:11.5px} /* Under the icon, not beside it or on a line of its own: taking a line, it lifted the icon of diff --git a/web/src/items.ts b/web/src/items.ts index 4f39f78..d51ef70 100644 --- a/web/src/items.ts +++ b/web/src/items.ts @@ -193,6 +193,8 @@ function epEl(e: Entry){ others>0&&`+${others} more file${others===1?'':'s'}`, enc&&enc.deadair&&enc.deadair>0&&`${shorter(enc.deadair)}`, enc&&enc.deadair_pending&&DEAD_PENDING, + // Kept on this device to play offline (#413), which only the files pane said. + e.enclosures.some(isKept)&&`${ICON.kept}On this device`, enc&&enc.last_error&&enc.state==='error'&&`${esc(enc.last_error)}`, ].filter(Boolean).join('')} diff --git a/web/src/offline.ts b/web/src/offline.ts index f18403b..0b5ae94 100644 --- a/web/src/offline.ts +++ b/web/src/offline.ts @@ -54,7 +54,9 @@ function syncKeepButtons(){ b.innerHTML=k?ICON.kept:ICON.keep; const label=k?'Kept on this device to play offline. Remove it':'Keep on this device to play offline'; b.title=label; b.setAttribute('aria-label',label); - }); + }); // A row says whether its item is kept; redrawn only where that has changed. + for(const e of S.entries) + if(e.enclosures.some(isKept)!==!!$(`#eps .ep[data-key="${CSS.escape(entryKey(e))}"] .kept`)) swapRow(e); } /// The button for a file on the server that plays; none without a worker to play it offline. function keepBtn(x: Enclosure){