diff options
| author | Danilo M. <danix@danix.xyz> | 2026-11-09 20:04:06 +0100 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-11-09 20:04:06 +0100 |
| commit | c47e0f22207302e4dcad9b8480e033fbe85dccd5 (patch) | |
| tree | e34e77c8cc018f78bc9bf5b2c70d07e05d07725c | |
| parent | d107596f6c277d6aa4616616cc4fe176f339a8f5 (diff) | |
| download | deck-it-c47e0f22207302e4dcad9b8480e033fbe85dccd5.tar.gz deck-it-c47e0f22207302e4dcad9b8480e033fbe85dccd5.zip | |
feat: center the tabs and swipe to switch them
| -rw-r--r-- | static/app.js | 32 | ||||
| -rw-r--r-- | static/deck.css | 18 | ||||
| -rw-r--r-- | static/index.html | 1 | ||||
| -rw-r--r-- | tests/test_frontend.py | 2 |
4 files changed, 28 insertions, 25 deletions
diff --git a/static/app.js b/static/app.js index d125d69..296166a 100644 --- a/static/app.js +++ b/static/app.js @@ -186,15 +186,33 @@ async function boot() { showTab(tabs[saved] ? saved : "Dashboard"); } -const fullscreenButton = document.getElementById("fullscreen"); -fullscreenButton?.addEventListener("click", async () => { - try { - if (document.fullscreenElement) await document.exitFullscreen(); - else await document.documentElement.requestFullscreen(); - } catch { - // the browser refused; nothing to do +function stepTab(dir) { + const names = [...tabBar.children].map((button) => button.textContent); + const at = names.findIndex((name) => !tabs[name]?.section.hidden); + const next = at + dir; + if (at !== -1 && next >= 0 && next < names.length) showTab(names[next]); +} + +// Swipe the content sideways to change tab; gestures on sliders and buttons +// (including the card drag handles) are theirs, not the shell's. +let swipe = null; +tabBody.addEventListener("pointerdown", (e) => { + if (e.pointerType === "mouse" && e.buttons !== 1) return; + if (e.target.closest("input, select, textarea, button")) { + swipe = null; + return; } + swipe = { x: e.clientX, y: e.clientY }; +}); +tabBody.addEventListener("pointerup", (e) => { + if (!swipe) return; + const dx = e.clientX - swipe.x; + const dy = e.clientY - swipe.y; + swipe = null; + if (Math.abs(dx) < 70 || Math.abs(dx) < Math.abs(dy) * 1.5) return; + stepTab(dx < 0 ? 1 : -1); }); +tabBody.addEventListener("pointercancel", () => { swipe = null; }); document.addEventListener("visibilitychange", () => { if (document.visibilityState === "visible") refreshers.forEach((fn) => fn()); diff --git a/static/deck.css b/static/deck.css index 5bd8a5d..ee2be1f 100644 --- a/static/deck.css +++ b/static/deck.css @@ -19,24 +19,10 @@ body { .topbar { flex: none; display: flex; - align-items: center; - gap: 8px; + justify-content: center; padding: 8px 14px 0; } -.tabs { flex: 1; display: flex; gap: 6px; min-width: 0; } -.fs { - flex: none; - font: inherit; - font-size: 18px; - line-height: 1; - color: var(--udt-subtext0); - background: transparent; - border: 1px solid transparent; - border-radius: 10px; - padding: 6px 12px; - cursor: pointer; -} -.fs:hover { background: var(--udt-surface1); color: var(--udt-text); } +.tabs { display: flex; gap: 6px; } .tabs button { font: inherit; color: var(--udt-subtext0); diff --git a/static/index.html b/static/index.html index 434e496..5a4ca3e 100644 --- a/static/index.html +++ b/static/index.html @@ -19,7 +19,6 @@ <body> <header class="topbar"> <nav id="tabs" class="tabs"></nav> - <button id="fullscreen" class="fs" aria-label="Toggle fullscreen">⛶</button> </header> <div id="tab-body"></div> <script src="vendor/gridstack/gridstack-all.js"></script> diff --git a/tests/test_frontend.py b/tests/test_frontend.py index 1111f89..0889c5b 100644 --- a/tests/test_frontend.py +++ b/tests/test_frontend.py @@ -94,7 +94,7 @@ class Frontend(unittest.TestCase): def test_tabbed_shell(self): self.assertIn(b'id="tabs"', self.get("/")[1]) self.assertIn(b'id="tab-body"', self.get("/")[1]) - self.assertIn(b'id="fullscreen"', self.get("/")[1]) + self.assertIn(b"stepTab", self.get("/app.js")[1]) app = self.get("/app.js")[1] for part in (b"deckit.tab", b"ensureTab", b"showTab"): self.assertIn(part, app) |
