From a8f6692de45196e5b5b0cd85c58febb18618cb58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20K=C3=BChn?= Date: Thu, 30 Apr 2026 11:00:54 +0200 Subject: [PATCH] Add stopwatch fill button and asset cache-busting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Stopwatch ⏱ button appears in add/quick-book rows when sw >= 1s, fills input with floor(seconds) - AppController passes md5 hashes of app.js/style.css to template for automatic cache-busting Co-Authored-By: Claude Sonnet 4.6 --- public/app.js | 25 +++++++++++++++++++------ public/style.css | 2 ++ templates/app.html.twig | 2 ++ 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/public/app.js b/public/app.js index 159d41e..aac257d 100644 --- a/public/app.js +++ b/public/app.js @@ -715,18 +715,23 @@ document.addEventListener('visibilitychange',function(){ } }); -(function(){ +var sw = (function(){ var swEl = document.getElementById('sw'); var state = 0; // 0=stopped, 1=running, 2=paused var start = 0, elapsed = 0, raf = null; - function fmt(ms){ - var s = ms / 1000; - return s.toFixed(2) + 's'; + function getMs(){ return state === 1 ? elapsed + (Date.now() - start) : elapsed; } + + function updateFillBtns(){ + var show = getMs() >= 1000; + document.querySelectorAll('.btn-sw-fill').forEach(function(b){ b.style.display = show ? '' : 'none'; }); } + function fmt(ms){ return (ms / 1000).toFixed(2) + 's'; } + function tick(){ swEl.textContent = fmt(Date.now() - start + elapsed); + updateFillBtns(); raf = requestAnimationFrame(tick); } @@ -740,12 +745,20 @@ document.addEventListener('visibilitychange',function(){ elapsed += Date.now() - start; swEl.textContent = fmt(elapsed); swEl.classList.remove('running'); - state = 2; + state = 2; updateFillBtns(); } else { cancelAnimationFrame(raf); elapsed = 0; swEl.textContent = '0.00s'; swEl.classList.remove('running'); - state = 0; + state = 0; updateFillBtns(); } }); + + document.addEventListener('click', function(e){ + if(!e.target.classList.contains('btn-sw-fill')) return; + var inp = e.target.closest('.add-row, .qb-row').querySelector('.num-in'); + if(inp) inp.value = Math.floor(getMs() / 1000); + }); + + return { getMs: getMs }; })(); diff --git a/public/style.css b/public/style.css index 3ca65bd..0706739 100644 --- a/public/style.css +++ b/public/style.css @@ -73,6 +73,8 @@ body{font-family:'DM Sans',sans-serif;background:var(--bg);color:var(--text);min .ulbl{font-size:12px;color:var(--text3)} .btn-as{flex:1;padding:8px;border-radius:var(--rs);background:var(--blue-bg);color:var(--blue);border:1px solid rgba(37,99,235,.2);cursor:pointer;font-family:'DM Sans',sans-serif;font-size:13px;font-weight:600} .btn-as:active{opacity:.7} +.btn-sw-fill{flex:none;padding:7px 9px;border-radius:var(--rs);background:var(--bg3);color:var(--text2);border:1px solid var(--border2);cursor:pointer;font-size:13px;line-height:1} +.btn-sw-fill:active{opacity:.7} .nosets{font-size:12px;color:var(--text3);padding:4px 0 8px} .card-foot{padding:8px 16px 12px;display:flex;justify-content:flex-end} .btn-del{background:none;border:none;color:var(--text3);font-size:12px;cursor:pointer;padding:4px 0;font-family:'DM Sans',sans-serif} diff --git a/templates/app.html.twig b/templates/app.html.twig index 3022790..727f421 100644 --- a/templates/app.html.twig +++ b/templates/app.html.twig @@ -55,6 +55,7 @@
+
@@ -74,6 +75,7 @@
+