From c9fa963e71258c5adfb71cf1996cd1bcb33df0bb Mon Sep 17 00:00:00 2001 From: Roland Reichwein Date: Sun, 26 Feb 2023 08:54:17 +0100 Subject: Start with copy of whiteboard --- html/banner256.png | Bin 0 -> 5647 bytes html/index.html | 34 +++++ html/pdf-icon-100.png | Bin 0 -> 4526 bytes html/pdf-icon-30.png | Bin 0 -> 1356 bytes html/pdf-icon-50.png | Bin 0 -> 2317 bytes html/pdf-icon.svg | 56 ++++++++ html/stats.html | 35 +++++ html/stats.js | 96 ++++++++++++++ html/whiteboard.css | 150 +++++++++++++++++++++ html/whiteboard.js | 351 ++++++++++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 722 insertions(+) create mode 100644 html/banner256.png create mode 100644 html/index.html create mode 100644 html/pdf-icon-100.png create mode 100644 html/pdf-icon-30.png create mode 100644 html/pdf-icon-50.png create mode 100644 html/pdf-icon.svg create mode 100644 html/stats.html create mode 100644 html/stats.js create mode 100644 html/whiteboard.css create mode 100644 html/whiteboard.js (limited to 'html') diff --git a/html/banner256.png b/html/banner256.png new file mode 100644 index 0000000..89efb4f Binary files /dev/null and b/html/banner256.png differ diff --git a/html/index.html b/html/index.html new file mode 100644 index 0000000..b72e23b --- /dev/null +++ b/html/index.html @@ -0,0 +1,34 @@ + + + + + + + Reichwein Whiteboard + + + + + + + +
+

Whiteboard

+ +
+
+ + + Starting up... + + +
+
+ Reichwein.IT Whiteboard by https://www.reichwein.it
+
+ + + + diff --git a/html/pdf-icon-100.png b/html/pdf-icon-100.png new file mode 100644 index 0000000..9a20387 Binary files /dev/null and b/html/pdf-icon-100.png differ diff --git a/html/pdf-icon-30.png b/html/pdf-icon-30.png new file mode 100644 index 0000000..5f60d2f Binary files /dev/null and b/html/pdf-icon-30.png differ diff --git a/html/pdf-icon-50.png b/html/pdf-icon-50.png new file mode 100644 index 0000000..62873ea Binary files /dev/null and b/html/pdf-icon-50.png differ diff --git a/html/pdf-icon.svg b/html/pdf-icon.svg new file mode 100644 index 0000000..8b30bbb --- /dev/null +++ b/html/pdf-icon.svg @@ -0,0 +1,56 @@ + + + + PDF file icon + + + + + + + + + + image/svg+xml + + PDF file icon + 08/10/2018 + + + Adobe Systems + + + + + CMetalCore + + + Fuente del texto "PDF": +Franklin Gothic Medium Cond + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/html/stats.html b/html/stats.html new file mode 100644 index 0000000..3f7e141 --- /dev/null +++ b/html/stats.html @@ -0,0 +1,35 @@ + + + + + + + Reichwein Whiteboard + + + + + + +
+

Whiteboard

+

Statistics

