clone

a desktop tv and program guide for over-the-air broadcasts: what's on air, what your tuner gets, live tv, recordings

Keep the guide's TV and details apart at every size

FelineStateMachine committed acf1dea
At a larger interface size the Mac app's guide put the TV preview over
the program's details: under CSS zoom its WebKit sizes the floating TV
and the grid's column from viewport units differently. app.js now gives
style.css the viewport in plain pixels (--vw, --vh), which zoom alike
everywhere, and keeps them current as the window changes.

A small window at a large size also left the details spilling over the
filters and the grid. What doesn't fit is now left out, the description
first, then the tags, the episode line and the hint; the title and the
actions always show, the actions on one row that scrolls sideways when
even that isn't enough.
commit
acf1dea856cfc3a78faca667e07415d274c820e2
parent
419914f8ce8ec5175cf6e4f983a563110f80f8c5
author
FelineStateMachine <226808055+FelineStateMachine@users.noreply.github.com>
kind
fix risk score 1.7 of 3, Jev's estimate
2 files changed, +38 -1
Mfrontend/dist/app.js30++++++++++++++++++++++++++++++
Mfrontend/dist/style.css9++++++++-
M frontend/dist/app.js +30 -0
@@ -2143,6 +2143,8 @@ function humanMinutes(m) {
21432143 function renderGuideActions(ch, p) {21442144 const box = f($('#guide'), 'actions');21452145 if (!ch) { box.innerHTML = ''; return; }2146+ // Laid out, it may need less shown beside it.2147+ requestAnimationFrame(fitGuideDetail);21462148 const now = Date.now();21472149 const airing = !p || p._s <= now;21482150 const act = (name, label, cls = '') => `<button type="button" class="act fx${cls ? ` ${cls}` : ''}" data-act="${name}">${label}</button>`;
@@ -2171,6 +2173,27 @@ function renderGuideActions(ch, p) {
21712173 guideRefocus();21722174 }21732175 2176+// fitGuideDetail leaves out what of the program's details doesn't fit2177+// above the grid at this window and interface size: its description2178+// first, then its tags, its episode line and the hint. The title and the2179+// actions always show, the actions on one row that scrolls sideways when2180+// even that isn't enough.2181+function fitGuideDetail() {2182+ const g = $('#guide');2183+ const top = $('.g-top', g);2184+ const box = $('.g-detail', g);2185+ const parts = ['desc', 'meta', 'ep', 'hint'].map((k) => f(g, k));2186+ for (const el of parts) el.classList.remove('gd-gone');2187+ box.classList.remove('one-row');2188+ const cs = getComputedStyle(top);2189+ const room = top.clientHeight - parseFloat(cs.paddingTop) - parseFloat(cs.paddingBottom);2190+ for (const el of parts) {2191+ if (box.offsetHeight <= room) return;2192+ el.classList.add('gd-gone');2193+ }2194+ if (box.offsetHeight > room) box.classList.add('one-row');2195+}2196+21742197 function recordSelected(kind, newOnly) {21752198 const list = guideChannels();21762199 return recordAiring(list[state.gRow], selectedProgram(list), kind, newOnly);
@@ -3241,6 +3264,13 @@ function applyScale() {
32413264 root.style.zoom = z === 1 ? '' : String(z);32423265 root.style.setProperty('--zoom', String(z));32433266 }3267+ // The viewport in the root's own pixels, for style.css's --vw and --vh:3268+ // engines differ on what vw and vh mean under CSS zoom (the Mac app's3269+ // WebKit sizes the guide's floating TV and its grid differently), while3270+ // pixels zoom alike everywhere.3271+ const cssZoom = native ? 1 : z;3272+ root.style.setProperty('--vw', `${innerWidth / cssZoom / 100}px`);3273+ root.style.setProperty('--vh', `${innerHeight / cssZoom / 100}px`);32443274 // Larger sizes leave less room above the guide (.tight in style.css):32453275 // the size chosen, not the zoom, which on the Android TV app also fits a32463276 // 1080p screen's interface into the WebView.
M frontend/dist/style.css +8 -1
@@ -25,7 +25,8 @@
2525 --f-body: "Schibsted", "Helvetica Neue", sans-serif;2626 --f-mono: "Plex Mono", "Menlo", monospace;2727 /* Interface size: app.js zooms the root by --zoom. Viewport units go28- through --vw and --vh, which undo the zoom so full screen stays full. */28+ through --vw and --vh, which undo the zoom so full screen stays full;29+ app.js sets them in pixels, and these stand in until it does. */2930 --zoom: 1;3031 --vw: calc(1vw / var(--zoom));3132 --vh: calc(1vh / var(--zoom));
@@ -197,6 +198,7 @@ body.mode-guide #guide, body.mode-antenna #antenna, body.mode-settings #settings
197198 .gd-meta { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin: 10px 0; font: 400 12px var(--f-mono); color: var(--muted); }198199 .gd-desc { margin: 0; color: var(--muted); max-width: 70ch; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }199200 .gd-hint { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 12px; font: 400 11px var(--f-mono); color: var(--faint); letter-spacing: .06em; }201+.g-detail .gd-gone { display: none; }200202 .gd-art { align-self: stretch; margin-top: -2px; border-radius: 3px; background: var(--ink-2) center/cover no-repeat; opacity: 0; transition: opacity .4s;201203 -webkit-mask-image: linear-gradient(90deg, transparent, #000 35%); mask-image: linear-gradient(90deg, transparent, #000 35%); }202204 .gd-art.has { opacity: .85; }
@@ -493,6 +495,11 @@ body:not(.booting) #boot { opacity: 0; visibility: hidden; }
493495 /* ---------- Recording ---------- */494496 .gd-actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-top: 12px; }495497 .gd-actions:empty { display: none; }498+/* No room for a second row (a small window at a large size): one row that499+ scrolls sideways, with room for the focus ring. */500+.g-detail.one-row .gd-actions { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; padding: 4px; margin: 8px -4px -4px; }501+.g-detail.one-row .gd-actions::-webkit-scrollbar { display: none; }502+.g-detail.one-row .gd-actions > * { flex-shrink: 0; }496503 .act {497504 border: 0; cursor: pointer; padding: 8px 13px; border-radius: 3px; background: none;498505 box-shadow: inset 0 0 0 1px var(--line-2); color: var(--paper);