Add project files
This commit is contained in:
commit
ba5971a788
37 files changed
+11180
No files matched your search
+226
@@ -0,0 +1,226 @@
|
||||
(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(); });
|
||||
Reference in new issue
Block a user