WIP : Fixing binary signature upload

This commit is contained in:
jndaniels 2024-12-01 20:05:27 -06:00
parent 81c94611c5
commit 71bba9ab74
10 changed files with 256 additions and 49 deletions

View File

@ -1,10 +1,52 @@
{ {
"meta": {
"$_POST": [
{
"type": "text",
"name": "imageData"
}
]
},
"exec": {
"steps": {
"name": "fileUpload", "name": "fileUpload",
"module": "upload", "module": "upload",
"action": "upload", "action": "upload",
"options": { "options": {
"fields": "" "fields": "{{$_POST.imageData}}",
"path": "/uploads",
"replaceSpace": true,
"replaceDiacritics": true,
"asciiOnly": true
}, },
"meta": [], "meta": [
"outputType": "array" {
"name": "name",
"type": "text"
},
{
"name": "path",
"type": "text"
},
{
"name": "url",
"type": "text"
},
{
"name": "type",
"type": "text"
},
{
"name": "size",
"type": "text"
},
{
"name": "error",
"type": "number"
}
],
"outputType": "array",
"output": true
}
}
} }

View File

@ -3,7 +3,7 @@
"$_POST": [ "$_POST": [
{ {
"type": "text", "type": "text",
"name": "image" "name": "imageData"
} }
] ]
}, },
@ -14,14 +14,14 @@
"module": "core", "module": "core",
"action": "setvalue", "action": "setvalue",
"options": { "options": {
"key": "image", "value": "{{$_POST.imageData.replace(/^data:image\\/\\w+;base64,/, \"\")}}"
"value": "image.replace(/^data:image\\/\\w+;base64,/, \"\")"
}, },
"meta": [], "meta": [],
"outputType": "text" "outputType": "text",
"output": true
}, },
{ {
"name": "upload", "name": "saveSignatureBinary",
"module": "upload", "module": "upload",
"action": "upload", "action": "upload",
"options": { "options": {

View File

@ -1,5 +1,5 @@
// JavaScript Document // JavaScript Document
console.log("LIBRARY: dummyFunctions.js")
function myFunction() { function myFunction() {
let myVar = 'Hello, Wappler!'; let myVar = 'Hello, Wappler!';
return myVar; return myVar;
@ -22,7 +22,7 @@ async function toBase64(filePath) {
} }
/**
const terminal = document.getElementById('terminal'); const terminal = document.getElementById('terminal');
@ -42,3 +42,5 @@ let lineCount = 0;
setInterval(() => { setInterval(() => {
addLine(`Line ${++lineCount}: This is a test log.`); addLine(`Line ${++lineCount}: This is a test log.`);
}, 500); // Add a new line every 500ms }, 500); // Add a new line every 500ms
*/

View File

@ -1,5 +1,5 @@
// JavaScript Document v1.3 // JavaScript Document v1.3
console.log("LIBRARY: pdfLibSignature.js")
//let dynamicSignatureElementId = dmx.global.data.signatureElementName // Gets the DYNAMIC ID for the canvas ID //let dynamicSignatureElementId = dmx.global.data.signatureElementName // Gets the DYNAMIC ID for the canvas ID
const canvas = document.getElementById('signatureCanvas'); // const canvas = document.getElementById('signatureCanvasIndex'); const canvas = document.getElementById('signatureCanvas'); // const canvas = document.getElementById('signatureCanvasIndex');
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');

View File

@ -57,6 +57,10 @@ dmx.config({
{ {
"type": "text", "type": "text",
"name": "Points" "name": "Points"
},
{
"type": "text",
"name": "section"
} }
] ]
}, },
@ -65,6 +69,10 @@ dmx.config({
{ {
"type": "text", "type": "text",
"name": "file" "name": "file"
},
{
"type": "text",
"name": "imageData"
} }
], ],
"flowRunPageId": { "flowRunPageId": {
@ -87,6 +95,26 @@ dmx.config({
}, },
"runPageId": { "runPageId": {
"outputType": "text" "outputType": "text"
},
"flow1": {
"meta": [
{
"name": "saveSignatureFlow",
"type": "text"
}
],
"local": [
{
"name": "",
"type": "object",
"metaData": {
"alertMessage": {
"meta": null,
"outputType": "text"
}
}
}
]
} }
} }
}); });

