Add remote signature pad (close #11)

master
Skylar Ittner 3 years ago
parent 068640c548
commit 34fd854a99

@ -100,48 +100,4 @@ html, body {
.modal-dialog.modal-xxl {
max-width: 90vw;
}
}
select option {
color: var(--bs-dark);
}
.alert {
color: var(--bs-dark);
}
.theme-purple {
background-image: linear-gradient(90deg,#33b7e2,#5e62b0,#dc307c);
}
.theme-green {
background-image: linear-gradient(90deg, #9ebd13 0%, #008552 100%);
}
.theme-red {
background-image: linear-gradient(90deg, #d53369 0%, #daae51 100%);
}
.theme-aqua {
background-image: linear-gradient(90deg, #00d2ff 0%, #3a47d5 100%);
}
.theme-ocean {
background-image: linear-gradient(90deg, #1CB5E0 0%, #000851 100%);
}
.theme-dusty {
background-image: linear-gradient(90deg, #fcff9e 0%, #c67700 100%);
}
.theme-lilac {
background-image: linear-gradient(90deg, #efd5ff 0%, #515ada 100%);
}
.theme-spring {
background-image: linear-gradient(90deg, #00C9FF 0%, #92FE9D 100%);
}
.theme-shadow {
background-image: linear-gradient(90deg, #4b6cb7 0%, #182848 100%);
}

@ -0,0 +1,54 @@
/*
Copyright 2021 Netsyms Technologies.
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
Created on : Jul 17, 2021, 12:29:13 PM
Author : Skylar Ittner
*/
select option {
color: var(--bs-dark);
}
.alert {
color: var(--bs-dark);
}
.theme-purple {
background-image: linear-gradient(90deg,#33b7e2,#5e62b0,#dc307c);
}
.theme-green {
background-image: linear-gradient(90deg, #9ebd13 0%, #008552 100%);
}
.theme-red {
background-image: linear-gradient(90deg, #d53369 0%, #daae51 100%);
}
.theme-aqua {
background-image: linear-gradient(90deg, #00d2ff 0%, #3a47d5 100%);
}
.theme-ocean {
background-image: linear-gradient(90deg, #1CB5E0 0%, #000851 100%);
}
.theme-dusty {
background-image: linear-gradient(90deg, #fcff9e 0%, #c67700 100%);
}
.theme-lilac {
background-image: linear-gradient(90deg, #efd5ff 0%, #515ada 100%);
}
.theme-spring {
background-image: linear-gradient(90deg, #00C9FF 0%, #92FE9D 100%);
}
.theme-shadow {
background-image: linear-gradient(90deg, #4b6cb7 0%, #182848 100%);
}

@ -10,6 +10,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="css/bootstrap.min.css" />
<link rel="stylesheet" href="css/main.css" />
<link rel="stylesheet" href="css/theme.css" />
<link rel="icon" href="img/icon.svg" />
<div class="modal fade" id="settingsModal" tabindex="-1" aria-labelledby="settingsModalLabel" aria-hidden="true">
@ -124,6 +125,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
<div class="modal-body">
<div class="btn btn-warning d-inline-block" onclick="resizeSignaturePadCanvas();signaturePad.clear();"><i class="fas fa-eraser"></i> Erase</div>
<div class="btn btn-primary" onclick="signaturePadUndo();"><i class="fas fa-undo"></i> Undo</div>
<div class="btn btn-info" onclick="signatureRemoteModalOpen();"><i class="fas fa-mobile-alt"></i> Sign with Phone</div>
</div>
<div class="modal-body">
<div class="signature-wrapper">
@ -139,6 +141,34 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
</div>
</div>
<div class="modal fade" id="signatureRemoteModal" tabindex="-1" aria-labelledby="signatureRemoteModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="signatureRemoteModalLabel"><i class="fas fa-mobile"></i> Remote Signature Pad Connection</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="list-group">
<div class="list-group-item">
Use a phone, tablet, or other touchscreen device as a signature pad. It must be connected to the same network (e.g. WiFi hotspot) as this computer.
On the other device, go to the website shown below, or open a camera or barcode scanner app and point it the QR code shown.
</div>
<div class="list-group-item text-center">
<span id="signatureRemoteUrlLabel"></span>
</div>
<div class="list-group-item">
<div id="signatureRemoteQRCode" class="d-flex justify-content-center p-2" style="background-color: white; border-radius: 0.5em;"></div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="verifyModal" tabindex="-1" aria-labelledby="verifyModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
@ -263,6 +293,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
<script src="node_modules/pdfjs-dist/build/pdf.min.js"></script>
<script src="node_modules/jspdf/dist/jspdf.umd.min.js"></script>
<script src="node_modules/signature_pad/dist/signature_pad.umd.min.js"></script>
<script src="node_modules/qrcodejs/qrcode.min.js"></script>
<script src="js/kbpgp-2.1.15.js"></script>
<script src="js/svg-to-image.js"></script>
<script src="js/data.js"></script>
@ -273,4 +304,5 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
<script src="js/certbuilder.js"></script>
<script src="js/drawtools.js"></script>
<script src="js/pdf.js"></script>
<script src="js/main.js"></script>
<script src="js/main.js"></script>
<script src="js/sigserver.js"></script>

@ -117,6 +117,63 @@ function activateNotarySignaturePad() {
};
}
function signatureRemoteModalOpen() {
var url = getSignatureServerUrl();
$("#signatureRemoteUrlLabel").text(url);
new QRCode(document.getElementById("signatureRemoteQRCode"), url);
new bootstrap.Modal(document.getElementById('signatureRemoteModal')).show();
}
function handleRemoteSignatureData(data) {
if (!$("#signatureModal").hasClass("show")) {
return;
}
var canvas = document.getElementById("signaturecanvas");
signaturePad.fromData(scaleSignatureData(data, canvas.width, canvas.height));
}
function scaleSignatureData(data, width, height) {
// Get width and height of data
// Get the minimum positions too for adding a bit of margin
var oldwidth = 0;
var oldheight = 0;
var oldminX = 999999999;
var oldminY = 999999999;
for (var i = 0; i < data.length; i++) {
for (var j = 0; j < data[i].points.length; j++) {
oldwidth = Math.max(data[i].points[j].x, oldwidth);
oldheight = Math.max(data[i].points[j].y, oldheight);
oldminX = Math.min(data[i].points[j].x, oldminX);
oldminY = Math.min(data[i].points[j].y, oldminY);
}
}
var xPad = oldminX / 2;
var yPad = oldminY / 2;
// Calculate scale ratios
var scaleX = (oldwidth + xPad * 2) / width;
var scaleY = (oldheight + yPad * 2) / height;
// Pick the largest scale ratio and use only that one so it maintains aspect ratio
var scale = Math.max(scaleX, scaleY);
// Loop over the old data and scale the points into a new signature data object
var newdata = [];
for (var i = 0; i < data.length; i++) {
var newpoints = [];
for (var j = 0; j < data[i].points.length; j++) {
newpoints.push({
time: data[i].points[j].time,
x: (data[i].points[j].x + xPad) / scale,
y: (data[i].points[j].y + yPad) / scale
});
}
newdata.push({
color: data[i].color,
points: newpoints
});
}
return newdata;
}
function trimAndShrinkSVG(svgstring) {
var div = document.getElementById('svgtrimbox');
div.innerHTML = svgstring;

@ -0,0 +1,68 @@
/*
* Copyright 2021 Netsyms Technologies.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
const http_port = 36744; // dialed ENSIG on phone keypad (eNotary Signature)
var http = require('http');
var server = http.createServer(function (req, res) {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST');
if (req.method == "GET") {
console.log("Serving " + req.url);
switch (req.url) {
case "/":
res.writeHead(200, {'Content-Type': 'text/html'});
var index = getFileAsString("./sigserver/index.html");
index = index.replace("theme-BLANK", "theme-" + getStorage("color_theme"));
res.end(index);
break;
case "/bootstrap.min.css":
res.writeHead(200, {'Content-Type': 'text/css'});
res.end(getFileAsString("./css/bootstrap.min.css"));
break;
case "/theme.css":
res.writeHead(200, {'Content-Type': 'text/css'});
res.end(getFileAsString("./css/theme.css"));
break;
case "/fontawesome/all.min.js":
res.writeHead(200, {'Content-Type': 'text/javascript'});
res.end(getFileAsString("./node_modules/@fortawesome/fontawesome-free/js/all.min.js"));
break;
case "/signature_pad.umd.min.js":
res.writeHead(200, {'Content-Type': 'text/javascript'});
res.end(getFileAsString("./node_modules/signature_pad/dist/signature_pad.umd.min.js"));
break;
case "/img/signature-line.svg":
res.writeHead(200, {'Content-Type': 'image/svg+xml'});
res.end(getFileAsString("./img/signature-line.svg"));
break;
default:
res.writeHead(404);
res.end("404 not found.");
}
} else if (req.method == "POST") {
var body = '';
req.on('data', function (data) {
body += data;
});
req.on('end', function () {
handleRemoteSignatureData(JSON.parse(body));
});
res.writeHead(201);
}
});
server.listen(http_port); //3 - listen for any incoming requests
console.log('Signature collection pad service running on port ' + http_port + '.');
function getSignatureServerUrl() {
const prepareUrls = require('local-ip-url/prepareUrls');
var urls = prepareUrls({
protocol: 'http',
host: '0.0.0.0',
port: http_port
});
return urls.lanUrl;
}

@ -488,6 +488,11 @@
"verror": "1.10.0"
}
},
"local-ip-url": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/local-ip-url/-/local-ip-url-1.0.3.tgz",
"integrity": "sha512-OyHVtNHgSmxr8B+6iA8sxlGrdNQJdr6M8db5/v83BdZA/cYe7+0i1XsyBQ0AoRj2nK32O3znEuPU9/b8TS6iww=="
},
"lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
@ -603,6 +608,11 @@
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
},
"qrcodejs": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/qrcodejs/-/qrcodejs-1.0.0.tgz",
"integrity": "sha1-r6tenoWFIfhZrjNtLtD5/S52zKc="
},
"qs": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",

@ -25,9 +25,11 @@
"hasha": "^5.2.2",
"jquery": "^3.6.0",
"jspdf": "^2.3.1",
"local-ip-url": "^1.0.3",
"nwglobal": "0.0.2",
"opentimestamps": "^0.4.9",
"pdfjs-dist": "^2.8.335",
"qrcodejs": "^1.0.0",
"signature_pad": "^3.0.0-beta.4"
}
}

@ -0,0 +1,114 @@
<!DOCTYPE html>
<!--
Copyright 2021 Netsyms Technologies.
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<html>
<head>
<title>Signature Pad</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="bootstrap.min.css" />
<link rel="stylesheet" href="theme.css" />
<script src="fontawesome/all.min.js"></script>
<script src="signature_pad.umd.min.js"></script>
<style>
.signature-wrapper {
background-color: transparent;
background-image: linear-gradient(125deg, rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0.2) 70%);
box-shadow: inset 1px 1px rgba(255, 255, 255, 0.2), inset -1px -1px rgba(255, 255, 255, 0.1), 1px 3px 24px -1px rgba(0, 0, 0, 0.15);
border-radius: 0 0 0.5rem 0.5rem;
position: relative;
width: 100%;
height: 100%;
margin: 0 auto;
/* fix bug on iOS where image sticks out right side and makes entire page scroll horiz. */
overflow: hidden;
user-select: none;
}
.signature-wrapper img {
position: absolute;
bottom: 0;
left: 0;
}
.signature-wrapper canvas {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
</style>
</head>
<body class="theme-BLANK">
<div class="card" style="width: calc(100vw - 1rem); height: calc(100vh - 1rem); margin: 0.5rem;">
<div class="card-header d-flex justify-content-between align-items-center">
<h6 class="card-title mb-0">Signature Pad</h6>
<div class="btn btn-warning btn-sm" onclick="resizeSignaturePadCanvas();signaturePad.clear();"><i class="fas fa-eraser"></i> Erase</div>
<div class="btn btn-primary btn-sm" onclick="signaturePadUndo();"><i class="fas fa-undo"></i> Undo</div>
<div class="btn btn-success btn-sm" onclick="sendSignature();"><i class="fas fa-check"></i> Done</div>
</div>
<div class="card-body p-0">
<div class="signature-wrapper">
<img src="img/signature-line.svg" />
<canvas id="signaturecanvas"></canvas>
</div>
</div>
</div>
<script>
var signaturePad = null;
function initSignaturePad() {
var canvas = document.getElementById("signaturecanvas");
signaturePad = new SignaturePad(canvas, {
backgroundColor: 'rgba(255, 255, 255, 0.5)'
});
resizeSignaturePadCanvas();
}
function resizeSignaturePadCanvas() {
var canvas = document.getElementById("signaturecanvas");
var ratio = Math.max(window.devicePixelRatio || 1, 1);
canvas.width = canvas.offsetWidth * ratio;
canvas.height = canvas.offsetHeight * ratio;
canvas.getContext("2d").scale(ratio, ratio);
if (signaturePad != null) {
signaturePad.clear(); // otherwise isEmpty() might return incorrect value
}
}
function signaturePadUndo() {
var data = signaturePad.toData();
resizeSignaturePadCanvas();
if (data) {
data.pop(); // remove the last dot or line
signaturePad.fromData(data);
}
}
function sendSignature() {
var signature = signaturePad.toData();
var xhttp = new XMLHttpRequest();
xhttp.open("POST", "/", true);
xhttp.send(JSON.stringify(signature));
}
window.addEventListener('load', function () {
initSignaturePad();
});
window.addEventListener('resize', function () {
resizeSignaturePadCanvas();
});
window.addEventListener('orientationchange', function () {
resizeSignaturePadCanvas();
});
</script>
</body>
</html>
Loading…
Cancel
Save