diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bdd584..1ea5264 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,10 +37,10 @@ The long form, with what was wrong before and how it was found, is in Download latest dialogs (save, download, cancel). The icons are Font Awesome Free, embedded as SVG: only the ones used, no font to download, and nothing fetched from anyone else. They replace font characters such as ⟳ ⤓ ↗, which came out thin and tiny and differed from font to - font. Keep is a flag everywhere, as it was in the original. -- A file's state and type are icons: a check when it is downloaded, a warning when it failed - (the error is in the tooltip), and nothing while it waits; audio, video, image, PDF, torrent - and other files each have their own. The DOWNLOADED and PENDING labels are gone. + font. Keep is a flag everywhere, as it was in the original, and mark unread is an envelope. +- A file's type is an icon (audio, video, image, PDF, torrent, other), green once it is + downloaded and red when the download failed, with the details in its tooltip. One icon per + row keeps the column lined up. The DOWNLOADED and PENDING labels are gone. ### Security diff --git a/tests/ui/app.spec.js b/tests/ui/app.spec.js index 03ca1b9..bfd5ef7 100644 --- a/tests/ui/app.spec.js +++ b/tests/ui/app.spec.js @@ -87,7 +87,7 @@ test('the three panes are there and the item text lands in the bottom one', asyn // Only the downloaded one gets a player, and max_new_per_check is 1, so find it by // its chip rather than assuming which episode the daemon happened to fetch. - const downloaded = page.locator('.ep', { has: page.locator('[title="Downloaded"]') }).first(); + const downloaded = page.locator('.ep', { has: page.locator('.kind.here') }).first(); await downloaded.click(); await expect(page.locator('#files audio')).toBeVisible(); await expect(page.locator('#files .encbox [title="Save to this computer"]')).toBeVisible(); @@ -110,9 +110,8 @@ test('a downloaded file that is not audio gets no player', async ({ page }) => { await expect(page.locator('#detail .dt')).toHaveText('An Article'); await expect(page.locator('#files audio')).toHaveCount(0); - // What it is and that it is here, as icons with the words in their tooltips. - await expect(page.locator('#files .encbox [title="image"]')).toBeVisible(); - await expect(page.locator('#files .encbox [title="Downloaded"]')).toBeVisible(); + // What it is and that it is here: one icon, green, with the words in its tooltip. + await expect(page.locator('#files .encbox .kind.here')).toHaveAttribute('title', 'image, downloaded'); // Still offered as a file, just not as an episode: viewable and keepable. await expect(page.locator('#files [title="Save to this computer"]')).toBeVisible(); const view = page.locator('#files a[title="View in a new tab"]'); @@ -131,7 +130,7 @@ test('an item with several enclosures lists them all', async ({ page }) => { await row.click(); // The Files pane lists every one: the audio and the image. await expect(page.locator('#files .encbox')).toHaveCount(2); - await expect(page.locator('#files .encbox').nth(1).locator('[title="image"]')).toBeVisible(); + await expect(page.locator('#files .encbox').nth(1).locator('.kind[title^="image"]')).toBeVisible(); }); test('the filter tabs change what is listed', async ({ page }) => { @@ -674,7 +673,7 @@ test('a deleted file looks as if it was never downloaded', async ({ page }) => { await page.locator('#files button[data-a="del"]').click(); // No "reaped", no chip at all: just the way to get it again. - await expect(row.locator('[title="Downloaded"]')).toHaveCount(0); + await expect(row.locator('.kind.here')).toHaveCount(0); await expect(row).not.toContainText(/reaped/i); await row.click(); await expect(page.locator('#files')).not.toContainText(/reaped/i); diff --git a/web/index.html b/web/index.html index 467e734..bb9acea 100644 --- a/web/index.html +++ b/web/index.html @@ -238,11 +238,11 @@ a.btn{text-decoration:none;color:inherit} .btn.ico{padding:4px 9px;min-width:32px;font-size:14px;line-height:1.25;text-align:center} /* An icon (Font Awesome, embedded as SVG) in the button's own colour. */ .i{display:inline-block;width:16px;height:16px;vertical-align:-3px;flex:none;fill:currentColor} -/* A file's state and type, as icons in place of the old DOWNLOADED / PENDING / audio chips. */ -.fst,.kind{display:inline-grid;place-items:center} -.fst{color:var(--good)} -.fst.bad{color:var(--bad)} -.kind{color:var(--dim)} +/* A file's type as an icon, in place of the old DOWNLOADED / PENDING / audio chips: green once + it is here, red when the download failed. */ +.kind{display:inline-grid;place-items:center;color:var(--dim)} +.kind.here{color:var(--good)} +.kind.bad{color:var(--bad)} .fhead .art .i{width:22px;height:22px} .toolbar{ display:flex;gap:10px;align-items:center;margin-bottom:12px;flex-wrap:wrap; @@ -503,8 +503,10 @@ input[type=range]::-moz-range-thumb{width:12px;height:12px;border:0;border-radiu :root[data-theme="classic"] .ep.sel .st, :root[data-theme="classic"] .ep.sel .fl, :root[data-theme="classic"] .ep.sel .file, -:root[data-theme="classic"] .ep.sel .fst, :root[data-theme="classic"] .ep.sel .kind{color:#fff} +/* Green and red stay green and red on the blue, just lighter so they read. */ +:root[data-theme="classic"] .ep.sel .kind.here{color:#a6f3a6} +:root[data-theme="classic"] .ep.sel .kind.bad{color:#ffb8ad} /* Lists were white in the original; the pale blue-grey belongs to the source list alone. */ :root[data-theme="classic"] .childrow{background:#fff} :root[data-theme="classic"] .dt{background:linear-gradient(#80aae6,#3f78cf);color:#fff;padding:6px 12px;border-radius:4px} @@ -616,9 +618,7 @@ const ICON={ directory:fa('0 0 448 512',''), // solid/table-list popular:fa('0 0 576 512',''), // solid/star all:fa('0 0 512 512',''), // solid/layer-group - dot:fa('0 0 512 512',''), // solid/circle - done:fa('0 0 512 512',''), // solid/circle-check - alert:fa('0 0 512 512',''), // solid/circle-exclamation + unread:fa('0 0 512 512',''), // solid/envelope: a closed letter, not a record button audio:fa('0 0 448 512',''), // solid/headphones video:fa('0 0 576 512',''), // solid/video image:fa('0 0 448 512',''), // solid/image @@ -973,7 +973,7 @@ function epEl(e){ ${esc(feedName(e.feed_id))}
- ${enc?stateIcon(enc)+kindIcon(enc):''} + ${enc?kindIcon(enc):''} ${enc&&enc.length?`${mb(enc.length)}`:''} ${enc&&!has?`
`:''}
@@ -1013,20 +1013,16 @@ function kindOf(enc){ return (ext && ext.length<=5) ? ext.toLowerCase() : 'file'; } -/// What a file is, as an icon; the word is in its tooltip. +/// What a file is, as one icon coloured by whether it is here: green once downloaded, red when +/// the download failed, plain otherwise, so a file waiting and one deleted read alike. One icon +/// either way keeps the column lined up; the words are in its tooltip. function kindIcon(enc){ const k=kindOf(enc); const i={audio:ICON.audio,video:ICON.video,image:ICON.image,pdf:ICON.doc,torrent:ICON.torrent}[k]||ICON.file; - return `${i}`; -} - -/// Whether a file is here, as an icon: a check when downloaded, a warning when it failed, and -/// nothing otherwise, so a file waiting to download and one deleted to save space read alike. -function stateIcon(enc){ - if(enc.path) return `${ICON.done}`; - if(enc.state!=='error') return ''; - const why=esc(enc.last_error||'Download failed'); - return `${ICON.alert}`; + const [cls,label]=enc.path ? [' here',`${k}, downloaded`] + : enc.state==='error' ? [' bad',`${k}, download failed${enc.last_error?': '+enc.last_error:''}`] + : ['',k]; + return `${i}`; } function feedArt(id=S.feed){ const f=S.feeds.find(x=>x.id===id); return f&&f.image; } @@ -1118,7 +1114,7 @@ function showDetail(e){ ${dateOf(e.published)} ${e.duration?`${clock(e.duration)}`:''} + aria-label="Mark ${e.read?'unread':'read'}">${e.read?ICON.unread:ICON.check} ${e.link?` - ${kindIcon(x)}${stateIcon(x)} + ${kindIcon(x)} ${size} ${ICON.open} ${saveBtn} @@ -1172,7 +1168,7 @@ function encBox(x){ // through here, which would make ipx a fetch-anything relay. const viewable = !isPlayable(x) && x.state !== 'pending'; return `
- ${kindIcon(x)}${stateIcon(x)} + ${kindIcon(x)} ${size} ${x.state==='error'&&x.last_error?`${esc(x.last_error)}`:''} ${viewable?`${ICON.open}`:''}