+ + + + + +
Active Connections:
Number of Documents:
Database Size (gross):Bytes
Database Size (net):Bytes
+
+ Starting up... + +
+
+ Reichwein.IT Whiteboard by https://www.reichwein.it
+
+ + + + diff --git a/html/stats.js b/html/stats.js new file mode 100644 index 0000000..89c674a --- /dev/null +++ b/html/stats.js @@ -0,0 +1,96 @@ +// started on main page load +function init() { + init_stats(); +} + +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'; + } +} + +var websocket; + +// +// Callbacks for websocket data of different types +// + +function on_stats(numberofdocuments, numberofconnections, dbsizegross, dbsizenet) +{ + document.getElementById("numberofdocuments").textContent = numberofdocuments; + document.getElementById("numberofconnections").textContent = numberofconnections; + document.getElementById("dbsizegross").textContent = dbsizegross; + document.getElementById("dbsizenet").textContent = dbsizenet; +} + +function on_version(version) +{ + document.getElementById("version").textContent = version; +} + +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 == "stats") { + on_stats(xmlDocument.getElementsByTagName("numberofdocuments")[0].textContent, + xmlDocument.getElementsByTagName("numberofconnections")[0].textContent, + xmlDocument.getElementsByTagName("dbsizegross")[0].textContent, + xmlDocument.getElementsByTagName("dbsizenet")[0].textContent); + } else if (type == "version") { + on_version(xmlDocument.getElementsByTagName("version")[0].textContent); + } else if (type == "error") { + alert(xmlDocument.getElementsByTagName("message")[0].textContent); + } else { + alert("Unhandled message type: " + e.data + "|" + type); + } +} + +function connect_websocket() { + document.getElementById("reconnect").style.display = 'none'; + set_status("Connecting..."); + var newlocation = location.origin + location.pathname; + newlocation = newlocation.replace(/^http/, 'ws'); + newlocation = newlocation.replace(/stats.html$/, ''); + if (newlocation.slice(-1) != "/") + newlocation += "/"; + newlocation += "websocket"; + + websocket = new WebSocket(newlocation); + + websocket.onmessage = function(e) { on_message(e); }; + + websocket.onopen = function(e) { + websocket.send("getversion"); + websocket.send("getstats"); + set_status(""); // ok + }; + + websocket.onclose = function(e) { + alert("Server connection closed."); + document.getElementById("reconnect").style.display = 'inline'; + }; + + websocket.onerror = function(e) { + alert("Error: Server connection closed."); + document.getElementById("reconnect").style.display = 'inline'; + }; +} + +// button in html +function on_reconnect_click() { + connect_websocket(); +} + +function init_stats() { + set_status("Loading..."); + connect_websocket(); +} + diff --git a/html/whiteboard.css b/html/whiteboard.css new file mode 100644 index 0000000..2d222d5 --- /dev/null +++ b/html/whiteboard.css @@ -0,0 +1,150 @@ +body { + font-family: sans-serif; +} + +figcaption { + text-align: center; + font-size: 8px; + color: #808080; +} + +figure { + display: inline-block; +} + +p { + margin: 30px 0px 30px 0px; +} + +div.status { + color: #FF0000; +} + +span.helper { + display: inline-block; + height: 100%; + vertical-align: middle; +} + +img.center-img { + vertical-align: middle; +} + +.clickable { + cursor: pointer; +} + +textarea { + /* + height: 30vh; + padding: 1em; + font-size: 1.5em; + text-align: left; + border: 1px solid #000; + */ + box-sizing: border-box; + resize: none; + + width: 100%; + height: 540px; +} + +.qrwindow { + position: fixed; + top: 50%; + left: 50%; + width: 400px; + height: 400px; + margin-top: -200px; + margin-left: -200px; + background-color: #FFFFFF; + opacity: 1; + z-index: 10; + border-width: 3px; + border-style: solid; + border-color: #FFFFFF; + padding: 10pt; + box-sizing: border-box; +} + +.qrcode { + width: 374px; + height: 374px; + + image-rendering: optimizeSpeed; /* */ + image-rendering: -moz-crisp-edges; /* Firefox */ + image-rendering: -o-crisp-edges; /* Opera */ + image-rendering: -webkit-optimize-contrast; /* Chrome (and Safari) */ + image-rendering: pixelated; /* Chrome as of 2019 */ + image-rendering: optimize-contrast; /* CSS3 Proposed */ + -ms-interpolation-mode: nearest-neighbor; /* IE8+ */ +} + +.mobile { + width: 300px; + border-width: 80px 15px 80px 15px; + border-style: solid; + border-radius: 30px; + border-color: #000000; +} + +.logo { + display: block; + margin: 0 auto; +} + +.screenshot { + width: 400px; + border: 2px solid; + border-color: #8888AA; +} + +img.banner { + vertical-align: -5px; +} + +.button { + color:#FFFFFF; + background-color:#50B050; + text-decoration: none; + padding: 15px 20px; + font-size: 16px; + border: none; + border-radius: 6px; + cursor: pointer; +} + +.buttonred { + color:#FFFFFF; + background-color:#B05050; + text-decoration: none; + padding: 15px 20px; + font-size: 16px; + border: none; + border-radius: 6px; + cursor: pointer; +} + +@media only screen and (min-width: 1px) and (max-width: 630px) { + +.qrwindow { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + margin: 0; +} + +} + +@media only screen and (min-width: 631px) and (max-width: 950px) { +} + +@media only screen and (min-width: 951px) { + div.page { + max-width: 950px; + width: 100%; + margin: 0 auto; + } +} diff --git a/html/whiteboard.js b/html/whiteboard.js new file mode 100644 index 0000000..379c757 --- /dev/null +++ b/html/whiteboard.js @@ -0,0 +1,351 @@ +// 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() + ""); +} + -- cgit v1.2.3