diff --git a/CHANGELOG.md b/CHANGELOG.md index 20a8e9f..4b78cec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,9 @@ How to use this file: ### Changed +- The note confirming the payslips added goes after five seconds: it + dissolves, then the room it took closes smoothly; not while the pointer + or the focus is on it. - In the web app the page no longer lists the files: it says only how many need a look, if any. The files are in a sheet of their own, opened from that line or from "Files read" under the report: what needs a look diff --git a/web/app.js b/web/app.js index 0b87a95..1b6e59d 100644 --- a/web/app.js +++ b/web/app.js @@ -321,9 +321,51 @@ function added(files) { let text = t('web.added.none'); if (months.length === 1) text = t('web.added.one', months[0]); if (months.length > 1) text = t('web.added.many', months.length, months.join(', ')); - $('added').textContent = text; - $('added').classList.toggle('none', months.length === 0); - $('added').hidden = false; + const note = $('added'); + note.textContent = text; + note.classList.toggle('none', months.length === 0); + note.classList.remove('fading', 'going'); + note.style.height = ''; + note.hidden = false; + fadeAdded(); +} + +// The confirmation goes after a few seconds, dissolving and then folding +// away so that the report rises smoothly under it; not while the pointer +// or the focus is on it, so that it can be read. +const ADDED_FOR = 5000; +let addedTimer = 0, addedTurn = 0; +function fadeAdded() { + clearTimeout(addedTimer); + // A note shown again ends the fading of the one before. + const turn = ++addedTurn; + addedTimer = setTimeout(() => { + const note = $('added'); + if (note.hidden) return; + if (note.matches(':hover, :focus-within')) { + fadeAdded(); + return; + } + if (calm()) { + note.hidden = true; + return; + } + // First it dissolves, then the room it took closes. + note.classList.add('fading'); + note.addEventListener('transitionend', () => { + if (turn !== addedTurn) return; + note.style.height = `${note.offsetHeight}px`; + note.getBoundingClientRect(); + note.classList.add('going'); + note.style.height = '0px'; + note.addEventListener('transitionend', () => { + if (turn !== addedTurn) return; + note.hidden = true; + note.classList.remove('fading', 'going'); + note.style.height = ''; + }, { once: true }); + }, { once: true }); + }, ADDED_FOR); } // progress shows how many files have been read, or hides the bar. @@ -347,6 +389,8 @@ async function handle(fileList) { // the progress and any password. const adding = !$('result').hidden; const before = state.files.length; + clearTimeout(addedTimer); + addedTurn++; $('added').hidden = true; if (adding) { $('pick').hidden = false; diff --git a/web/style.css b/web/style.css index 50d1062..a40ad57 100644 --- a/web/style.css +++ b/web/style.css @@ -147,6 +147,9 @@ h1 { font-size: clamp(30px, 6vw, 40px); line-height: 1.1; margin: 0 0 12px; lett .added::before { content: "✓ "; color: var(--good); font-weight: 700; } .added.none { background: var(--raised); color: var(--ink-2); } .added.none::before { content: ""; } +.added.fading { opacity: 0; transition: opacity .6s ease; } +.added.going { overflow: hidden; margin-bottom: 0; padding-top: 0; padding-bottom: 0; + transition: height .25s ease, margin .25s ease, padding .25s ease; } .misread { margin: 16px 0 0; text-align: center; } .report-tabs { display: flex; gap: 4px; margin: 0 -16px; padding: 0 16px; overflow-x: auto; scrollbar-width: none; border-top: 1px solid var(--line); } .report-tabs[hidden] { display: none; } diff --git a/web/test/e2e.test.mjs b/web/test/e2e.test.mjs index d80541a..b42c508 100644 --- a/web/test/e2e.test.mjs +++ b/web/test/e2e.test.mjs @@ -390,6 +390,8 @@ test('adds payslips to a report', async () => { await frame.waitForFunction(() => document.querySelectorAll('.picker button').length === 3); // The report opens on the latest month again. assert.equal(await frame.$eval('.picker button[aria-pressed="true"]', (b) => b.textContent), 'Mar 26'); + // The confirmation goes by itself after a few seconds. + await page.waitForSelector('#added', { state: 'hidden', timeout: 8000 }); assert.deepEqual(seen.errors, []); onlyThisSite(seen); await page.close();