diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c688e7..b865a32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- Pulling the item list down to check for new items shows a spinner for a couple of seconds, and + a second pull meanwhile does nothing, instead of no sign at all that the check started. - Settings no longer lists the server's download folder, which only an admin can change, on the admin page. - On the Unread tab, a swipe back (or k) goes to the item you just read, instead of past it or diff --git a/tests/ui/app.spec.js b/tests/ui/app.spec.js index 92ee4f2..f6dbda6 100644 --- a/tests/ui/app.spec.js +++ b/tests/ui/app.spec.js @@ -1325,6 +1325,15 @@ test.describe('touch gestures on a phone', () => { await drag(page, { x: 200, y: box.y + 20 }, { x: 200, y: box.y + 220 }); expect((await fetch).postDataJSON()).toEqual({ feed: 'test-show', force: true }); await expect(page.locator('#pulltip')).toHaveCount(0); // the note goes on letting go + // and a spinner says the check started, for a couple of seconds. + await expect(page.locator('#pullspin')).toBeVisible(); + // A second pull while it is up checks nothing more. + let again = 0; + page.on('request', r => { if (r.url().endsWith('/api/fetch')) again++; }); + await drag(page, { x: 200, y: box.y + 20 }, { x: 200, y: box.y + 220 }); + await expect(page.locator('#pullspin')).toHaveCount(1); + await expect(page.locator('#pullspin')).toHaveCount(0, { timeout: 5_000 }); + expect(again).toBe(0); }); }); diff --git a/web/app.css b/web/app.css index 178096e..2284605 100644 --- a/web/app.css +++ b/web/app.css @@ -689,6 +689,12 @@ input:focus,select:focus{outline:0;border-color:var(--accent)} reloads the page, from starting on top of it. */ #pulltip{display:flex;align-items:flex-end;justify-content:center;padding-bottom:6px;overflow:hidden; font-size:12px;color:var(--faint)} +/* What a pull started, for a couple of seconds after letting go, under the top bar. */ +#pullspin{position:fixed;left:50%;top:calc(58px + var(--safe-t));transform:translateX(-50%);z-index:55; + display:flex;align-items:center;gap:8px;padding:6px 12px;border-radius:999px;font-size:12px; + color:var(--dim);background:var(--panel);border:1px solid var(--line);box-shadow:var(--shadow)} +#pullspin::before{content:"";width:12px;height:12px;border-radius:50%; + border:2px solid var(--line);border-top-color:var(--accent);animation:ipxspin .8s linear infinite} /* The selected item's files, beside the list, as the original's Files pane was. */ #files{grid-area:files;overflow-y:auto;padding:8px 12px;border-left:1px solid var(--line);background:var(--panel)} /* Nothing selected, or an item with no files: the list takes the width. */ diff --git a/web/src/gestures.ts b/web/src/gestures.ts index a53fab3..d8c7a29 100644 --- a/web/src/gestures.ts +++ b/web/src/gestures.ts @@ -36,13 +36,25 @@ function pullShow(dy: number){ tip.textContent = dy >= PULL ? 'Release to check for new items' : 'Pull to check for new items'; } +/// A check started by a pull, while its spinner is up. Letting go showed nothing before (issue +/// #68), the sidebar's spinner is hidden on a phone, and people pulled again and again. +let refreshing = false; + function refreshFeed(){ const f = S.feeds.find(x => x.id === S.feed); if(!f && S.feed !== ':all') return; + if(refreshing) return; + refreshing = true; + // Outside #list, which a feed's render rebuilds and would take the spinner with it. + const spin = document.createElement('div'); + spin.id = 'pullspin'; spin.setAttribute('role', 'status'); spin.textContent = 'Checking for new items'; + document.body.append(spin); // New items arrive by the event stream when the scan finishes, as they do for a button press. - api('/api/fetch', {method: 'POST', body: JSON.stringify(f ? {feed: f.id, force: true} : {force: true})}) + const asked = api('/api/fetch', {method: 'POST', body: JSON.stringify(f ? {feed: f.id, force: true} : {force: true})}) .catch(e => toast(e.message, true)); loadEntries(); + // Up for two seconds at least, so it is seen even when the server answers at once. + Promise.all([asked, new Promise(r => setTimeout(r, 2000))]).then(() => { spin.remove(); refreshing = false; }); } document.addEventListener('touchstart', ev => {