phimpal-tv 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/mod.js +55 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -113,6 +113,13 @@ swallow all input — that looked like a frozen cursor with a dead Return key. S
|
|
|
113
113
|
- page scans are memoised (a TV CPU was re-computing the same styles hundreds of times per
|
|
114
114
|
key), held keys are coalesced, and carousels are recognised structurally so paging also
|
|
115
115
|
works on Tizen ≤ 6.5, whose Chromium ignores `overflow-x: clip`.
|
|
116
|
+
- (0.2.2) a **black box**: the last 40 steps (key, mode, focus, scope, paging…) are written to
|
|
117
|
+
`localStorage['tv-trace']` as they happen. If a session ends in the middle of a key, the
|
|
118
|
+
**next launch** shows the last 12 steps in the same red box for 25 s — so after a freeze,
|
|
119
|
+
relaunch the module and photograph that box.
|
|
120
|
+
- (0.2.2) a navigation scope (dialog/menu) now has to be on screen **and** contain something
|
|
121
|
+
focusable; an element merely carrying `role="dialog"` cannot trap the keys any more, and a
|
|
122
|
+
scope that refuses to close is ignored afterwards.
|
|
116
123
|
|
|
117
124
|
## Known limits
|
|
118
125
|
|
package/mod.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// PhimPal TV v0.2.
|
|
1
|
+
// PhimPal TV v0.2.2 — generated by build.js from phimpal-tv.user.js, do not edit.
|
|
2
2
|
// TizenBrew site-modification module for https://phimpal.com/
|
|
3
3
|
(function registerRemoteKeys() {
|
|
4
4
|
try {
|
|
@@ -67,6 +67,28 @@
|
|
|
67
67
|
} catch (x) { /* ignore */ }
|
|
68
68
|
}
|
|
69
69
|
const safe = (where, fn) => function () { try { return fn.apply(this, arguments); } catch (e) { report(where, e); } };
|
|
70
|
+
|
|
71
|
+
// Black box: the last steps are written to localStorage as they happen. If the previous
|
|
72
|
+
// session died mid-key (frozen page, app killed), the next launch shows that tail on screen.
|
|
73
|
+
const TRACE_KEY = 'tv-trace', TRACE_MAX = 40;
|
|
74
|
+
let traceBuf = [];
|
|
75
|
+
function trace(msg) {
|
|
76
|
+
traceBuf.push((Date.now() % 100000) + ' ' + msg);
|
|
77
|
+
if (traceBuf.length > TRACE_MAX) traceBuf.shift();
|
|
78
|
+
try { localStorage.setItem(TRACE_KEY, traceBuf.join('\n')); } catch (e) { /* ignore */ }
|
|
79
|
+
}
|
|
80
|
+
function showPreviousTrace() {
|
|
81
|
+
let prev = null; try { prev = localStorage.getItem(TRACE_KEY); } catch (e) { /* ignore */ }
|
|
82
|
+
if (!prev || !/\bkey \d+ start\b(?![\s\S]*\bkey end\b)/.test(prev)) return; // previous session ended cleanly
|
|
83
|
+
const tail = prev.split('\n').slice(-12).join('\n');
|
|
84
|
+
try {
|
|
85
|
+
let b = $('#tv-error');
|
|
86
|
+
if (!b) { b = document.createElement('pre'); b.id = 'tv-error'; document.body.appendChild(b); }
|
|
87
|
+
b.textContent = 'phimpal-tv — previous session ended mid-key:\n' + tail;
|
|
88
|
+
b.style.opacity = '1';
|
|
89
|
+
clearTimeout(errTimer); errTimer = setTimeout(() => { b.style.opacity = '0'; }, 25000);
|
|
90
|
+
} catch (e) { /* ignore */ }
|
|
91
|
+
}
|
|
70
92
|
const rectOf = (el) => el.getBoundingClientRect();
|
|
71
93
|
const isWatch = () => /^\/watch\//.test(location.pathname);
|
|
72
94
|
const isHome = () => location.pathname === '/';
|
|
@@ -187,14 +209,23 @@
|
|
|
187
209
|
}
|
|
188
210
|
const hoverMenuOpen = () => !!S.hoverTrigger && !!currentScope(); // aria-expanded goes stale, the rendered menu doesn't
|
|
189
211
|
|
|
190
|
-
// dialogs / menus that should capture navigation
|
|
212
|
+
// dialogs / menus that should capture navigation.
|
|
213
|
+
// A scope must be on screen AND contain something to focus: an element that merely
|
|
214
|
+
// carries role="dialog" (a toast, an off-screen drawer, a banner) would otherwise trap
|
|
215
|
+
// every key — arrows find nothing inside it and Back cannot close it. Scopes that refuse
|
|
216
|
+
// to close are blacklisted for the rest of the page.
|
|
191
217
|
const subPanel = () => $('.vjs-subtitle-panel.is-open');
|
|
218
|
+
const deadScopes = new WeakSet();
|
|
192
219
|
function currentScope() {
|
|
193
220
|
const list = $$('.vjs-modal-dialog:not(.vjs-hidden), [role="dialog"], [role="alertdialog"], [role="menu"], [data-scope="menu"][data-part="content"], [data-scope="popover"][data-part="content"], [data-scope="dialog"][data-part="content"]');
|
|
194
221
|
const dialog = list.find((e) => {
|
|
222
|
+
if (deadScopes.has(e)) return false;
|
|
195
223
|
if (e.closest('.video-js') && !e.classList.contains('vjs-modal-dialog')) return false;
|
|
224
|
+
if (e.closest('aside')) return false; // mobile drawer, parked off-screen with a transform
|
|
196
225
|
const r = rectOf(e);
|
|
197
|
-
|
|
226
|
+
if (!(r.width > 0 && r.height > 0 && styleVisible(e))) return false;
|
|
227
|
+
if (r.right <= 0 || r.bottom <= 0 || r.left >= innerWidth || r.top >= innerHeight) return false; // off-screen
|
|
228
|
+
return $$(CAND, e).some((c) => c !== e && isVisibleBlock(c)); // has something to focus
|
|
198
229
|
});
|
|
199
230
|
if (dialog) return dialog; // a caption-settings modal opens on top of the subtitle panel
|
|
200
231
|
const sp = subPanel();
|
|
@@ -289,14 +320,18 @@
|
|
|
289
320
|
if (!canClick(btn)) return false;
|
|
290
321
|
const item = block.closest('.shrink-0') || block;
|
|
291
322
|
const sib = dir === A.RIGHT ? item.nextElementSibling : item.previousElementSibling;
|
|
323
|
+
trace('page click ' + dir);
|
|
292
324
|
btn.click();
|
|
325
|
+
trace('page clicked');
|
|
293
326
|
setTimeout(safe('page', () => {
|
|
294
327
|
let t = sib && (sib.matches('.group') ? sib : sib.querySelector('.group'));
|
|
295
328
|
if (!(t && isVisibleBlock(t))) {
|
|
296
329
|
const vis = withScanCache(() => $$('.group', car.clip).filter(isVisibleBlock));
|
|
297
330
|
t = dir === A.RIGHT ? vis[0] : vis[vis.length - 1];
|
|
298
331
|
}
|
|
332
|
+
trace('page settled target=' + describe(t));
|
|
299
333
|
if (t) setFocus(t); else badge('page: no card');
|
|
334
|
+
trace('page done');
|
|
300
335
|
}), CFG.carouselAnimMs);
|
|
301
336
|
return true;
|
|
302
337
|
}
|
|
@@ -326,6 +361,7 @@
|
|
|
326
361
|
}
|
|
327
362
|
function setFocus(block, opts = {}) {
|
|
328
363
|
if (!block) return;
|
|
364
|
+
trace('focus ' + describe(block));
|
|
329
365
|
if (S.focused !== block) clearFocus();
|
|
330
366
|
S.focused = block;
|
|
331
367
|
if (isCard(block)) { block.classList.add('tv-focus'); (popupOf(block) || block).classList.add('tv-focus-ring'); }
|
|
@@ -697,7 +733,8 @@
|
|
|
697
733
|
const scope = currentScope();
|
|
698
734
|
const blocks = collectBlocks(scope);
|
|
699
735
|
let t = pickTarget(f, dir, blocks);
|
|
700
|
-
|
|
736
|
+
trace('move ' + dir + ' scope=' + describe(scope) + ' blocks=' + blocks.length + ' target=' + describe(t));
|
|
737
|
+
if (!t && (dir === A.LEFT || dir === A.RIGHT) && pageCarousel(f, dir)) { trace('paging ' + dir); return; }
|
|
701
738
|
if (!t && dir === A.UP && !scope && scrollY > 0) {
|
|
702
739
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
703
740
|
setTimeout(() => { const t2 = pickTarget(S.focused, A.UP, collectBlocks()); if (t2) setFocus(t2); }, 450);
|
|
@@ -715,7 +752,9 @@
|
|
|
715
752
|
if (scope && scope.classList.contains('vjs-subtitle-panel')) return closeSubPanel();
|
|
716
753
|
const hoverT = S.hoverTrigger;
|
|
717
754
|
const finish = () => {
|
|
718
|
-
|
|
755
|
+
const still = currentScope();
|
|
756
|
+
if (still === scope) { deadScopes.add(scope); trace('scope refused to close, ignoring it'); } // never trap the user
|
|
757
|
+
else if (still) return; // a different dialog is open, stay in it
|
|
719
758
|
if (S.returnMode) { const m = S.returnMode; S.returnMode = null; return m === 'PLAYER' ? enterPlayer() : enterBar(); }
|
|
720
759
|
const back = S.beforeScope || hoverT; S.beforeScope = null;
|
|
721
760
|
if (back && isVisibleBlock(back)) return setFocus(back, { noHover: true }); // don't re-open a hover menu
|
|
@@ -769,8 +808,16 @@
|
|
|
769
808
|
badge('reset: ' + why, true);
|
|
770
809
|
}
|
|
771
810
|
function onKeySafe(e) {
|
|
811
|
+
const known = TIZEN_KEYS[e.keyCode] || (CFG.keyboardEmulation && KB_KEYS[e.keyCode]);
|
|
812
|
+
if (known) trace('key ' + e.keyCode + ' start mode=' + S.mode + ' sub=' + S.sub + ' focus=' + describe(S.focused));
|
|
772
813
|
try { onKey(e); }
|
|
773
814
|
catch (err) { report('key ' + e.keyCode, err); try { hardReset('error'); } catch (x) { report('reset', x); } }
|
|
815
|
+
if (known) trace('key end mode=' + S.mode + ' focus=' + describe(S.focused));
|
|
816
|
+
}
|
|
817
|
+
function describe(el) {
|
|
818
|
+
if (!el) return 'none';
|
|
819
|
+
const a = el.tagName === 'A' ? el : (el.querySelector && el.querySelector('a[href]'));
|
|
820
|
+
return el.tagName + (el.id ? '#' + el.id : '') + '.' + (el.className + '').trim().split(/\s+/).slice(0, 2).join('.') + (a && a.getAttribute('href') ? '[' + a.getAttribute('href').slice(0, 30) + ']' : '');
|
|
774
821
|
}
|
|
775
822
|
function onKey(e) {
|
|
776
823
|
if (e.__tv) return;
|
|
@@ -810,6 +857,7 @@
|
|
|
810
857
|
if (S.mode !== 'NAV') { S.returnMode = S.mode === 'SEEK' ? 'PLAYER' : S.mode; clearSeek(); S.mode = 'NAV'; S.sub = null; S.menu = null; }
|
|
811
858
|
if (!S.focused || !scope.contains(S.focused)) {
|
|
812
859
|
const bl = collectBlocks(scope);
|
|
860
|
+
trace('scope grabbed key: ' + describe(scope) + ' blocks=' + bl.length);
|
|
813
861
|
if (bl.length) { S.beforeScope = S.focused; setFocus(bl[0]); if (act !== A.BACK) return; }
|
|
814
862
|
}
|
|
815
863
|
} else if (S.returnMode) {
|
|
@@ -936,6 +984,8 @@
|
|
|
936
984
|
function init() {
|
|
937
985
|
if (window.__tvNav && window.__tvNav.destroy) window.__tvNav.destroy(); // hot re-install (dev)
|
|
938
986
|
injectCSS();
|
|
987
|
+
showPreviousTrace();
|
|
988
|
+
trace('init ' + location.pathname + ' ' + innerWidth + 'x' + innerHeight);
|
|
939
989
|
addEventListener('keydown', onKeySafe, true);
|
|
940
990
|
patchHistory();
|
|
941
991
|
const onRouteSafe = safe('route', checkRoute);
|
package/package.json
CHANGED