(function () { 'use strict'; /* ================================================================== helpers */ var $ = function (id) { return document.getElementById(id); }; function el(tag, cls, text) { var e = document.createElement(tag); if (cls) e.className = cls; if (text !== undefined && text !== null) e.textContent = text; return e; } function clear(n) { while (n.firstChild) n.removeChild(n.firstChild); } function plural(n, w) { return n + ' ' + w + (n === 1 ? '' : 's'); } function basename(p) { var i = p.lastIndexOf('/'); return i < 0 ? p : p.slice(i + 1); } function dirname(p) { var i = p.lastIndexOf('/'); return i < 0 ? '' : p.slice(0, i); } function joinPath(d, n) { return d ? d + '/' + n : n; } function stem(n) { var i = n.lastIndexOf('.'); return i <= 0 ? n : n.slice(0, i); } function isGrammarFile(p) { return /\.g4$/i.test(p); } function human(n) { return n < 1024 ? n + ' B' : n < 1048576 ? (n / 1024).toFixed(1) + ' KB' : (n / 1048576).toFixed(1) + ' MB'; } var store = { get: function (k, d) { try { var v = localStorage.getItem('antlrlab:' + k); return v === null ? d : JSON.parse(v); } catch (e) { return d; } }, set: function (k, v) { try { localStorage.setItem('antlrlab:' + k, JSON.stringify(v)); } catch (e) { /* ignore */ } } }; function debounce(fn, ms) { var t = null; var d = function () { clearTimeout(t); var a = arguments; t = setTimeout(function () { fn.apply(null, a); }, ms); }; d.cancel = function () { clearTimeout(t); }; return d; } /* ==================================================================== state */ var S = { info: null, tabs: [], active: null, // id of the visible tab lastGrammar: null, // the grammar that Run uses lastInput: null, // the input that Run uses results: {}, // input tab id -> { data, rev, grev } gres: null, // last grammar outcome from the server crash: null, // set when the last run crashed, timed out or failed in the worker grev: 0, // bumps on every grammar edit start: {}, // grammar name -> chosen start rule prefs: { autorun: true, theme: 'auto', fs: 13, diagram: true, output: true, view: 'diagram', out: 'problems' }, running: false, queued: false, untitled: { grammar: 0, input: 0 } }; /* ====================================================================== API */ function api(method, path, body) { var opts = { method: method, credentials: 'same-origin', headers: { 'X-Requested-With': 'antlrlab' } }; if (body !== undefined) { opts.body = JSON.stringify(body); opts.headers['Content-Type'] = 'application/json'; } return fetch(path, opts).then(function (r) { return r.text().then(function (t) { var data = null; try { data = JSON.parse(t); } catch (e) { /* not JSON */ } if (!r.ok) { var err = new Error((data && data.error) || ('HTTP ' + r.status)); err.status = r.status; err.data = data; throw err; } return data; }); }); } /* ==================================================================== toast */ function toast(msg, kind) { var t = el('div', 'toast' + (kind ? ' ' + kind : ''), msg); $('toasts').appendChild(t); setTimeout(function () { if (t.parentNode) t.parentNode.removeChild(t); }, kind === 'error' ? 6500 : 2600); } /* ================================================================== dialogs */ // modal({title, build(ctx) -> Node, buttons: [{label, value, primary}]}) -> Promise(value | null) function modal(opts) { return new Promise(function (resolve) { var root = $('modal-root'); var prev = document.activeElement; clear(root); var back = el('div', 'modal-back'); var box = el('div', 'modal'); box.setAttribute('role', 'dialog'); box.setAttribute('aria-modal', 'true'); box.appendChild(el('h3', '', opts.title)); var body = el('div', 'mbody'); var ctx = { close: close, box: box, accept: null }; if (opts.message) body.appendChild(el('p', '', opts.message)); if (opts.build) body.appendChild(opts.build(ctx)); box.appendChild(body); var foot = el('div', 'mfoot'); var primary = null; (opts.buttons || []).forEach(function (b) { var btn = el('button', 'btn' + (b.primary ? ' primary' : ''), b.label); btn.type = 'button'; btn.onclick = function () { if (b.check && !b.check()) return; close(b.value === undefined ? b.label : b.value); }; if (b.primary) primary = btn; foot.appendChild(btn); }); box.appendChild(foot); back.appendChild(box); root.appendChild(back); function onKey(ev) { if (ev.key === 'Escape') { ev.preventDefault(); ev.stopPropagation(); close(null); } else if (ev.key === 'Enter' && primary && ev.target.tagName !== 'BUTTON' && !(ev.target.closest && ev.target.closest('.flist'))) { ev.preventDefault(); ev.stopPropagation(); primary.click(); } } back.addEventListener('mousedown', function (ev) { if (ev.target === back) close(null); }); document.addEventListener('keydown', onKey, true); function close(v) { document.removeEventListener('keydown', onKey, true); clear(root); if (prev && prev.focus) prev.focus(); resolve(v); } var first = box.querySelector('[data-focus]') || primary || box.querySelector('button'); if (first) first.focus(); }); } function ask(title, message, buttons) { return modal({ title: title, message: message, buttons: buttons }); } /* ===================================================================== menus */ var menus = []; // [{label, items: function() -> [...]}] var openMenu = -1; var dropdown = null; function defineMenus(list) { menus = list; var bar = $('menubar'); clear(bar); menus.forEach(function (m, i) { var b = el('button', 'menu-btn', m.label); b.type = 'button'; b.setAttribute('role', 'menuitem'); b.setAttribute('aria-haspopup', 'true'); b.setAttribute('aria-expanded', 'false'); b.onclick = function () { if (openMenu === i) closeMenu(); else showMenu(i, false); }; b.onmouseenter = function () { if (openMenu >= 0 && openMenu !== i) showMenu(i, false); }; b.onkeydown = function (ev) { if (ev.key === 'ArrowDown' || ev.key === 'Enter' || ev.key === ' ') { ev.preventDefault(); showMenu(i, true); } else if (ev.key === 'ArrowRight') { ev.preventDefault(); bar.children[(i + 1) % menus.length].focus(); } else if (ev.key === 'ArrowLeft') { ev.preventDefault(); bar.children[(i + menus.length - 1) % menus.length].focus(); } }; bar.appendChild(b); }); var rn = el('span', '', ''); rn.id = 'rootname'; bar.appendChild(rn); } function closeMenu(refocus) { if (dropdown) { dropdown.remove(); dropdown = null; } var bar = $('menubar'); Array.prototype.forEach.call(bar.querySelectorAll('.menu-btn'), function (b) { b.setAttribute('aria-expanded', 'false'); }); var was = openMenu; openMenu = -1; if (refocus && was >= 0) bar.children[was].focus(); } function showMenu(i, focusFirst) { closeMenu(); openMenu = i; var btn = $('menubar').children[i]; btn.setAttribute('aria-expanded', 'true'); var dd = el('div', 'dropdown'); dd.setAttribute('role', 'menu'); menus[i].items().forEach(function (it) { if (it.sep) { dd.appendChild(el('div', 'sep')); return; } var row = el('div', 'mi'); row.setAttribute('role', 'menuitem'); row.tabIndex = -1; if (it.disabled) row.setAttribute('aria-disabled', 'true'); if (it.checked) { row.appendChild(el('span', 'ck', '\u2713')); row.setAttribute('aria-checked', 'true'); } row.appendChild(el('span', '', it.label)); if (it.key) row.appendChild(el('span', 'k', it.key)); var run = function () { closeMenu(); setTimeout(it.action, 0); }; row.onclick = run; row.onkeydown = function (ev) { var items = Array.prototype.filter.call(dd.children, function (c) { return c.classList.contains('mi') && c.getAttribute('aria-disabled') !== 'true'; }); var at = items.indexOf(row); if (ev.key === 'ArrowDown') { ev.preventDefault(); items[(at + 1) % items.length].focus(); } else if (ev.key === 'ArrowUp') { ev.preventDefault(); items[(at + items.length - 1) % items.length].focus(); } else if (ev.key === 'Enter' || ev.key === ' ') { ev.preventDefault(); run(); } else if (ev.key === 'Escape') { ev.preventDefault(); closeMenu(true); } else if (ev.key === 'ArrowRight' || ev.key === 'ArrowLeft') { ev.preventDefault(); var n = (i + (ev.key === 'ArrowRight' ? 1 : menus.length - 1)) % menus.length; showMenu(n, true); $('menubar').children[n].focus(); var first = dropdown && dropdown.querySelector('.mi:not([aria-disabled=true])'); if (first) first.focus(); } }; dd.appendChild(row); }); document.body.appendChild(dd); var r = btn.getBoundingClientRect(); dd.style.left = Math.max(0, Math.min(r.left, window.innerWidth - dd.offsetWidth - 4)) + 'px'; dd.style.top = r.bottom + 'px'; dropdown = dd; if (focusFirst) { var f = dd.querySelector('.mi:not([aria-disabled=true])'); if (f) f.focus(); } } document.addEventListener('mousedown', function (ev) { if (dropdown && !dropdown.contains(ev.target) && !ev.target.closest('.menu-btn')) closeMenu(); }); window.addEventListener('blur', function () { closeMenu(); });