BIN
public/example.db Normal file

Binary file not shown.

View File

@ -1,24 +1,40 @@
// JavaScript Document // JavaScript Document
// Replaces libPDFscripts.js // Replaces libPDFscripts.js
// NEW - Process Scripts // NEW - Process Scripts
//
// ** OFFICIAL SCRIPT TO USE WHEN IN PRODUCTION
//
// Utility to fetch PDF bytes // Utility to fetch PDF bytes
console.log("LIBRARY: libProcessScript.js")
//const path = FileSystemHandle.require('node:path') //const path = FileSystemHandle.require('node:path')
let showLog = true; let showLog = true;
const dd = createDebugLogger(showLog, "dd"); const dd = createDebugLogger(showLog, "dd"); // code function at bottom of the page
function createDebugLogger(debug, prefix = "") {
return function (message, ...optionalParams) {
if (debug) { // Wrapper function to fetch, update, and download PDF
console.log( async function processAndDownloadPdf(filePath, ...dataSources) {
`[${prefix}]`.toUpperCase(), `${message}`, try {
...optionalParams const pdfBytes = await fetchPdfBytes(filePath);
); const updatedPdfBytes = await updatePdfFields(pdfBytes, dataSources);
const fileName = generateRandomFilename(8, "ERT_", ".pdf");
//const saveFilePath = await savePdfToFile(pdfBytes);
//dd("[PDF PATH]", saveFilePath);
downloadPdf(updatedPdfBytes, fileName);
} catch (err) {
dd("Error Processing PDF", err)
//console.error("Error processing the PDF:", err);
} }
};
} }
async function fetchPdfBytes(filePath) { async function fetchPdfBytes(filePath) {
dd(filePath); dd(filePath);
@ -95,19 +111,7 @@ function generateRandomFilename(length = 8, prefix = "", suffix = "") {
// Example Usage: // Example Usage:
// Wrapper function to fetch, update, and download PDF
async function processAndDownloadPdf(filePath, ...dataSources) {
try {
const pdfBytes = await fetchPdfBytes(filePath);
const updatedPdfBytes = await updatePdfFields(pdfBytes, dataSources);
const fileName = generateRandomFilename(8, "ERT_", ".pdf");
//const saveFilePath = await savePdfToFile(pdfBytes);
//dd("[PDF PATH]", saveFilePath);
downloadPdf(updatedPdfBytes, fileName);
} catch (err) {
console.error("Error processing the PDF:", err);
}
}
async function savePdfToFile(pdfBytes) { async function savePdfToFile(pdfBytes) {
@ -121,3 +125,33 @@ async function savePdfToFile(pdfBytes) {
// Return a URL or file path based on your server setup // Return a URL or file path based on your server setup
return `/PDF/${randomFilename}`; return `/PDF/${randomFilename}`;
} }
function createDebugLogger(debug, prefix = "DD") {
return function (message, ...optionalParams) {
switch (debug) {
case true:
console.log(
`[${prefix}]`.toUpperCase(), `${message}`, ...optionalParams);
break
}
};
}
function saveSignature() {
const canvas = document.getElementById('signatureCanvas');
const imageData = canvas.toDataURL('image/png');
dd(imageData.length)
// Trigger the server action and handle the response
dmx.parse('serverconnect_sign.load({ image: imageData })');
/**
dmx.parse('serverconnect_sign.load', { image: imageData }).then((response) => {
//dmx.parse('serverconnect_sign.load({image: "' + imageData + '"})').then((response) => {
const uploadedSignature = document.getElementById('uploadedSignature');
uploadedSignature.src = response.fileUrl; // Use the file path returned from the server
dd(response.fileUrl)
}).catch((error) => {
console.error("Error uploading signature:", error);
});
*/
}

56
public/terminal.html Normal file
View File

@ -0,0 +1,56 @@
<!DOCTYPE html>
<html lang="en">
<head>
<base href="/">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Terminal Emulator</title>
<style>
body {
margin: 0;
font-family: monospace;
background-color: #000;
color: #0f0;
}
#terminal {
width: 100%;
height: 90vh;
background-color: #000;
overflow-y: auto;
padding: 10px;
border: 2px solid #333;
}
.line {
margin: 0;
}
</style>
</head>
<body>
<div id="terminal"></div>
<script>
const terminal = document.getElementById('terminal');
// Function to add a line to the terminal
function addLine(text) {
const line = document.createElement('p');
line.className = 'line';
line.textContent = text;
terminal.appendChild(line);
// Automatically scroll to the bottom
terminal.scrollTop = terminal.scrollHeight;
}
// Simulate adding lines
let lineCount = 0;
setInterval(() => {
addLine(`Line ${++lineCount}: This is a test log.`);
}, 500); // Add a new line every 500ms
</script>
</body>
</html>

View File

@ -1,5 +1,45 @@
<!-- Wappler include head-page="layouts/main" fontawesome_5="cdn" bootstrap5="local" is="dmx-app" id="index" appConnect="local" components="{dmxBootstrap5Navigation:{},dmxAnimateCSS:{},dmxStateManagement:{},dmxDatastore:{},dmxBootstrap5Modal:{},dmxFormatter:{},dmxBootstrap5TableGenerator:{},dmxBootstrap5Toasts:{},dmxBootbox5:{},dmxBrowser:{},dmxBootstrap5Tooltips:{},dmxValidator:{}}" --> <!-- Wappler include head-page="layouts/main" fontawesome_5="cdn" bootstrap5="local" is="dmx-app" id="index" appConnect="local" components="{dmxBootstrap5Navigation:{},dmxAnimateCSS:{},dmxStateManagement:{},dmxDatastore:{},dmxBootstrap5Modal:{},dmxFormatter:{},dmxBootstrap5TableGenerator:{},dmxBootstrap5Toasts:{},dmxBootbox5:{},dmxBrowser:{},dmxBootstrap5Tooltips:{},dmxValidator:{}}" -->
<dmx-toggle id="dataStoreVisibality"></dmx-toggle> <script is="dmx-flow" id="flow1" type="text/dmx-flow">[
{
runJS: {
name: "saveSignatureFlow",
output: true,
outputType: "text",
function: "saveSignature"
}
},
{
alert: {message: "{{saveSignatureFlow}}"}
}
]</script>
<dmx-serverconnect id="serverconnect_sign" url="/api/uploadSignature" noload="true" dmx-on:start="toasts1.showSimple({message: 'Upload Start', delay: 2500, type: 'success'})" dmx-on:done="toasts1.showSimple({message: 'Upload Finished', delay: 2500, type: 'info'})"></dmx-serverconnect>
<div class="modal" id="modalVerifySignature" is="dmx-bs5-modal" tabindex="-1">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Verify Signature</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>Signature will show here. Is it acceptable? Save or discard.</p>
<p>
<div class="container" id="signatureContainer">
<div class="row">
<div class="col text-center">
<img id="uploadedSignature" alt="Signature will appear here" dmx-style:uploadedsignaturestyle="'max-width: 690; border: 1px solid #ccc;'" width="700" height="225">
</div>
</div>
</div>
</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary">Save changes</button>
</div>
</div>
</div>
</div>
<div class="modal" id="modal-success" is="dmx-bs5-modal" tabindex="-1" role="dialog"> <div class="modal" id="modal-success" is="dmx-bs5-modal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<div class="modal-content"> <div class="modal-content">
@ -66,6 +106,7 @@
</div> </div>
</div> </div>
</div> </div>
<dmx-toggle id="dataStoreVisibality"></dmx-toggle>
<div class="container wappler-block pt-3 pb-3"> <div class="container wappler-block pt-3 pb-3">
<nav class="navbar navbar-expand-lg justify-content-around"> <nav class="navbar navbar-expand-lg justify-content-around">
@ -237,7 +278,7 @@
<div id="eSignatureRowButtons" class="row ms-0 me-0 ps-0 pe-0 justify-content-center"> <div id="eSignatureRowButtons" class="row ms-0 me-0 ps-0 pe-0 justify-content-center">
<button id="btn4_ClearSignature" class="btn btn-warning w-25 " data-bs-toggle="button" onclick="clearCanvas()" dmx-style:margin-right="'16px'">Clear Signature</button> <button id="btn4_ClearSignature" class="btn btn-warning w-25 " data-bs-toggle="button" onclick="clearCanvas()" dmx-style:margin-right="'16px'">Clear Signature</button>
<button type="button" class="btn btn-danger w-auto visually-hidden" data-bs-dismiss="modal" dmx-bs-tooltip="'Close Signature Panel'" data-bs-trigger="hover" data-bs-placement="top" dmx-style:margin-right="'16px'">Close</button> <button type="button" class="btn btn-danger w-auto visually-hidden" data-bs-dismiss="modal" dmx-bs-tooltip="'Close Signature Panel'" data-bs-trigger="hover" data-bs-placement="top" dmx-style:margin-right="'16px'">Close</button>
<button type="button" class="btn btn-success w-auto" onclick="runMyFunction()">Save</button> <button type="button" class="btn btn-success w-auto" onclick="saveSignature()">Save</button>
</div> </div>
@ -248,7 +289,8 @@
</div> </div>
<script src="/PDF/pdfLibSignature.js"></script> <script src="/PDF/pdfLibSignature.js"></script>
<script src="/PDF/dummyFunctions.js"></script> <script src="/PDF/dummyFunctions.js"></script>
<script src="/js/libProcessScript.js"></script>
<meta name="ac:route" content="/index0"> <meta name="ac:route" content="/index0">

View File

@ -116,11 +116,11 @@
<p> <p>
</p> </p>
<div class="container"><button id="btn6" class="btn text-bg-danger" dmx-on:click="run({run:{name:'CLearPoints',outputType:'text',action:`datastore1.clear()`}})">Clear Point Table</button></div> <div class="container"><button id="btn6" class="btn text-bg-danger" dmx-on:click="run({run:{name:'CLearPoints',outputType:'text',action:`datastore1.clear()`}})">Clear All Points</button></div>
</div> </div>
</div> </div>
<dmx-toggle id="toggle1" checked="true"></dmx-toggle> <dmx-toggle id="toggle1" checked="true"></dmx-toggle>
<dmx-datastore id="datastore1" dmx-on:updated="toasts1.show({message: 'Record Updated ', title: 'New Insert Added'})" dmx-on:inserted="toasts1.showSimple({message: 'New Record Inserted '})"></dmx-datastore> <dmx-datastore id="datastore1"></dmx-datastore>
<div is="dmx-bs5-toasts" id="toasts1"></div> <div is="dmx-bs5-toasts" id="toasts1"></div>
<dmx-serverconnect id="scQuerySubSections" url="/api/qSubSection" noload="true" dmx-param:subid="1"></dmx-serverconnect> <dmx-serverconnect id="scQuerySubSections" url="/api/qSubSection" noload="true" dmx-param:subid="1"></dmx-serverconnect>
<dmx-serverconnect id="serverconnect1" url="/api/qSection"></dmx-serverconnect> <dmx-serverconnect id="serverconnect1" url="/api/qSection"></dmx-serverconnect>
@ -147,9 +147,12 @@
<div class="row"> <div class="row">
<div class="col-3"> <div class="col-3">
<p class="text-center"><b>Who you are grading</b></p> <p class="text-center"><b>Who you are grading</b></p>
<div class="d-inline-block" tabindex="0" data-bs-toggle="popover" data-bs-trigger="hover focus" data-bs-content="Disabled" data-bs-original-title title>
<select id="select1" class="form-select" dmx-bind:options="qDB.data.query" optiontext="db_fullName" optionvalue="ID" dmx-bind:disabled="datastore1.data.hasItems()"> <select id="select1" class="form-select" dmx-bind:options="qDB.data.query" optiontext="db_fullName" optionvalue="ID" dmx-bind:disabled="datastore1.data.hasItems()">
</select> </select>
</div> </div>
</div>
<div class="col"> <div class="col">
<table class="table table-striped table-sm"> <table class="table table-striped table-sm">
<thead> <thead>
@ -266,8 +269,7 @@
<tbody is="dmx-repeat" dmx-generator="bs5table" id="tableRepeat2" dmx-bind:repeat="scQuerySubSections.data.query"> <tbody is="dmx-repeat" dmx-generator="bs5table" id="tableRepeat2" dmx-bind:repeat="scQuerySubSections.data.query">
<tr> <tr>
<td> <td>
<input id="pointValue" name="section" class="form-control" dmx-bind:value="selPoints.selectedValue"> <select id="selPoints" class="form-select" name="sPoints" dmx-on:changed="datastore1.upsert({numSection: numSection, $id: numSection, description: txtSection},{numSection: numSection, Points: value, description: txtSection})" dmx-bind:id="numSection" dmx-bind:value="selectedValue">
<select id="selPoints" class="form-select" name="sPoints" dmx-on:changed="datastore1.upsert({numSection: pointValue.value, $id: numSection, description: txtSection},{numSection: pointValue.value, Points: value, description: txtSection});selPoints.setValue(0)" dmx-bind:id="numSection" dmx-bind:value="selectedValue">
<option value="0">0 points</option> <option value="0">0 points</option>
<option value="1">1 point</option> <option value="1">1 point</option>
<option value="2">2 points</option> <option value="2">2 points</option>
@ -295,7 +297,7 @@
<th>Num section</th> <th>Num section</th>
<th>Points</th> <th>Points</th>
<th>Description&nbsp; <th>Description&nbsp;
<button id="btn5" class="btn btn-outline-danger w-auto btn-sm lh-base" data-bs-toggle="button" dmx-bs-tooltip="'Clear ALL points'" data-bs-trigger="hover" dmx-on:click="run({'bootbox.confirm':{name:'clearPoints',message:'This will clear all points',title:'WATCH OUT',buttons:{confirm:{label:'Yes'},cancel:{label:'Cancel'}},then:{steps:{run:{name:'clearDataStore1',outputType:'text',action:`datastore1.clear()`}}}}})" dmx-hide="!datastore1.data.hasItems()">Clear</button> <button id="btn5" class="btn btn-outline-danger w-auto btn-sm lh-base" data-bs-toggle="button" dmx-on:click="run({'bootbox.confirm':{name:'clearPoints',message:'This will clear all points',title:'WATCH OUT',buttons:{confirm:{label:'Yes',className:'btn-danger'},cancel:{label:'Cancel'}},closeButton:false,backdrop:true,then:{steps:{run:{name:'clearDataStore1',outputType:'text',action:`datastore1.clear()`}}}}})" dmx-hide="!datastore1.data.hasItems()">Clear all points</button>
</th> </th>
</tr> </tr>
</thead> </thead>
@ -309,6 +311,7 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<script src="https://unpkg.com/pdf-lib/dist/pdf-lib.min.js"></script> <script src="https://unpkg.com/pdf-lib/dist/pdf-lib.min.js"></script>
<script src="/js/libProcessScript.js"></script> <script src="/js/libProcessScript.js"></script>
<!-- <!--