2024-09-30 19:36:06 +02:00

186 lines
5.5 KiB
JavaScript

const statusElement = document.getElementById('status');
const cellTmpl = document.querySelector('#cell-template .cell');
const plotErrTmpl = document.querySelector('#plot-template .plot-err');
const cells = document.getElementById('cells');
/** @typedef {node: HTMLElement, input: HTMLInputElement, result: HTMLElement} Cell */
/** @type {Cell} */
let curCell;
/** @returns {Cell} */
const getCell = (cellNode) => ({
node: cellNode,
input: cellNode.querySelector('.cell-input'),
result: cellNode.querySelector('.cell-result'),
});
let cellNum = 0;
function newCell() {
if (curCell) {
curCell.input.readOnly = true;
}
curCell = getCell(cellTmpl.cloneNode(true));
cellNum += 1;
curCell.node.id = 'cell_' + cellNum;
curCell.input.addEventListener('keydown', onKey);
cells.append(curCell.node);
focusCell(curCell);
}
/** @param {Cell} cell */
function focusCell(cell) {
cell.input.focus({ preventScroll: true });
if (cell.input.readOnly) {
cell.input.select();
}
cell.node.scrollIntoView({ behavior: 'smooth' });
}
/** @param {KeyboardEvent} ev */
function onKey(ev) {
/** @type {HTMLInputElement} */
const inp = ev.target;
if (ev.key === 'Enter') {
const text = inp.value;
if (inp === curCell.input) {
if (text.trim() !== '') {
curCell.result.textContent = calc.calculateAndPrint(text, 1000);
}
newCell();
} else {
if (text.trim() !== '') {
curCell.input.value = text;
}
focusCell(curCell);
}
} else if (ev.key === 'ArrowUp' || ev.key === 'ArrowDown') {
const cellNode = inp.parentElement;
const adjacent =
ev.key === 'ArrowUp'
? 'previousElementSibling'
: 'nextElementSibling';
let adjacentCellNode = cellNode;
do {
// this would be null if e.g. we're at the top and try to go up
adjacentCellNode = adjacentCellNode[adjacent];
} while (
adjacentCellNode != null &&
!adjacentCellNode.classList.contains('cell')
);
if (adjacentCellNode) {
focusCell(getCell(adjacentCellNode));
}
ev.preventDefault();
}
}
const emptySvg = `<?xml version="1.0" encoding="utf-8" standalone="no"?>
<svg
width="600" height="480"
viewBox="0 0 600 480"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
/>`;
let emptySvgUrl;
let plot_id = 0;
let gnuplotWorker;
const makeSvgUrl = (data) =>
URL.createObjectURL(new Blob([data], { type: 'image/svg+xml' }));
function runGnuplot(data_files, commands, extra_commandline, persist) {
if (!gnuplotWorker) {
gnuplotWorker = new Worker('gnuplot-worker.js');
gnuplotWorker.addEventListener('message', (ev) => {
const { id, output } = ev.data;
const plot = document.getElementById('plot_' + id);
if (output) {
plot.src = makeSvgUrl(output);
setTimeout(() => {
focusCell(curCell);
}, 10);
} else {
plot.replaceWith(plotErrTmpl.cloneNode(true));
}
});
}
if (!emptySvgUrl) {
emptySvgUrl = makeSvgUrl(emptySvg);
}
const img = new Image();
img.classList.add('plot');
img.src = emptySvgUrl;
const id = plot_id++;
img.id = 'plot_' + id;
curCell.node.insertAdjacentElement('afterend', img);
gnuplotWorker.postMessage({
data_files,
commands,
extra_commandline,
persist,
id,
});
return true;
}
var Module = {
postRun: () => {
console.time('new');
window.calc = new Module.Calculator();
calc.loadGlobalDefinitions();
console.timeEnd('new');
newCell();
},
print: function (text) {
if (arguments.length > 1)
text = Array.prototype.slice.call(arguments).join(' ');
console.log(text);
},
printErr: function (text) {
if (arguments.length > 1)
text = Array.prototype.slice.call(arguments).join(' ');
console.error(text);
},
setStatus: function (text) {
if (!Module.setStatus.last)
Module.setStatus.last = { time: Date.now(), text: '' };
if (text === Module.setStatus.last.text) return;
var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
var now = Date.now();
if (m && now - Module.setStatus.last.time < 30) return; // if this is a progress update, skip it if too soon
Module.setStatus.last.time = now;
Module.setStatus.last.text = text;
if (m) {
text = m[1];
}
statusElement.innerHTML = text;
},
totalDependencies: 0,
monitorRunDependencies: function (left) {
this.totalDependencies = Math.max(this.totalDependencies, left);
Module.setStatus(
left
? 'Preparing... (' +
(this.totalDependencies - left) +
'/' +
this.totalDependencies +
')'
: 'All downloads complete.'
);
},
};
Module.setStatus('Downloading...');
window.onerror = function (event) {
// TODO: do not warn on ok events like simulating an infinite loop or exitStatus
Module.setStatus('Exception thrown, see JavaScript console');
Module.setStatus = function (text) {
if (text) Module.printErr('[post-exception status] ' + text);
};
};