release: Mark VIII 1.0
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
'use strict';
|
||||
|
||||
const Pager = (() => {
|
||||
const SCREEN_WIDTH = 480;
|
||||
const SCREEN_HEIGHT = 222;
|
||||
const FB_STRIDE = SCREEN_WIDTH * 4;
|
||||
const PAGER_WIDTH = 745;
|
||||
|
||||
const KEY_MAP = {
|
||||
'LEFT.png': 'ArrowLeft',
|
||||
'UP.png': 'ArrowUp',
|
||||
'RIGHT.png': 'ArrowRight',
|
||||
'DOWN.png': 'ArrowDown',
|
||||
'A_Button.png': 'Enter',
|
||||
'B_Button.png': 'Escape'
|
||||
};
|
||||
|
||||
let panel = null;
|
||||
let bodyEl = null;
|
||||
let table = null;
|
||||
let pager = null;
|
||||
let canvas = null;
|
||||
let screenerr = null;
|
||||
let keyws = null;
|
||||
let screenws = null;
|
||||
|
||||
function ensure() {
|
||||
if (table) return;
|
||||
panel = document.getElementById('pager-panel');
|
||||
bodyEl = document.getElementById('pager-body');
|
||||
table = document.getElementById('pager_ui');
|
||||
pager = document.getElementById('pager');
|
||||
canvas = document.getElementById('pager_canvas');
|
||||
screenerr = document.getElementById('pager_error');
|
||||
table.querySelectorAll('img.pager-btn').forEach((img) => {
|
||||
const src = img.getAttribute('src').split('/').pop();
|
||||
const key = KEY_MAP[src];
|
||||
if (!key) return;
|
||||
img.addEventListener('click', () => press(img, key));
|
||||
});
|
||||
const retry = document.getElementById('screen_retry');
|
||||
if (retry) retry.addEventListener('click', () => connect());
|
||||
}
|
||||
|
||||
function press(el, key) {
|
||||
el.classList.add('pressed');
|
||||
setTimeout(() => el.classList.remove('pressed'), 80);
|
||||
sendKey(key);
|
||||
}
|
||||
|
||||
function sendKey(k) {
|
||||
if (keyws && keyws.readyState === WebSocket.OPEN) keyws.send(k);
|
||||
}
|
||||
|
||||
function renderRGBAFrame(bytes) {
|
||||
if (bytes.length < FB_STRIDE * SCREEN_HEIGHT) {
|
||||
console.warn('pager frame too small for 480x222', bytes.length);
|
||||
return;
|
||||
}
|
||||
const ctx = canvas.getContext('2d');
|
||||
const imageData = ctx.createImageData(SCREEN_WIDTH, SCREEN_HEIGHT);
|
||||
const dst = imageData.data;
|
||||
let di = 0;
|
||||
for (let y = 0; y < SCREEN_HEIGHT; y++) {
|
||||
const rowStart = y * FB_STRIDE;
|
||||
for (let x = 0; x < SCREEN_WIDTH; x++) {
|
||||
const si = rowStart + x * 4;
|
||||
dst[di] = bytes[si];
|
||||
dst[di + 1] = bytes[si + 1];
|
||||
dst[di + 2] = bytes[si + 2];
|
||||
dst[di + 3] = bytes[si + 3];
|
||||
di += 4;
|
||||
}
|
||||
}
|
||||
ctx.putImageData(imageData, 0, 0);
|
||||
pager.src = canvas.toDataURL('image/png');
|
||||
}
|
||||
|
||||
function connect() {
|
||||
disconnect();
|
||||
try {
|
||||
const sock = new WebSocket(App.pagerScreenWs);
|
||||
sock.binaryType = 'arraybuffer';
|
||||
screenws = sock;
|
||||
sock.onopen = () => { screenerr.hidden = true; };
|
||||
sock.onmessage = (ev) => {
|
||||
if (ev.data instanceof ArrayBuffer) renderRGBAFrame(new Uint8Array(ev.data));
|
||||
else if (ev.data && ev.data.arrayBuffer) ev.data.arrayBuffer().then((b) => renderRGBAFrame(new Uint8Array(b)));
|
||||
};
|
||||
sock.onerror = () => { screenerr.hidden = false; };
|
||||
sock.onclose = () => { if (screenws === sock) screenws = null; screenerr.hidden = false; };
|
||||
} catch (e) {
|
||||
screenerr.hidden = false;
|
||||
}
|
||||
try {
|
||||
const sock = new WebSocket(App.pagerKeysWs);
|
||||
keyws = sock;
|
||||
sock.onclose = () => { if (keyws === sock) keyws = null; };
|
||||
sock.onerror = () => { try { sock.close(); } catch (e2) {} };
|
||||
} catch (e) {
|
||||
keyws = null;
|
||||
}
|
||||
}
|
||||
|
||||
function disconnect() {
|
||||
if (screenws) { try { screenws.close(); } catch (e) {} screenws = null; }
|
||||
if (keyws) { try { keyws.close(); } catch (e) {} keyws = null; }
|
||||
}
|
||||
|
||||
function applyScale() {
|
||||
if (!bodyEl || !table) return;
|
||||
const availableWidth = bodyEl.clientWidth - 20;
|
||||
const scale = Math.min(1, availableWidth / PAGER_WIDTH);
|
||||
table.style.zoom = String(scale);
|
||||
table.style.marginBottom = '';
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
ensure();
|
||||
if (panel.classList.contains('hidden')) {
|
||||
panel.classList.remove('hidden');
|
||||
document.getElementById('pager-btn').classList.add('active');
|
||||
applyScale();
|
||||
connect();
|
||||
try { pager.focus(); } catch (e) {}
|
||||
} else {
|
||||
panel.classList.add('hidden');
|
||||
document.getElementById('pager-btn').classList.remove('active');
|
||||
disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
function isOpen() {
|
||||
return !!panel && !panel.classList.contains('hidden');
|
||||
}
|
||||
|
||||
function close() {
|
||||
if (isOpen()) toggle();
|
||||
}
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
if (table && panel && !panel.classList.contains('hidden')) applyScale();
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (!isOpen()) return;
|
||||
const t = e.target;
|
||||
if (t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.tagName === 'SELECT'
|
||||
|| t.tagName === 'BUTTON' || t.tagName === 'A' || t.isContentEditable)) return;
|
||||
if (e.ctrlKey || e.metaKey || e.altKey) return;
|
||||
const keys = ['ArrowLeft', 'ArrowUp', 'ArrowRight', 'ArrowDown', 'Enter', 'Escape'];
|
||||
if (keys.indexOf(e.key) === -1) return;
|
||||
if (e.key.indexOf('Arrow') === 0) e.preventDefault();
|
||||
if (!e.repeat) sendKey(e.key);
|
||||
});
|
||||
|
||||
return { toggle, close, isOpen };
|
||||
})();
|
||||
Reference in New Issue
Block a user