const $ = document.querySelector.bind(document); const $$ = document.querySelectorAll.bind(document); function scheduleCCall(f, ms, ...args) { const func = UTF8ToString(f); return setTimeout(() => Module[`_${func}`](...args), ms); } function toCString(str) { // caller must free if (!str) return 0; const size = lengthBytesUTF8(str) + 1; const ptr = _malloc(size); stringToUTF8(str, ptr, size); return ptr; } function elemGetContent(sel) { // caller must free return toCString($(UTF8ToString(sel)).textContent); } function elemSetContent(sel, str) { $(UTF8ToString(sel)).textContent = str ? UTF8ToString(str) : ''; } function elemSetValue(sel, str) { $(UTF8ToString(sel)).value = str ? UTF8ToString(str) : ''; } function elemHasClass(sel, c) { return $(UTF8ToString(sel)).classList.contains(UTF8ToString(c)); } function elemAddClass(sel, c) { $(UTF8ToString(sel)).classList.add(UTF8ToString(c)); } function elemRemoveClass(sel, c) { $(UTF8ToString(sel)).classList.remove(UTF8ToString(c)); } function elemToggleClass(sel, c) { $(UTF8ToString(sel)).classList.toggle(UTF8ToString(c)); } function elemSetUniqClass(sel, c, sel_set) { selstr = sel && UTF8ToString(sel); cstr = UTF8ToString(c); $$(UTF8ToString(sel_set)).forEach(e => e.classList[ sel && e.matches(selstr) ? 'add' : 'remove'](cstr)); } function elemScrollToBottom(sel) { const e = $(UTF8ToString(sel)); e.scrollTop = e.scrollHeight; } function elemScrollToCenterChild(sel, sel_child) { const e = $(UTF8ToString(sel)); const child = $(UTF8ToString(sel_child)); if (!child) return; e.scrollLeft = child.offsetLeft + child.offsetWidth/2 - e.offsetLeft - e.offsetWidth/2; } function targetAppendElemv(target, elemc, elemv, off_type, off_ns, off_name, off_content, off_n_attrs, off_attr_names, off_attr_vals, off_n_children, off_children) { for (let ptr = elemv; ptr < elemv + 4*elemc; ptr += 4) { const struct = HEAP32[ptr>>2]; let e; const type = UTF8ToString(HEAP32[(struct + off_type)>>2]); const contentptr = HEAP32[(struct + off_content)>>2]; const content = contentptr && UTF8ToString(contentptr); if (type === "text") { e = document.createTextNode(content); } else { const nsptr = HEAP32[(struct + off_ns)>>2]; const name = UTF8ToString( HEAP32[(struct + off_name)>>2]); e = nsptr ? document.createElementNS( UTF8ToString(nsptr), name) : document.createElement(name); if (content) e.textContent = content; } const n_attrs = HEAP32[(struct + off_n_attrs)>>2]; if (n_attrs) { const attr_names = HEAP32[(struct + off_attr_names)>>2]; const attr_vals = HEAP32[(struct + off_attr_vals)>>2]; for (let i = 0; i < n_attrs; ++i) e.setAttribute( UTF8ToString( HEAP32[(attr_names + i*4)>>2]), UTF8ToString( HEAP32[(attr_vals + i*4)>>2])); } const n_children = HEAP32[(struct + off_n_children)>>2]; if (n_children) targetAppendElemv(e, n_children, HEAP32[(struct + off_children)>>2], ...Array.prototype.slice.call(arguments, 3)); target.appendChild(e); } } function elemAppendElemv(sel, ...args) { targetAppendElemv($(UTF8ToString(sel)), ...args); } function promptHandle(command, data) { const str_c = toCString(command); const str_d = data && toCString(data); _prompt_handle(str_c, str_d); _free(str_c); if (str_d) _free(str_d); } function readOpt(opt) { // caller must free return toCString(localStorage.getItem(UTF8ToString(opt))); } function storeOpt(opt, val) { localStorage.setItem(UTF8ToString(opt), UTF8ToString(val)); } function exitDialogue(e) { e.preventDefault(); e.returnValue = ''; // bloody chromium } function enableExitDialogue(enable) { if (enable) window.addEventListener('beforeunload', exitDialogue); else window.removeEventListener('beforeunload', exitDialogue); } function boatAfloat() { if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', () => _init()); else _init(); } function flipControls(store_opt = true) { const enabled = $('#main').classList.toggle('controls-on-right'); $('#flip-controls-button').textContent = enabled ? '\uf1c2' : '\uf1c3'; if (store_opt) localStorage.setItem('dartboat_controls_on_right', enabled); } document.addEventListener('DOMContentLoaded', () => { let f; $('#controls').addEventListener('click', e => { const command = e.target.dataset.command; if (!command) return; command.split(';').forEach(x => promptHandle(...x.split(':', 2))); }); $('#key-exit').addEventListener('click', () => promptHandle('exit')); f = e => _set_opt(toCString(e.target.dataset.opt), toCString(e.target.value)); $$('[data-opt]').forEach(x => x.addEventListener('change', f)); if (localStorage.getItem('dartboat_controls_on_right') == 'true') flipControls(false); $('#flip-controls-button').addEventListener('click', flipControls); f = e => $(`#${e.target.dataset.modal}`).style.display = 'block'; $$('[data-modal]').forEach(x => x.addEventListener('click', f)); f = e => { if (e.target === e.currentTarget || e.target.classList.contains("modal-close")) e.currentTarget.style.display = 'none'; }; $$('.modal').forEach(x => x.addEventListener('click', f)); $('#info-slot-prev').addEventListener('click', () => _scoreboard_prev_slot()); $('#info-slot-next').addEventListener('click', () => _scoreboard_next_slot()); }); document.addEventListener('keydown', e => { if (e.altKey || e.ctrlKey || e.metaKey || e.target.type == 'text') return; if (isFinite(e.key)) promptHandle('append', e.key); else if (e.key == 'Enter') promptHandle('submit'); else if (e.key == 'Backspace') promptHandle('backspace'); else if (e.key == 'c') promptHandle('clear'); else if (e.key == 'r') promptHandle('rem'); else if (e.key == 'u') promptHandle('undo'); }); if ('serviceWorker' in navigator) navigator.serviceWorker.register('sw.js'); if ('virtualKeyboard' in navigator) navigator.virtualKeyboard.overlaysContent = true; // disable back button when installed as a PWA. a single click exits the app, // destroying match state without confirmation, so we must resort to this. if (window.matchMedia('(display-mode: standalone)').matches) { window.addEventListener('load', () => { if (!history.state) { history.replaceState(true, ''); history.pushState(true, ''); } }); window.addEventListener('popstate', () => history.pushState(true, '')); }