diff options
Diffstat (limited to 'html')
-rw-r--r-- | html/banner256.png | bin | 0 -> 5647 bytes | |||
-rw-r--r-- | html/index.html | 34 | ||||
-rw-r--r-- | html/pdf-icon-100.png | bin | 0 -> 4526 bytes | |||
-rw-r--r-- | html/pdf-icon-30.png | bin | 0 -> 1356 bytes | |||
-rw-r--r-- | html/pdf-icon-50.png | bin | 0 -> 2317 bytes | |||
-rw-r--r-- | html/pdf-icon.svg | 56 | ||||
-rw-r--r-- | html/stats.html | 35 | ||||
-rw-r--r-- | html/stats.js | 96 | ||||
-rw-r--r-- | html/whiteboard.css | 150 | ||||
-rw-r--r-- | html/whiteboard.js | 351 |
10 files changed, 722 insertions, 0 deletions
diff --git a/html/banner256.png b/html/banner256.png Binary files differnew file mode 100644 index 0000000..89efb4f --- /dev/null +++ b/html/banner256.png 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 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"/> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <meta name="keywords" content="Reichwein, Whiteboard"> + <title>Reichwein Whiteboard</title> + <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon"/> + <link rel="stylesheet" type="text/css" href="whiteboard.css"/> + <script src="libwhiteboard.js"></script> + <script src="whiteboard.js"></script> + </head> + <body onload="init();"> + <div class="qrwindow" id="qrwindow" hidden> + <img class="qrcode" id="qrcode"></img> + </div> + <div class="page"> + <h1><img class="banner" src="banner256.png" alt="Reichwein.IT"/> Whiteboard</h1> + <textarea id="board" name="board"></textarea> + <br/> + <br/> + <button class="button" onclick="on_new_page();">New page</button> + <button class="button" onclick="on_qrcode_click();">QR code</button> + <span id="status">Starting up...</span> + <button class="buttonred" id="reconnect" onclick="on_reconnect_click();" hidden>Reconnect</button> + <span class="helper"></span><img class="center-img clickable" onclick="on_pdf_click();" src="pdf-icon-30.png" height="37" width="30"/></span> + <br/> + <br/> + Reichwein.IT Whiteboard <span id="version"></span> by <a href="https://www.reichwein.it">https://www.reichwein.it</a><br/> + </div> + + <a id="download-a" hidden></a> + </body> +</html> diff --git a/html/pdf-icon-100.png b/html/pdf-icon-100.png Binary files differnew file mode 100644 index 0000000..9a20387 --- /dev/null +++ b/html/pdf-icon-100.png diff --git a/html/pdf-icon-30.png b/html/pdf-icon-30.png Binary files differnew file mode 100644 index 0000000..5f60d2f --- /dev/null +++ b/html/pdf-icon-30.png diff --git a/html/pdf-icon-50.png b/html/pdf-icon-50.png Binary files differnew file mode 100644 index 0000000..62873ea --- /dev/null +++ b/html/pdf-icon-50.png 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 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="75.320129mm" height="92.604164mm" viewBox="0 0 75.320129 92.604164" version="1.1" id="svg8" inkscape:version="0.92.3 (2405546, 2018-03-11)" sodipodi:docname="PDF file icon.svg"> + <title id="title2682">PDF file icon</title> + <defs id="defs2"> + <clipPath clipPathUnits="userSpaceOnUse" id="clipPath839"> + <rect style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" id="rect841" width="68.688263" height="67.886459" x="-74.033661" y="141.44913"/> + </clipPath> + </defs> + <sodipodi:namedview id="base" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="0.98994949" inkscape:cx="122.12727" inkscape:cy="167.02823" inkscape:document-units="mm" inkscape:current-layer="g899" showgrid="false" inkscape:object-nodes="false" inkscape:window-width="1366" inkscape:window-height="705" inkscape:window-x="-8" inkscape:window-y="-8" inkscape:window-maximized="1" inkscape:snap-page="false" inkscape:snap-others="false" fit-margin-top="0" fit-margin-left="0" fit-margin-right="0" fit-margin-bottom="0"/> + <metadata id="metadata5"> + <rdf:RDF> + <cc:Work rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/> + <dc:title>PDF file icon</dc:title> + <dc:date>08/10/2018</dc:date> + <dc:creator> + <cc:Agent> + <dc:title>Adobe Systems</dc:title> + </cc:Agent> + </dc:creator> + <dc:publisher> + <cc:Agent> + <dc:title>CMetalCore</dc:title> + </cc:Agent> + </dc:publisher> + <dc:description>Fuente del texto "PDF": +Franklin Gothic Medium Cond</dc:description> + <cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/"/> + </cc:Work> + <cc:License rdf:about="http://creativecommons.org/licenses/by-sa/4.0/"> + <cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/> + <cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/> + <cc:requires rdf:resource="http://creativecommons.org/ns#Notice"/> + <cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"/> + <cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/> + <cc:requires rdf:resource="http://creativecommons.org/ns#ShareAlike"/> + </cc:License> + </rdf:RDF> + </metadata> + <g inkscape:label="Capa 1" inkscape:groupmode="layer" id="layer1" transform="translate(36.076172,-93.731774)"> + <g id="g899" transform="matrix(1.4843054,0,0,1.4843054,17.471885,-90.243502)"> + <g id="g876"> + <path inkscape:connector-curvature="0" style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ff2116;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.11666656;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" d="m -29.632812,123.94727 c -3.551967,0 -6.44336,2.89347 -6.44336,6.44531 v 49.49804 c 0,3.55185 2.891393,6.44532 6.44336,6.44532 H 8.2167969 c 3.5519661,0 6.4433591,-2.89335 6.4433591,-6.44532 v -40.70117 c 0,0 0.101353,-1.19181 -0.416015,-2.35156 -0.484969,-1.08711 -1.275391,-1.84375 -1.275391,-1.84375 a 1.0584391,1.0584391 0 0 0 -0.0059,-0.008 L 3.5722246,125.7752 a 1.0584391,1.0584391 0 0 0 -0.015625,-0.0156 c 0,0 -0.8017392,-0.76344 -1.9902344,-1.27344 -1.39939552,-0.6005 -2.8417968,-0.53711 -2.8417968,-0.53711 l 0.021484,-0.002 z" id="path890"/> + <path inkscape:connector-curvature="0" style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#f5f5f5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.11666656;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" d="m -29.632812,126.06445 h 28.3789058 a 1.0584391,1.0584391 0 0 0 0.021484,0 c 0,0 1.13480448,0.011 1.96484378,0.36719 0.79889772,0.34282 1.36536982,0.86176 1.36914062,0.86524 1.25e-5,1e-5 0.00391,0.004 0.00391,0.004 l 9.3671868,9.18945 c 0,0 0.564354,0.59582 0.837891,1.20899 0.220779,0.49491 0.234375,1.40039 0.234375,1.40039 a 1.0584391,1.0584391 0 0 0 -0.002,0.0449 v 40.74609 c 0,2.41592 -1.910258,4.32813 -4.3261717,4.32813 H -29.632812 c -2.415914,0 -4.326172,-1.91209 -4.326172,-4.32813 v -49.49804 c 0,-2.41603 1.910258,-4.32813 4.326172,-4.32813 z" id="rect2684"/> + <path sodipodi:nodetypes="scccscccscccccssscacccsccccccccccccccccccccaccccccccc" inkscape:connector-curvature="0" d="m -23.40766,161.09299 c -1.45669,-1.45669 0.11934,-3.45839 4.39648,-5.58397 l 2.69124,-1.33743 1.04845,-2.29399 c 0.57665,-1.26169 1.43729,-3.32036 1.91254,-4.5748 l 0.8641,-2.28082 -0.59546,-1.68793 c -0.73217,-2.07547 -0.99326,-5.19438 -0.52872,-6.31588 0.62923,-1.51909 2.69029,-1.36323 3.50626,0.26515 0.63727,1.27176 0.57212,3.57488 -0.18329,6.47946 l -0.6193,2.38125 0.5455,0.92604 c 0.30003,0.50932 1.1764,1.71867 1.9475,2.68743 l 1.44924,1.80272 1.8033728,-0.23533 c 5.72900399,-0.74758 7.6912472,0.523 7.6912472,2.34476 0,2.29921 -4.4984914,2.48899 -8.2760865,-0.16423 -0.8499666,-0.59698 -1.4336605,-1.19001 -1.4336605,-1.19001 0,0 -2.3665326,0.48178 -3.531704,0.79583 -1.202707,0.32417 -1.80274,0.52719 -3.564509,1.12186 0,0 -0.61814,0.89767 -1.02094,1.55026 -1.49858,2.4279 -3.24833,4.43998 -4.49793,5.1723 -1.3991,0.81993 -2.86584,0.87582 -3.60433,0.13733 z m 2.28605,-0.81668 c 0.81883,-0.50607 2.47616,-2.46625 3.62341,-4.28553 l 0.46449,-0.73658 -2.11497,1.06339 c -3.26655,1.64239 -4.76093,3.19033 -3.98386,4.12664 0.43653,0.52598 0.95874,0.48237 2.01093,-0.16792 z m 21.21809,-5.95578 c 0.80089,-0.56097 0.68463,-1.69142 -0.22082,-2.1472 -0.70466,-0.35471 -1.2726074,-0.42759 -3.1031574,-0.40057 -1.1249,0.0767 -2.9337647,0.3034 -3.2403347,0.37237 0,0 0.993716,0.68678 1.434896,0.93922 0.58731,0.33544 2.0145161,0.95811 3.0565161,1.27706 1.02785,0.31461 1.6224,0.28144 2.0729,-0.0409 z m -8.53152,-3.54594 c -0.4847,-0.50952 -1.30889,-1.57296 -1.83152,-2.3632 -0.68353,-0.89643 -1.02629,-1.52887 -1.02629,-1.52887 0,0 -0.4996,1.60694 -0.90948,2.57394 l -1.27876,3.16076 -0.37075,0.71695 c 0,0 1.971043,-0.64627 2.97389,-0.90822 1.0621668,-0.27744 3.21787,-0.70134 3.21787,-0.70134 z m -2.74938,-11.02573 c 0.12363,-1.0375 0.1761,-2.07346 -0.15724,-2.59587 -0.9246,-1.01077 -2.04057,-0.16787 -1.85154,2.23517 0.0636,0.8084 0.26443,2.19033 0.53292,3.04209 l 0.48817,1.54863 0.34358,-1.16638 c 0.18897,-0.64151 0.47882,-2.02015 0.64411,-3.06364 z" style="fill:#ff2116;fill-opacity:1;stroke-width:0.26458335" id="path2697"/> + <g id="g858"> + <path inkscape:connector-curvature="0" id="path845" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:125%;font-family:'Franklin Gothic Medium Cond';-inkscape-font-specification:'Franklin Gothic Medium Cond';letter-spacing:0px;word-spacing:4.26000023px;fill:#2c2c2c;fill-opacity:1;stroke:none;stroke-width:0.35824656px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="m -20.930423,167.83862 h 2.364986 q 1.133514,0 1.840213,0.2169 0.706698,0.20991 1.189489,0.9446 0.482795,0.72769 0.482795,1.75625 0,0.94459 -0.391832,1.6233 -0.391833,0.67871 -1.056548,0.97958 -0.65772,0.30087 -2.02913,0.30087 h -0.818651 v 3.72941 h -1.581322 z m 1.581322,1.22447 v 3.33058 h 0.783664 q 1.049552,0 1.44838,-0.39184 0.405826,-0.39183 0.405826,-1.27345 0,-0.65772 -0.265887,-1.06355 -0.265884,-0.41282 -0.587747,-0.50378 -0.314866,-0.098 -1.000572,-0.098 z"/> + <path inkscape:connector-curvature="0" id="path847" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:125%;font-family:'Franklin Gothic Medium Cond';-inkscape-font-specification:'Franklin Gothic Medium Cond';letter-spacing:0px;word-spacing:4.26000023px;fill:#2c2c2c;fill-opacity:1;stroke:none;stroke-width:0.35824656px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="m -13.842461,167.83862 h 2.148082 q 1.560333,0 2.4909318,0.55276 0.9375993,0.55276 1.4133973,1.6443 0.482791,1.09153 0.482791,2.42096 0,1.3994 -0.4338151,2.49793 -0.4268149,1.09153 -1.3154348,1.76324 -0.8816233,0.67172 -2.5189212,0.67172 h -2.267031 z m 1.581326,1.26645 v 7.018 h 0.657715 q 1.378411,0 2.001144,-0.9516 0.6227329,-0.95858 0.6227329,-2.5539 0,-3.5125 -2.6238769,-3.5125 z"/> + <path inkscape:connector-curvature="0" id="path849" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:125%;font-family:'Franklin Gothic Medium Cond';-inkscape-font-specification:'Franklin Gothic Medium Cond';letter-spacing:0px;word-spacing:4.26000023px;fill:#2c2c2c;fill-opacity:1;stroke:none;stroke-width:0.35824656px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="m -5.7889096,167.83862 h 5.30372941 v 1.26645 H -4.2075842 v 2.85478 h 2.9807225 v 1.26646 h -2.9807225 v 4.16322 h -1.5813254 z"/> + </g> + </g> + </g> + </g> +</svg>
\ 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 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"/> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <meta name="keywords" content="Reichwein, Whiteboard"> + <title>Reichwein Whiteboard</title> + <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon"/> + <link rel="stylesheet" type="text/css" href="whiteboard.css"/> + <script src="stats.js"></script> + </head> + <body onload="init();"> + <div class="qrwindow" id="qrwindow" hidden> + <img class="qrcode" id="qrcode"></img> + </div> + <div class="page"> + <h1><img class="banner" src="banner256.png" alt="Reichwein.IT"/> Whiteboard</h1> + <h2>Statistics</h2> + <table> + <tr><td>Active Connections:</td><td id="numberofconnections" align="right"></td><td></td></tr> + <tr><td>Number of Documents:</td><td id="numberofdocuments" align="right"></td><td></td></tr> + <tr><td>Database Size (gross):</td><td id="dbsizegross" align="right"></td><td>Bytes</td></tr> + <tr><td>Database Size (net):</td><td id="dbsizenet" align="right"></td><td>Bytes</td></tr> + </table> + <br/> + <span id="status">Starting up...</span> + <button class="buttonred" id="reconnect" onclick="on_reconnect_click();" hidden>Reconnect</button> + <br/> + <br/> + Reichwein.IT Whiteboard <span id="version"></span> by <a href="https://www.reichwein.it">https://www.reichwein.it</a><br/> + </div> + + <a id="download-a" hidden></a> + </body> +</html> 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("<request><command>getversion</command></request>"); + websocket.send("<request><command>getstats</command></request>"); + 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("<request><command>getversion</command></request>"); + websocket.send("<request><command>getfile</command><id>" + get_id() + "</id></request>"); + 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("<request><command>newid</command></request>"); +} + +// 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("<request></request>", "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("<request></request>", "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("<request></request>", "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("<request><command>pdf</command><id>" + get_id() + "</id></request>"); +} + |