// started on main page load
function init() {
init_board();
}
var revision;
var modify_in_progress = 0;
var baseline = ""; // data contents relating to revision, acknowledged by server
var baseline_candidate = ""; // will become baseline, after ack by server
// helper for breaking feedback loop
var caretpos = 0;
function set_status(message)
{
if (message == "") {
document.getElementById("status").textContent = message;
document.getElementById("status").style.display = 'inline';
} else {
document.getElementById("status").textContent = "";
document.getElementById("status").style.display = 'none';
}
}
function showQRWindow()
{
document.getElementById("qrwindow").style.display = 'block';
}
function hideQRWindow()
{
document.getElementById("qrwindow").style.display = 'none';
}
var websocket;
//
// Callbacks for websocket data of different types
//
function on_getfile(data, rev, pos)
{
var board = document.getElementById("board");
if (board.value != data) {
board.value = data;
}
revision = rev;
baseline = data;
textAreaSetPos("board", pos);
}
function on_getdiff(diff, rev, pos)
{
if (rev != revision + 1)
console.log("Revision skipped on diff receive: " + rev + " after " + revision);
var board = document.getElementById("board");
var old_version_ptr = allocateUTF8(board.value);
var diff_ptr = allocateUTF8(new XMLSerializer().serializeToString(diff));
var new_version_ptr = Module._diff_apply(old_version_ptr, diff_ptr);
var data = UTF8ToString(new_version_ptr);
board.value = data;
_free(old_version_ptr);
_free(new_version_ptr);
_free(diff_ptr);
revision = rev;
baseline = data;
textAreaSetPos("board", pos);
}
function on_getpos(pos)
{
textAreaSetPos("board", pos);
}
function on_newid(id)
{
var new_location = document.location.origin + document.location.pathname + '?id=' + id;
window.location.href = new_location;
}
function on_qrcode(png)
{
var url = "data:image/png;base64," + png;
var img = document.getElementById("qrcode");
img.src = url;
showQRWindow();
}
function on_version(version)
{
document.getElementById("version").textContent = version;
}
function on_pdf(pdf)
{
var a = document.getElementById("download-a");
a.href = "data:application/pdf;base64," + pdf;
a.download = get_id() + ".pdf"
a.click();
}
function on_modify_ack(rev)
{
if (rev != revision + 1)
console.log("Revision skipped on published local change: " + rev + " after " + revision);
revision = rev;
baseline = baseline_candidate;
modify_in_progress = 0;
}
function on_message(e) {
var parser = new DOMParser();
var xmlDocument = parser.parseFromString(e.data, "text/xml");
var type = xmlDocument.getElementsByTagName("type")[0].textContent;
if (type == "getfile") {
on_getfile(xmlDocument.getElementsByTagName("data")[0].textContent,
parseInt(xmlDocument.getElementsByTagName("revision")[0].textContent),
parseInt(xmlDocument.getElementsByTagName("pos")[0].textContent));
} else if (type == "getdiff") {
on_getdiff(xmlDocument.getElementsByTagName("diff")[0],
parseInt(xmlDocument.getElementsByTagName("revision")[0].textContent),
parseInt(xmlDocument.getElementsByTagName("pos")[0].textContent));
} else if (type == "getpos") {
on_getpos(parseInt(xmlDocument.getElementsByTagName("pos")[0].textContent));
} else if (type == "modify") {
on_modify_ack(parseInt(xmlDocument.getElementsByTagName("revision")[0].textContent));
} else if (type == "newid") {
on_newid(xmlDocument.getElementsByTagName("id")[0].textContent);
} else if (type == "qrcode") {
on_qrcode(xmlDocument.getElementsByTagName("png")[0].textContent);
} else if (type == "version") {
on_version(xmlDocument.getElementsByTagName("version")[0].textContent);
} else if (type == "pdf") {
on_pdf(xmlDocument.getElementsByTagName("pdf")[0].textContent);
} else if (type == "error") {
alert(xmlDocument.getElementsByTagName("message")[0].textContent);
} else {
alert("Unhandled message type: " + e.data + "|" + type);
}
}
function handleSelection() {
const activeElement = document.activeElement
if (activeElement && activeElement.id === 'board') {
if (caretpos != activeElement.selectionStart) {
on_selectionchange(activeElement.selectionStart);
caretpos = activeElement.selectionStart;
}
}
}
function connect_websocket() {
document.getElementById("reconnect").style.display = 'none';
set_status("Connecting...");
var newlocation = location.origin + location.pathname;
newlocation = newlocation.replace(/^http/, 'ws');
if (newlocation.slice(-1) != "/")
newlocation += "/";
newlocation += "websocket";
websocket = new WebSocket(newlocation);
websocket.onmessage = function(e) { on_message(e); };
websocket.onopen = function(e) {
const searchParams = (new URL(document.location)).searchParams;
if (!searchParams.has('id')) {
redirect_to_new_page();
return;
}
websocket.send("getversion");
websocket.send("getfile" + get_id() + "");
set_status(""); // ok
document.getElementById("board").focus();
};
websocket.onclose = function(e) {
alert("Server connection closed.");
document.getElementById("reconnect").style.display = 'inline';
document.getElementById("reconnect").focus();
};
websocket.onerror = function(e) {
alert("Error: Server connection closed.");
document.getElementById("reconnect").style.display = 'inline';
document.getElementById("reconnect").focus();
};
}
// button in html
function on_reconnect_click() {
connect_websocket();
}
function init_board() {
set_status("Loading...");
Module.onRuntimeInitialized = () => {
connect_websocket();
};
var board = document.getElementById("board");
board.addEventListener("input", function() {on_input(); });
// Need this workaround (different from direct on_selectionchange) for Chrome.
// Otherwise, callback will not be called on Chrome.
document.addEventListener("selectionchange", handleSelection);
//board.addEventListener("selectionchange", function() {on_selectionchange(); });
document.getElementById("qrwindow").onclick = function() {
hideQRWindow();
}
document.onkeydown = function(evt) {
if (evt.key == "Escape") {
hideQRWindow();
}
}
document.getElementById("board").focus();
}
function get_id()
{
const searchParams = (new URL(document.location)).searchParams;
return searchParams.get('id');
}
// from html
function on_new_page()
{
redirect_to_new_page();
}
function redirect_to_new_page()
{
websocket.send("newid");
}
// local change done
function on_input()
{
if (modify_in_progress == 1) {
console.log("Deferring on_input handler by 100ms");
setTimeout(function(){on_input();}, 100); // re-try after 100ms
return;
}
modify_in_progress = 1;
var parser = new DOMParser();
var xmlDocument = parser.parseFromString("", "text/xml");
var requestElement = xmlDocument.getElementsByTagName("request")[0];
var commandElement = xmlDocument.createElement("command");
commandElement.appendChild(document.createTextNode("modify"));
requestElement.appendChild(commandElement);
var idElement = xmlDocument.createElement("id");
idElement.appendChild(document.createTextNode(get_id()));
requestElement.appendChild(idElement);
baseline_candidate = document.getElementById("board").value;
if (baseline == baseline_candidate) {
modify_in_progress = 0;
return;
}
var revisionElement = xmlDocument.createElement("baserev");
revisionElement.appendChild(document.createTextNode(revision));
requestElement.appendChild(revisionElement);
var old_version = allocateUTF8(baseline);
var new_version = allocateUTF8(baseline_candidate);
var diff = Module._diff_create(old_version, new_version);
var diffDocument = parser.parseFromString(UTF8ToString(diff), "text/xml");
_free(old_version);
_free(new_version);
_free(diff);
requestElement.appendChild(xmlDocument.importNode(diffDocument.getElementsByTagName("diff")[0], true));
var posElement = xmlDocument.createElement("pos");
posElement.appendChild(document.createTextNode(document.getElementById("board").selectionStart));
requestElement.appendChild(posElement);
websocket.send(new XMLSerializer().serializeToString(xmlDocument));
}
// for cursor position
function on_selectionchange(pos)
{
var parser = new DOMParser();
var xmlDocument = parser.parseFromString("", "text/xml");
var requestElement = xmlDocument.getElementsByTagName("request")[0];
var commandElement = xmlDocument.createElement("command");
commandElement.appendChild(document.createTextNode("cursorpos"));
requestElement.appendChild(commandElement);
var idElement = xmlDocument.createElement("id");
idElement.appendChild(document.createTextNode(get_id()));
requestElement.appendChild(idElement);
var posElement = xmlDocument.createElement("pos");
posElement.appendChild(document.createTextNode(pos));
requestElement.appendChild(posElement);
websocket.send(new XMLSerializer().serializeToString(xmlDocument));
}
function textAreaSetPos(id, pos)
{
if (document.getElementById(id).selectionStart != pos) {
document.getElementById(id).selectionStart = pos;
document.getElementById(id).selectionEnd = pos;
caretpos = pos;
}
}
// HTML button
function on_qrcode_click()
{
var parser = new DOMParser();
var xmlDocument = parser.parseFromString("", "text/xml");
var requestElement = xmlDocument.getElementsByTagName("request")[0];
var commandElement = xmlDocument.createElement("command");
commandElement.appendChild(document.createTextNode("qrcode"));
requestElement.appendChild(commandElement);
var idElement = xmlDocument.createElement("url");
idElement.appendChild(document.createTextNode(document.location));
requestElement.appendChild(idElement);
websocket.send(new XMLSerializer().serializeToString(xmlDocument));
}
function on_pdf_click()
{
websocket.send("pdf" + get_id() + "");
}