Restyled the sign page and cleaned up code in various places.
This commit is contained in:
parent
fc0af56136
commit
b2a29e2b13
16 changed files with 685 additions and 674 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -109,4 +109,6 @@ local.properties
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
*.rar
|
*.rar
|
||||||
|
|
||||||
/build
|
/build
|
||||||
|
|
||||||
|
/.vscode
|
|
@ -42,3 +42,8 @@ html[lang-direction=rtl] * {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-group > label:first-of-type {
|
||||||
|
border-top-left-radius: 0.25rem !important;
|
||||||
|
border-bottom-left-radius: 0.25rem !important;
|
||||||
|
}
|
||||||
|
|
|
@ -108,16 +108,17 @@ function initializeGame() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function resetEnemies() {
|
function resetEnemies() {
|
||||||
pdfs.forEach((pdf) => gameContainer.removeChild(pdf));
|
pdfs.forEach((pdf) => gameContainer.removeChild(pdf));
|
||||||
pdfs.length = 0;
|
pdfs.length = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function updateGame() {
|
function updateGame() {
|
||||||
if (gameOver || paused) return;
|
if (gameOver || paused) return;
|
||||||
|
|
||||||
pdfs.forEach((pdf, pdfIndex) => {
|
for (let pdfIndex = 0; pdfIndex < pdfs.length; pdfIndex++) {
|
||||||
|
const pdf = pdfs[pdfIndex];
|
||||||
const pdfY = parseInt(pdf.style.top) + pdfSpeed;
|
const pdfY = parseInt(pdf.style.top) + pdfSpeed;
|
||||||
if (pdfY + 50 > gameContainer.clientHeight) {
|
if (pdfY + 50 > gameContainer.clientHeight) {
|
||||||
gameContainer.removeChild(pdf);
|
gameContainer.removeChild(pdf);
|
||||||
|
@ -149,11 +150,7 @@ function resetEnemies() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
projectiles.forEach((projectile, projectileIndex) => {
|
projectiles.forEach((projectile, projectileIndex) => {
|
||||||
const projectileY = parseInt(projectile.style.top) - 10;
|
const projectileY = parseInt(projectile.style.top) - 10;
|
||||||
|
@ -180,31 +177,32 @@ function resetEnemies() {
|
||||||
|
|
||||||
setTimeout(updateGame, 1000 / 60);
|
setTimeout(updateGame, 1000 / 60);
|
||||||
}
|
}
|
||||||
function resetGame() {
|
|
||||||
playerX = gameContainer.clientWidth / 2;
|
|
||||||
playerY = 50;
|
|
||||||
updatePlayerPosition();
|
|
||||||
|
|
||||||
pdfs.forEach((pdf) => gameContainer.removeChild(pdf));
|
function resetGame() {
|
||||||
projectiles.forEach((projectile) => gameContainer.removeChild(projectile));
|
playerX = gameContainer.clientWidth / 2;
|
||||||
|
playerY = 50;
|
||||||
|
updatePlayerPosition();
|
||||||
|
|
||||||
pdfs.length = 0;
|
pdfs.forEach((pdf) => gameContainer.removeChild(pdf));
|
||||||
projectiles.length = 0;
|
projectiles.forEach((projectile) => gameContainer.removeChild(projectile));
|
||||||
|
|
||||||
score = 0;
|
pdfs.length = 0;
|
||||||
level = 1;
|
projectiles.length = 0;
|
||||||
lives = 3;
|
|
||||||
|
|
||||||
gameOver = false;
|
|
||||||
|
|
||||||
updateScore();
|
score = 0;
|
||||||
updateLives();
|
level = 1;
|
||||||
levelElement.textContent = 'Level: ' + level;
|
lives = 3;
|
||||||
pdfSpeed = 1;
|
|
||||||
clearTimeout(spawnPdfTimeout); // Clear the existing spawnPdfTimeout
|
gameOver = false;
|
||||||
setTimeout(updateGame, 1000 / 60);
|
|
||||||
spawnPdfInterval();
|
updateScore();
|
||||||
}
|
updateLives();
|
||||||
|
levelElement.textContent = 'Level: ' + level;
|
||||||
|
pdfSpeed = 1;
|
||||||
|
clearTimeout(spawnPdfTimeout); // Clear the existing spawnPdfTimeout
|
||||||
|
setTimeout(updateGame, 1000 / 60);
|
||||||
|
spawnPdfInterval();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -243,9 +241,7 @@ function resetGame() {
|
||||||
updateHighScore();
|
updateHighScore();
|
||||||
}
|
}
|
||||||
alert('Game Over! Your final score is: ' + score);
|
alert('Game Over! Your final score is: ' + score);
|
||||||
setTimeout(() => { // Wrap the resetGame() call in a setTimeout
|
document.getElementById('game-container-wrapper').close();
|
||||||
resetGame();
|
|
||||||
}, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -281,6 +277,7 @@ function resetGame() {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
window.resetGame = resetGame;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.initializeGame = initializeGame;
|
window.initializeGame = initializeGame;
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
<th:block th:insert="~{fragments/common :: game}"></th:block>
|
||||||
<div id="page-container">
|
<div id="page-container">
|
||||||
<div id="content-wrap">
|
<div id="content-wrap">
|
||||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
<th:block th:insert="~{fragments/common :: game}"></th:block>
|
||||||
<div id="page-container">
|
<div id="page-container">
|
||||||
<div id="content-wrap">
|
<div id="content-wrap">
|
||||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||||
|
@ -40,7 +41,7 @@
|
||||||
|
|
||||||
<br> <br>
|
<br> <br>
|
||||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{imageToPDF.submit}"></button>
|
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{imageToPDF.submit}"></button>
|
||||||
<script>
|
<script>
|
||||||
$('#fileInput-input').on('change', function() {
|
$('#fileInput-input').on('change', function() {
|
||||||
var files = document.getElementById("fileInput-input").files;
|
var files = document.getElementById("fileInput-input").files;
|
||||||
var conversionType = document.getElementById("conversionType");
|
var conversionType = document.getElementById("conversionType");
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
<th:block th:insert="~{fragments/common :: head(title=#{PDFToHTML.title})}"></th:block>
|
<th:block th:insert="~{fragments/common :: head(title=#{PDFToHTML.title})}"></th:block>
|
||||||
<body>
|
<body>
|
||||||
|
<th:block th:insert="~{fragments/common :: game}"></th:block>
|
||||||
<div id="page-container">
|
<div id="page-container">
|
||||||
<div id="content-wrap">
|
<div id="content-wrap">
|
||||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
<th:block th:insert="~{fragments/common :: game}"></th:block>
|
||||||
<div id="page-container">
|
<div id="page-container">
|
||||||
<div id="content-wrap">
|
<div id="content-wrap">
|
||||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
<th:block th:insert="~{fragments/common :: game}"></th:block>
|
||||||
<div id="page-container">
|
<div id="page-container">
|
||||||
<div id="content-wrap">
|
<div id="content-wrap">
|
||||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
<th:block th:insert="~{fragments/common :: head(title=#{PDFToPresentation.title})}"></th:block>
|
<th:block th:insert="~{fragments/common :: head(title=#{PDFToPresentation.title})}"></th:block>
|
||||||
<body>
|
<body>
|
||||||
|
<th:block th:insert="~{fragments/common :: game}"></th:block>
|
||||||
<div id="page-container">
|
<div id="page-container">
|
||||||
<div id="content-wrap">
|
<div id="content-wrap">
|
||||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
<th:block th:insert="~{fragments/common :: head(title=#{PDFToText.title})}"></th:block>
|
<th:block th:insert="~{fragments/common :: head(title=#{PDFToText.title})}"></th:block>
|
||||||
<body>
|
<body>
|
||||||
|
<th:block th:insert="~{fragments/common :: game}"></th:block>
|
||||||
<div id="page-container">
|
<div id="page-container">
|
||||||
<div id="content-wrap">
|
<div id="content-wrap">
|
||||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
<th:block th:insert="~{fragments/common :: head(title=#{PDFToWord.title})}"></th:block>
|
<th:block th:insert="~{fragments/common :: head(title=#{PDFToWord.title})}"></th:block>
|
||||||
<body>
|
<body>
|
||||||
|
<th:block th:insert="~{fragments/common :: game}"></th:block>
|
||||||
<div id="page-container">
|
<div id="page-container">
|
||||||
<div id="content-wrap">
|
<div id="content-wrap">
|
||||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
<th:block th:insert="~{fragments/common :: head(title=#{PDFToXML.title})}"></th:block>
|
<th:block th:insert="~{fragments/common :: head(title=#{PDFToXML.title})}"></th:block>
|
||||||
<body>
|
<body>
|
||||||
|
<th:block th:insert="~{fragments/common :: game}"></th:block>
|
||||||
<div id="page-container">
|
<div id="page-container">
|
||||||
<div id="content-wrap">
|
<div id="content-wrap">
|
||||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||||
|
|
|
@ -101,456 +101,439 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<th:block th:fragment="fileSelector(name, multiple)" th:with="accept=${accept} ?: '*/*', inputText=${inputText} ?: #{pdfPrompt}">
|
|
||||||
|
|
||||||
|
<th:block th:fragment="game">
|
||||||
<script>
|
<dialog id="game-container-wrapper" class="game-container-wrapper" data-modal>
|
||||||
|
<script>
|
||||||
|
console.log("loaded game")
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
|
function loadGameScript(callback) {
|
||||||
function loadGameScript(callback) {
|
console.log('loadGameScript called');
|
||||||
console.log('loadGameScript called');
|
const script = document.createElement('script');
|
||||||
const script = document.createElement('script');
|
script.src = 'js/game.js';
|
||||||
script.src = 'js/game.js';
|
script.onload = callback;
|
||||||
script.onload = callback;
|
document.body.appendChild(script);
|
||||||
document.body.appendChild(script);
|
|
||||||
}
|
|
||||||
let gameScriptLoaded = false;
|
|
||||||
$('#show-game-btn').on('click', function() {
|
|
||||||
console.log('Show game button clicked');
|
|
||||||
if (!gameScriptLoaded) {
|
|
||||||
console.log('Show game button load');
|
|
||||||
loadGameScript(function() {
|
|
||||||
console.log('Game script loaded');
|
|
||||||
window.initializeGame();
|
|
||||||
gameScriptLoaded = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
$('#game-container-wrapper').show();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
$('form').submit(function(event) {
|
|
||||||
|
|
||||||
const boredWaiting = localStorage.getItem('boredWaiting');
|
|
||||||
if (boredWaiting === 'enabled') {
|
|
||||||
$('#show-game-btn').show();
|
|
||||||
}
|
|
||||||
var processing = "Processing..."
|
|
||||||
var submitButtonText = $('#submitBtn').text()
|
|
||||||
|
|
||||||
$('#submitBtn').text('Processing...');
|
|
||||||
console.log("start download code")
|
|
||||||
var files = $('#fileInput-input')[0].files;
|
|
||||||
var url = this.action;
|
|
||||||
console.log(url)
|
|
||||||
event.preventDefault(); // Prevent the default form handling behavior
|
|
||||||
/* Check if ${multiple} is false */
|
|
||||||
var multiple = [[${multiple}]] || false;
|
|
||||||
var override = $('#override').val() || '';
|
|
||||||
console.log("override=" + override)
|
|
||||||
if (override === 'multi' || (!multiple && files.length > 1) && override !== 'single' ) {
|
|
||||||
console.log("multi parallel download")
|
|
||||||
submitMultiPdfForm(event,url);
|
|
||||||
} else {
|
|
||||||
console.log("start single download")
|
|
||||||
|
|
||||||
// Get the selected download option from localStorage
|
|
||||||
const downloadOption = localStorage.getItem('downloadOption');
|
|
||||||
|
|
||||||
var formData = new FormData($('form')[0]);
|
|
||||||
|
|
||||||
// Send the request to the server using the fetch() API
|
|
||||||
fetch(url, {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData
|
|
||||||
}).then(response => {
|
|
||||||
if (!response) {
|
|
||||||
throw new Error('Received null response for file ' + i);
|
|
||||||
}
|
|
||||||
console.log("load single download")
|
|
||||||
|
|
||||||
|
|
||||||
// Extract the filename from the Content-Disposition header, if present
|
|
||||||
let filename = null;
|
|
||||||
const contentDispositionHeader = response.headers.get('Content-Disposition');
|
|
||||||
console.log(contentDispositionHeader)
|
|
||||||
if (contentDispositionHeader && contentDispositionHeader.indexOf('attachment') !== -1) {
|
|
||||||
filename = decodeURIComponent(contentDispositionHeader.split('filename=')[1].replace(/"/g, ''));
|
|
||||||
} else {
|
|
||||||
// If the Content-Disposition header is not present or does not contain the filename, use a default filename
|
|
||||||
filename = 'download';
|
|
||||||
}
|
|
||||||
console.log("filename=" + filename)
|
|
||||||
|
|
||||||
|
|
||||||
const contentType = response.headers.get('Content-Type');
|
|
||||||
console.log("contentType=" + contentType)
|
|
||||||
// Check if the response is a PDF or an image
|
|
||||||
if (contentType.includes('pdf') || contentType.includes('image')) {
|
|
||||||
response.blob().then(blob => {
|
|
||||||
console.log("pdf/image")
|
|
||||||
|
|
||||||
// Perform the appropriate action based on the download option
|
|
||||||
if (downloadOption === 'sameWindow') {
|
|
||||||
console.log("same window")
|
|
||||||
|
|
||||||
// Open the file in the same window
|
|
||||||
window.location.href = URL.createObjectURL(blob);
|
|
||||||
} else if (downloadOption === 'newWindow') {
|
|
||||||
console.log("new window")
|
|
||||||
|
|
||||||
// Open the file in a new window
|
|
||||||
window.open(URL.createObjectURL(blob), '_blank');
|
|
||||||
} else {
|
|
||||||
console.log("else save")
|
|
||||||
|
|
||||||
// Download the file
|
|
||||||
const link = document.createElement('a');
|
|
||||||
link.href = URL.createObjectURL(blob);
|
|
||||||
link.download = filename;
|
|
||||||
link.click();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (contentType.includes('json')) {
|
|
||||||
// Handle the JSON response
|
|
||||||
response.json().then(data => {
|
|
||||||
// Format the error message
|
|
||||||
const errorMessage = JSON.stringify(data, null, 2);
|
|
||||||
|
|
||||||
// Display the error message in an alert
|
|
||||||
alert(`An error occurred: ${errorMessage}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
response.blob().then(blob => {
|
|
||||||
console.log("else save 2 zip")
|
|
||||||
|
|
||||||
// For ZIP files or other file types, just download the file
|
|
||||||
const link = document.createElement('a');
|
|
||||||
link.href = URL.createObjectURL(blob);
|
|
||||||
link.download = filename;
|
|
||||||
link.click();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.log("error listener")
|
|
||||||
|
|
||||||
// Extract the error message and stack trace from the response
|
|
||||||
const errorMessage = error.message;
|
|
||||||
const stackTrace = error.stack;
|
|
||||||
|
|
||||||
// Create an error message to display to the user
|
|
||||||
const message = `${errorMessage}\n\n${stackTrace}`;
|
|
||||||
|
|
||||||
$('#submitBtn').text(submitButtonText);
|
|
||||||
|
|
||||||
// Display the error message to the user
|
|
||||||
alert(message);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
$('#submitBtn').text(submitButtonText);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
async function submitMultiPdfForm(event, url) {
|
|
||||||
// Get the selected PDF files
|
|
||||||
let files = $('#fileInput-input')[0].files;
|
|
||||||
|
|
||||||
// Get the existing form data
|
|
||||||
let formData = new FormData($('form')[0]);
|
|
||||||
formData.delete('fileInput');
|
|
||||||
|
|
||||||
// Show the progress bar
|
|
||||||
$('#progressBarContainer').show();
|
|
||||||
|
|
||||||
// Initialize the progress bar
|
|
||||||
let progressBar = $('#progressBar');
|
|
||||||
progressBar.css('width', '0%');
|
|
||||||
progressBar.attr('aria-valuenow', 0);
|
|
||||||
progressBar.attr('aria-valuemax', files.length);
|
|
||||||
|
|
||||||
// Check the flag in localStorage, default to 4
|
|
||||||
const zipThreshold = parseInt(localStorage.getItem('zipThreshold'), 10) || 4;
|
|
||||||
const zipFiles = files.length > zipThreshold;
|
|
||||||
|
|
||||||
// Initialize JSZip instance if needed
|
|
||||||
let jszip = null;
|
|
||||||
if (zipFiles) {
|
|
||||||
jszip = new JSZip();
|
|
||||||
}
|
}
|
||||||
|
let gameScriptLoaded = false;
|
||||||
|
$('#show-game-btn').on('click', function() {
|
||||||
|
console.log('Show game button clicked');
|
||||||
|
if (!gameScriptLoaded) {
|
||||||
|
console.log('Show game button load');
|
||||||
|
loadGameScript(function() {
|
||||||
|
console.log('Game script loaded');
|
||||||
|
window.initializeGame();
|
||||||
|
gameScriptLoaded = true;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
window.resetGame();
|
||||||
|
}
|
||||||
|
document.getElementById('game-container-wrapper').showModal();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<div id="game-container">
|
||||||
|
<div id="lives">Lives: 3</div>
|
||||||
|
<div id="score">Score: 0</div>
|
||||||
|
<div id="high-score">High Score: 0</div>
|
||||||
|
<div id="level">Level: 1</div>
|
||||||
|
<img src="favicon.svg" class="player" id="player">
|
||||||
|
</div>
|
||||||
|
<style>
|
||||||
|
#game-container {
|
||||||
|
position: relative;
|
||||||
|
width: 100vh;
|
||||||
|
height: 0;
|
||||||
|
padding-bottom: 75%; /* 4:3 aspect ratio */
|
||||||
|
background-color: transparent;
|
||||||
|
margin: auto;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 2px solid black; /* Add border */
|
||||||
|
}
|
||||||
|
|
||||||
// Submit each PDF file in parallel
|
.pdf, .player, .projectile {
|
||||||
let promises = [];
|
position: absolute;
|
||||||
for (let i = 0; i < files.length; i++) {
|
}
|
||||||
let promise = new Promise(async function(resolve, reject) {
|
.pdf {
|
||||||
let fileFormData = new FormData();
|
width: 50px;
|
||||||
fileFormData.append('fileInput', files[i]);
|
height: 50px;
|
||||||
for (let pair of formData.entries()) {
|
}
|
||||||
fileFormData.append(pair[0], pair[1]);
|
.player {
|
||||||
}
|
width: 50px;
|
||||||
console.log(fileFormData);
|
height: 50px;
|
||||||
|
}
|
||||||
|
.projectile {
|
||||||
|
background-color: black !important;
|
||||||
|
width: 5px;
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
#score, #level, #lives, #high-score {
|
||||||
|
color: black;
|
||||||
|
font-family: sans-serif;
|
||||||
|
position: absolute;
|
||||||
|
font-size: calc(14px + 0.25vw); /* Reduced font size */
|
||||||
|
}
|
||||||
|
#score {
|
||||||
|
top: 10px;
|
||||||
|
left: 10px;
|
||||||
|
}
|
||||||
|
#lives {
|
||||||
|
top: 10px;
|
||||||
|
left: calc(7vw); /* Adjusted position */
|
||||||
|
}
|
||||||
|
#high-score {
|
||||||
|
top: 10px;
|
||||||
|
left: calc(14vw); /* Adjusted position */
|
||||||
|
}
|
||||||
|
#level {
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
</style>
|
||||||
let response = await fetch(url, {
|
</dialog>
|
||||||
method: 'POST',
|
</th:block>
|
||||||
body: fileFormData
|
|
||||||
});
|
|
||||||
|
|
||||||
|
<th:block th:fragment="fileSelector(name, multiple)" th:with="accept=${accept} ?: '*/*', inputText=${inputText} ?: #{pdfPrompt}">
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('form').submit(function(event) {
|
||||||
|
const boredWaiting = localStorage.getItem('boredWaiting');
|
||||||
|
if (boredWaiting === 'enabled') {
|
||||||
|
$('#show-game-btn').show();
|
||||||
|
}
|
||||||
|
var processing = "Processing..."
|
||||||
|
var submitButtonText = $('#submitBtn').text()
|
||||||
|
|
||||||
|
$('#submitBtn').text('Processing...');
|
||||||
|
console.log("start download code")
|
||||||
|
var files = $('#fileInput-input')[0].files;
|
||||||
|
var url = this.action;
|
||||||
|
console.log(url)
|
||||||
|
event.preventDefault(); // Prevent the default form handling behavior
|
||||||
|
/* Check if ${multiple} is false */
|
||||||
|
var multiple = [[${multiple}]] || false;
|
||||||
|
var override = $('#override').val() || '';
|
||||||
|
console.log("override=" + override)
|
||||||
|
if (override === 'multi' || (!multiple && files.length > 1) && override !== 'single' ) {
|
||||||
|
console.log("multi parallel download")
|
||||||
|
submitMultiPdfForm(event,url);
|
||||||
|
} else {
|
||||||
|
console.log("start single download")
|
||||||
|
|
||||||
|
// Get the selected download option from localStorage
|
||||||
|
const downloadOption = localStorage.getItem('downloadOption');
|
||||||
|
|
||||||
|
var formData = new FormData($('form')[0]);
|
||||||
|
|
||||||
|
// Send the request to the server using the fetch() API
|
||||||
|
fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
}).then(response => {
|
||||||
if (!response) {
|
if (!response) {
|
||||||
throw new Error('Received null response for file ' + i);
|
throw new Error('Received null response for file ' + i);
|
||||||
}
|
}
|
||||||
|
console.log("load single download")
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Error submitting request for file ${i}: ${response.status} ${response.statusText}`);
|
// Extract the filename from the Content-Disposition header, if present
|
||||||
}
|
let filename = null;
|
||||||
|
const contentDispositionHeader = response.headers.get('Content-Disposition');
|
||||||
let contentDisposition = response.headers.get('content-disposition');
|
console.log(contentDispositionHeader)
|
||||||
let fileName = "file.pdf"
|
if (contentDispositionHeader && contentDispositionHeader.indexOf('attachment') !== -1) {
|
||||||
if (!contentDisposition) {
|
filename = decodeURIComponent(contentDispositionHeader.split('filename=')[1].replace(/"/g, ''));
|
||||||
//throw new Error('Content-Disposition header not found for file ' + i);
|
} else {
|
||||||
} else {
|
// If the Content-Disposition header is not present or does not contain the filename, use a default filename
|
||||||
fileName = contentDisposition.split('filename=')[1].replace(/"/g, '');
|
filename = 'download';
|
||||||
}
|
|
||||||
console.log('Received response for file ' + i + ': ' + response);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let blob = await response.blob();
|
|
||||||
if (zipFiles) {
|
|
||||||
// Add the file to the ZIP archive
|
|
||||||
jszip.file(fileName, blob);
|
|
||||||
resolve();
|
|
||||||
} else {
|
|
||||||
// Download the file directly
|
|
||||||
let url = window.URL.createObjectURL(blob);
|
|
||||||
let a = document.createElement('a');
|
|
||||||
a.href = url;
|
|
||||||
a.download = fileName;
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
a.remove();
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error submitting request for file ' + i + ': ' + error);
|
|
||||||
|
|
||||||
// Set default values or fallbacks for error properties
|
|
||||||
let status = error && error.status || 500;
|
|
||||||
let statusText = error && error.statusText || 'Internal Server Error';
|
|
||||||
let message = error && error.message || 'An error occurred while processing your request.';
|
|
||||||
|
|
||||||
// Reject the Promise to signal that the request has failed
|
|
||||||
reject();
|
|
||||||
// Redirect to error page with Spring Boot error parameters
|
|
||||||
let url = '/error?status=' + status + '&error=' + encodeURIComponent(statusText) + '&message=' + encodeURIComponent(message);
|
|
||||||
window.location.href = url;
|
|
||||||
}
|
}
|
||||||
});
|
console.log("filename=" + filename)
|
||||||
|
|
||||||
// Update the progress bar as each request finishes
|
|
||||||
promise.then(function() {
|
const contentType = response.headers.get('Content-Type');
|
||||||
updateProgressBar(progressBar, files);
|
console.log("contentType=" + contentType)
|
||||||
});
|
// Check if the response is a PDF or an image
|
||||||
|
if (contentType.includes('pdf') || contentType.includes('image')) {
|
||||||
promises.push(promise);
|
response.blob().then(blob => {
|
||||||
}
|
console.log("pdf/image")
|
||||||
|
|
||||||
// Wait for all requests to finish
|
|
||||||
try {
|
|
||||||
await Promise.all(promises);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error while uploading files: ' + error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the progress bar
|
|
||||||
progressBar.css('width', '100%');
|
|
||||||
progressBar.attr('aria-valuenow', files.length);
|
|
||||||
|
|
||||||
// After all requests are finished, download the ZIP file if needed
|
|
||||||
if (zipFiles) {
|
|
||||||
try {
|
|
||||||
let content = await jszip.generateAsync({ type: "blob" });
|
|
||||||
let url = window.URL.createObjectURL(content);
|
|
||||||
let a = document.createElement('a');
|
|
||||||
a.href = url;
|
|
||||||
a.download = "files.zip";
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
a.remove();
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error generating ZIP file: ' + error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function updateProgressBar(progressBar, files) {
|
|
||||||
let progress = ((progressBar.attr('aria-valuenow') / files.length) * 100) + (100 / files.length);
|
|
||||||
progressBar.css('width', progress + '%');
|
|
||||||
progressBar.attr('aria-valuenow', parseInt(progressBar.attr('aria-valuenow')) + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="custom-file-chooser">
|
|
||||||
<div class="custom-file">
|
|
||||||
<input type="file" class="custom-file-input" th:name="${name}" th:id="${name}+'-input'" th:accept="${accept}" multiple>
|
|
||||||
<label class="custom-file-label" th:for="${name}+'-input'" th:text="${inputText}"></label>
|
|
||||||
</div>
|
|
||||||
<div class="selected-files"></div>
|
|
||||||
</div>
|
|
||||||
<br>
|
|
||||||
<div id="progressBarContainer" style="display: none; position: relative;">
|
|
||||||
<div class="progress" style="height: 1rem;">
|
|
||||||
<div id="progressBar" class="progress-bar progress-bar-striped progress-bar-animated bg-success" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%;">
|
|
||||||
<span class="sr-only">Loading...</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button type="button" class="btn btn-primary" id="show-game-btn" style="display:none;">Bored waiting?</button>
|
|
||||||
|
|
||||||
<div id="game-container-wrapper" class="game-container-wrapper" style="display: none;">
|
|
||||||
<div id="game-container">
|
|
||||||
<div id="lives">Lives: 3</div>
|
|
||||||
<div id="score">Score: 0</div>
|
|
||||||
<div id="high-score">High Score: 0</div>
|
|
||||||
<div id="level">Level: 1</div>
|
|
||||||
<img src="favicon.svg" class="player" id="player">
|
|
||||||
</div>
|
|
||||||
<style>
|
|
||||||
#game-container {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: 0;
|
|
||||||
padding-bottom: 75%; /* 4:3 aspect ratio */
|
|
||||||
background-color: transparent;
|
|
||||||
margin: auto;
|
|
||||||
overflow: hidden;
|
|
||||||
border: 2px solid black; /* Add border */
|
|
||||||
}
|
|
||||||
|
|
||||||
.pdf, .player, .projectile {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
.pdf {
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
}
|
|
||||||
.player {
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
}
|
|
||||||
.projectile {
|
|
||||||
background-color: black !important;
|
|
||||||
width: 5px;
|
|
||||||
height: 10px;
|
|
||||||
}
|
|
||||||
#score, #level, #lives, #high-score {
|
|
||||||
color: black;
|
|
||||||
font-family: sans-serif;
|
|
||||||
position: absolute;
|
|
||||||
font-size: calc(14px + 0.25vw); /* Reduced font size */
|
|
||||||
}
|
|
||||||
#score {
|
|
||||||
top: 10px;
|
|
||||||
left: 10px;
|
|
||||||
}
|
|
||||||
#lives {
|
|
||||||
top: 10px;
|
|
||||||
left: calc(7vw); /* Adjusted position */
|
|
||||||
}
|
|
||||||
#high-score {
|
|
||||||
top: 10px;
|
|
||||||
left: calc(14vw); /* Adjusted position */
|
|
||||||
}
|
|
||||||
#level {
|
|
||||||
top: 10px;
|
|
||||||
right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<script th:inline="javascript">
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
// Perform the appropriate action based on the download option
|
||||||
const fileInput = document.getElementById('fileInput-input');
|
if (downloadOption === 'sameWindow') {
|
||||||
|
console.log("same window")
|
||||||
|
|
||||||
|
// Open the file in the same window
|
||||||
|
window.location.href = URL.createObjectURL(blob);
|
||||||
|
} else if (downloadOption === 'newWindow') {
|
||||||
|
console.log("new window")
|
||||||
|
|
||||||
|
// Open the file in a new window
|
||||||
|
window.open(URL.createObjectURL(blob), '_blank');
|
||||||
|
} else {
|
||||||
|
console.log("else save")
|
||||||
|
|
||||||
|
// Download the file
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = URL.createObjectURL(blob);
|
||||||
|
link.download = filename;
|
||||||
|
link.click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (contentType.includes('json')) {
|
||||||
|
// Handle the JSON response
|
||||||
|
response.json().then(data => {
|
||||||
|
// Format the error message
|
||||||
|
const errorMessage = JSON.stringify(data, null, 2);
|
||||||
|
|
||||||
|
// Display the error message in an alert
|
||||||
|
alert(`An error occurred: ${errorMessage}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
response.blob().then(blob => {
|
||||||
|
console.log("else save 2 zip")
|
||||||
|
|
||||||
|
// For ZIP files or other file types, just download the file
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = URL.createObjectURL(blob);
|
||||||
|
link.download = filename;
|
||||||
|
link.click();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.log("error listener")
|
||||||
|
|
||||||
// Prevent default behavior for drag events
|
// Extract the error message and stack trace from the response
|
||||||
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
|
const errorMessage = error.message;
|
||||||
fileInput.addEventListener(eventName, preventDefaults, false);
|
const stackTrace = error.stack;
|
||||||
});
|
|
||||||
|
|
||||||
function preventDefaults(e) {
|
// Create an error message to display to the user
|
||||||
e.preventDefault();
|
const message = `${errorMessage}\n\n${stackTrace}`;
|
||||||
e.stopPropagation();
|
|
||||||
}
|
$('#submitBtn').text(submitButtonText);
|
||||||
|
|
||||||
|
// Display the error message to the user
|
||||||
|
alert(message);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
$('#submitBtn').text(submitButtonText);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
async function submitMultiPdfForm(event, url) {
|
||||||
|
// Get the selected PDF files
|
||||||
|
let files = $('#fileInput-input')[0].files;
|
||||||
|
|
||||||
// Add drop event listener
|
// Get the existing form data
|
||||||
fileInput.addEventListener('drop', handleDrop, false);
|
let formData = new FormData($('form')[0]);
|
||||||
|
formData.delete('fileInput');
|
||||||
|
|
||||||
function handleDrop(e) {
|
// Show the progress bar
|
||||||
const dt = e.dataTransfer;
|
$('#progressBarContainer').show();
|
||||||
const files = dt.files;
|
|
||||||
fileInput.files = files;
|
|
||||||
handleFileInputChange(fileInput)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Initialize the progress bar
|
||||||
|
let progressBar = $('#progressBar');
|
||||||
|
progressBar.css('width', '0%');
|
||||||
|
progressBar.attr('aria-valuenow', 0);
|
||||||
|
progressBar.attr('aria-valuemax', files.length);
|
||||||
|
|
||||||
|
// Check the flag in localStorage, default to 4
|
||||||
|
const zipThreshold = parseInt(localStorage.getItem('zipThreshold'), 10) || 4;
|
||||||
|
const zipFiles = files.length > zipThreshold;
|
||||||
|
|
||||||
|
// Initialize JSZip instance if needed
|
||||||
|
let jszip = null;
|
||||||
|
if (zipFiles) {
|
||||||
|
jszip = new JSZip();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Submit each PDF file in parallel
|
||||||
|
let promises = [];
|
||||||
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
let promise = new Promise(async function(resolve, reject) {
|
||||||
|
let fileFormData = new FormData();
|
||||||
|
fileFormData.append('fileInput', files[i]);
|
||||||
|
for (let pair of formData.entries()) {
|
||||||
|
fileFormData.append(pair[0], pair[1]);
|
||||||
|
}
|
||||||
|
console.log(fileFormData);
|
||||||
|
|
||||||
|
try {
|
||||||
|
let response = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
body: fileFormData
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response) {
|
||||||
|
throw new Error('Received null response for file ' + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Error submitting request for file ${i}: ${response.status} ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let contentDisposition = response.headers.get('content-disposition');
|
||||||
|
let fileName = "file.pdf"
|
||||||
|
if (!contentDisposition) {
|
||||||
|
//throw new Error('Content-Disposition header not found for file ' + i);
|
||||||
|
} else {
|
||||||
|
fileName = contentDisposition.split('filename=')[1].replace(/"/g, '');
|
||||||
|
}
|
||||||
|
console.log('Received response for file ' + i + ': ' + response);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
let blob = await response.blob();
|
||||||
|
if (zipFiles) {
|
||||||
|
// Add the file to the ZIP archive
|
||||||
|
jszip.file(fileName, blob);
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
// Download the file directly
|
||||||
|
let url = window.URL.createObjectURL(blob);
|
||||||
|
let a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = fileName;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
a.remove();
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error submitting request for file ' + i + ': ' + error);
|
||||||
|
|
||||||
|
// Set default values or fallbacks for error properties
|
||||||
|
let status = error && error.status || 500;
|
||||||
|
let statusText = error && error.statusText || 'Internal Server Error';
|
||||||
|
let message = error && error.message || 'An error occurred while processing your request.';
|
||||||
|
|
||||||
|
// Reject the Promise to signal that the request has failed
|
||||||
|
reject();
|
||||||
|
// Redirect to error page with Spring Boot error parameters
|
||||||
|
let url = '/error?status=' + status + '&error=' + encodeURIComponent(statusText) + '&message=' + encodeURIComponent(message);
|
||||||
|
window.location.href = url;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update the progress bar as each request finishes
|
||||||
|
promise.then(function() {
|
||||||
|
updateProgressBar(progressBar, files);
|
||||||
|
});
|
||||||
|
|
||||||
|
promises.push(promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for all requests to finish
|
||||||
|
try {
|
||||||
|
await Promise.all(promises);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error while uploading files: ' + error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the progress bar
|
||||||
|
progressBar.css('width', '100%');
|
||||||
|
progressBar.attr('aria-valuenow', files.length);
|
||||||
|
|
||||||
|
// After all requests are finished, download the ZIP file if needed
|
||||||
|
if (zipFiles) {
|
||||||
|
try {
|
||||||
|
let content = await jszip.generateAsync({ type: "blob" });
|
||||||
|
let url = window.URL.createObjectURL(content);
|
||||||
|
let a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = "files.zip";
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
a.remove();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error generating ZIP file: ' + error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function updateProgressBar(progressBar, files) {
|
||||||
|
let progress = ((progressBar.attr('aria-valuenow') / files.length) * 100) + (100 / files.length);
|
||||||
|
progressBar.css('width', progress + '%');
|
||||||
|
progressBar.attr('aria-valuenow', parseInt(progressBar.attr('aria-valuenow')) + 1);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="custom-file-chooser">
|
||||||
|
<div class="custom-file">
|
||||||
|
<input type="file" class="custom-file-input" th:name="${name}" th:id="${name}+'-input'" th:accept="${accept}" multiple>
|
||||||
|
<label class="custom-file-label" th:for="${name}+'-input'" th:text="${inputText}"></label>
|
||||||
|
</div>
|
||||||
|
<div class="selected-files"></div>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<div id="progressBarContainer" style="display: none; position: relative;">
|
||||||
|
<div class="progress" style="height: 1rem;">
|
||||||
|
<div id="progressBar" class="progress-bar progress-bar-striped progress-bar-animated bg-success" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%;">
|
||||||
|
<span class="sr-only">Loading...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-primary" id="show-game-btn" style="display:none;">Bored waiting?</button>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<script th:inline="javascript">
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
const fileInput = document.getElementById([[${name+"-input"}]]);
|
||||||
|
|
||||||
|
// Prevent default behavior for drag events
|
||||||
|
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
|
||||||
|
fileInput.addEventListener(eventName, preventDefaults, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
function preventDefaults(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add drop event listener
|
||||||
|
fileInput.addEventListener('drop', handleDrop, false);
|
||||||
|
|
||||||
|
function handleDrop(e) {
|
||||||
|
const dt = e.dataTransfer;
|
||||||
|
const files = dt.files;
|
||||||
|
fileInput.files = files;
|
||||||
|
handleFileInputChange(fileInput)
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$([[${"#"+name+"-input"}]]).on("change", function() {
|
||||||
$([[${"#"+name+"-input"}]]).on("change", function() {
|
|
||||||
handleFileInputChange(this);
|
handleFileInputChange(this);
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleFileInputChange(inputElement) {
|
function handleFileInputChange(inputElement) {
|
||||||
const files = $(inputElement).get(0).files;
|
const files = $(inputElement).get(0).files;
|
||||||
const fileNames = Array.from(files).map(f => f.name);
|
const fileNames = Array.from(files).map(f => f.name);
|
||||||
const selectedFilesContainer = $(inputElement).siblings(".selected-files");
|
const selectedFilesContainer = $(inputElement).siblings(".selected-files");
|
||||||
selectedFilesContainer.empty();
|
selectedFilesContainer.empty();
|
||||||
fileNames.forEach(fileName => {
|
fileNames.forEach(fileName => {
|
||||||
selectedFilesContainer.append("<div>" + fileName + "</div>");
|
selectedFilesContainer.append("<div>" + fileName + "</div>");
|
||||||
});
|
});
|
||||||
console.log("fileNames.length=" + fileNames.length)
|
console.log("fileNames.length=" + fileNames.length)
|
||||||
if (fileNames.length === 1) {
|
if (fileNames.length === 1) {
|
||||||
$(inputElement).siblings(".custom-file-label").addClass("selected").html(fileNames[0]);
|
$(inputElement).siblings(".custom-file-label").addClass("selected").html(fileNames[0]);
|
||||||
} else if (fileNames.length > 1) {
|
} else if (fileNames.length > 1) {
|
||||||
$(inputElement).siblings(".custom-file-label").addClass("selected").html(fileNames.length + " files selected");
|
$(inputElement).siblings(".custom-file-label").addClass("selected").html(fileNames.length + " files selected");
|
||||||
} else {
|
} else {
|
||||||
$(inputElement).siblings(".custom-file-label").addClass("selected").html([[#{pdfPrompt}]]);
|
$(inputElement).siblings(".custom-file-label").addClass("selected").html([[#{pdfPrompt}]]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.custom-file-label {
|
.custom-file-label {
|
||||||
padding-right: 90px;
|
padding-right: 90px;
|
||||||
}
|
}
|
||||||
|
.selected-files {
|
||||||
.selected-files {
|
margin-top: 10px;
|
||||||
margin-top: 10px;
|
max-height: 150px;
|
||||||
max-height: 150px;
|
overflow-y: auto;
|
||||||
overflow-y: auto;
|
white-space: pre-wrap;
|
||||||
white-space: pre-wrap;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
</th:block>
|
</th:block>
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
<th:block th:insert="~{fragments/common :: game}"></th:block>
|
||||||
<div id="page-container">
|
<div id="page-container">
|
||||||
<div id="content-wrap">
|
<div id="content-wrap">
|
||||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
<th:block th:insert="~{fragments/common :: game}"></th:block>
|
||||||
<div id="page-container">
|
<div id="page-container">
|
||||||
<div id="content-wrap">
|
<div id="content-wrap">
|
||||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||||
|
|
|
@ -12,32 +12,41 @@
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/interact.js/1.10.11/interact.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/interact.js/1.10.11/interact.min.js"></script>
|
||||||
<style>
|
<style>
|
||||||
#pdf-container {
|
#pdf-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pdf-canvas {
|
#pdf-canvas {
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#signature-canvas {
|
#signature-canvas {
|
||||||
border: 1px solid red;
|
border: 1px solid red;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
touch-action: none;
|
touch-action: none;
|
||||||
top: 10px; /* Make sure this value matches the margin-top of #pdf-canvas */
|
top: 10px; /* Make sure this value matches the margin-top of #pdf-canvas */
|
||||||
left: 0;
|
left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.signature-pad-container {
|
#signature-pad-container {
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
display: none;
|
display: block;
|
||||||
margin-top: 10px;
|
text-align: center;
|
||||||
}
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#signature-pad-canvas {
|
||||||
|
background: rgba(125,125,125,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#pdf-canvas {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
<th:block th:insert="~{fragments/common.html :: game}"></th:block>
|
||||||
<div id="page-container">
|
<div id="page-container">
|
||||||
<div id="content-wrap">
|
<div id="content-wrap">
|
||||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||||
|
@ -46,20 +55,30 @@
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<h2 th:text="#{extractImages.header}"></h2>
|
<h2 th:text="#{extractImages.header}"></h2>
|
||||||
<input type="file" id="pdf-upload" accept=".pdf" class="btn btn-outline-primary mb-2" />
|
<div th:replace="~{fragments/common :: fileSelector(name='pdf-upload', multiple=false, accept='application/pdf')}"></div>
|
||||||
<input type="file" id="signature-upload" accept="image/*" class="btn btn-outline-primary mb-2" />
|
|
||||||
<button id="toggle-draw-signature" class="btn btn-outline-primary mb-2">Draw signature</button>
|
<div class = "btn-group">
|
||||||
<button id="download-pdf" class="btn btn-outline-primary mb-2">Download PDF</button>
|
<input type="radio" class="btn-check" name="signature-type" id="draw-signature" autocomplete="off" checked>
|
||||||
|
<label class="btn btn-outline-secondary" for="draw-signature">Draw signature</label>
|
||||||
|
<input type="radio" class="btn-check" name="signature-type" id="import-image" autocomplete="off">
|
||||||
|
<label class="btn btn-outline-secondary" for="import-image">Import image</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div th:replace="~{fragments/common :: fileSelector(name='signature-upload', multiple=false, accept='image/*', inputText=#{imgPrompt})}"></div>
|
||||||
|
<!-- Signature Pad -->
|
||||||
|
<div id="signature-pad-container">
|
||||||
|
<canvas id="signature-pad-canvas"></canvas>
|
||||||
|
<br>
|
||||||
|
<button id="clear-signature" class="btn btn-outline-danger mt-2">Clear</button>
|
||||||
|
<button id="save-signature" class="btn btn-outline-success mt-2">Save</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="pdf-container">
|
<div id="pdf-container">
|
||||||
<canvas id="pdf-canvas"></canvas>
|
<canvas id="pdf-canvas"></canvas>
|
||||||
<canvas id="signature-canvas" hidden style="position: absolute;" data-scale="1"></canvas>
|
<canvas id="signature-canvas" hidden style="position: absolute;" data-scale="1"></canvas>
|
||||||
</div>
|
</div>
|
||||||
<!-- Signature Pad -->
|
|
||||||
<div class="signature-pad-container">
|
<button id="download-pdf" class="btn btn-primary mb-2">Download PDF</button>
|
||||||
<canvas id="signature-pad-canvas"></canvas>
|
|
||||||
<button id="clear-signature" class="btn btn-outline-danger mt-2">Clear</button>
|
|
||||||
<button id="save-signature" class="btn btn-outline-success mt-2">Save</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -67,93 +86,58 @@
|
||||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const pdfUpload = document.getElementById('pdf-upload');
|
const pdfUpload = document.querySelector('input[name=pdf-upload]');
|
||||||
const signatureUpload = document.getElementById('signature-upload');
|
const signatureUpload = document.querySelector('input[name=signature-upload]');
|
||||||
const pdfCanvas = document.getElementById('pdf-canvas');
|
const pdfCanvas = document.getElementById('pdf-canvas');
|
||||||
const signatureCanvas = document.getElementById('signature-canvas');
|
const signatureCanvas = document.getElementById('signature-canvas');
|
||||||
const downloadPdfBtn = document.getElementById('download-pdf');
|
const downloadPdfBtn = document.getElementById('download-pdf');
|
||||||
const toggleDrawSignatureBtn = document.getElementById('toggle-draw-signature');
|
const signaturePadContainer = document.getElementById('signature-pad-container')
|
||||||
const signaturePadCanvas = document.getElementById('signature-pad-canvas');
|
const signaturePadCanvas = document.getElementById('signature-pad-canvas');
|
||||||
const clearSignatureBtn = document.getElementById('clear-signature');
|
const clearSignatureBtn = document.getElementById('clear-signature');
|
||||||
const saveSignatureBtn = document.getElementById('save-signature');
|
const saveSignatureBtn = document.getElementById('save-signature');
|
||||||
|
|
||||||
const signaturePad = new SignaturePad(signaturePadCanvas, {
|
|
||||||
minWidth: 1,
|
|
||||||
maxWidth: 2,
|
|
||||||
penColor: 'black',
|
|
||||||
});
|
|
||||||
|
|
||||||
|
document.querySelector('input[name=signature-upload]').closest(".custom-file-chooser").style.display = "none"
|
||||||
const pdfCtx = pdfCanvas.getContext('2d');
|
|
||||||
let pdfDoc = null;
|
|
||||||
|
|
||||||
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.12.313/pdf.worker.min.js';
|
const signaturePad = new SignaturePad(signaturePadCanvas, {
|
||||||
|
minWidth: 1,
|
||||||
|
maxWidth: 2,
|
||||||
|
penColor: 'black',
|
||||||
|
});
|
||||||
|
|
||||||
async function loadPdf(pdfData) {
|
|
||||||
pdfDoc = await pdfjsLib.getDocument({ data: pdfData }).promise;
|
const pdfCtx = pdfCanvas.getContext('2d');
|
||||||
renderPage(1);
|
let pdfDoc = null;
|
||||||
}
|
|
||||||
|
|
||||||
pdfUpload.addEventListener('change', async (event) => {
|
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.12.313/pdf.worker.min.js';
|
||||||
const file = event.target.files[0];
|
|
||||||
if (file) {
|
|
||||||
const pdfData = await file.arrayBuffer();
|
|
||||||
loadPdf(pdfData);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
signatureUpload.addEventListener('change', async (event) => {
|
pdfUpload.addEventListener('change', async (event) => {
|
||||||
const file = event.target.files[0];
|
const file = event.target.files[0];
|
||||||
if (file) {
|
if (file) {
|
||||||
const reader = new FileReader();
|
const pdfData = await file.arrayBuffer();
|
||||||
reader.onload = (e) => {
|
pdfDoc = await pdfjsLib.getDocument({ data: pdfData }).promise;
|
||||||
const img = new Image();
|
renderPage(1);
|
||||||
img.src = e.target.result;
|
}
|
||||||
img.onload = () => {
|
});
|
||||||
const ctx = signatureCanvas.getContext('2d');
|
|
||||||
ctx.drawImage(img, 0, 0, img.width, img.height);
|
|
||||||
signatureCanvas.width = img.width;
|
|
||||||
signatureCanvas.height = img.height;
|
|
||||||
signatureCanvas.hidden = false;
|
|
||||||
setTimeout(() => {
|
|
||||||
const x = 0;
|
|
||||||
const y = 0;
|
|
||||||
signatureCanvas.style.transform = `translate(${x}px, ${y}px)`;
|
|
||||||
signatureCanvas.setAttribute('data-x', x);
|
|
||||||
signatureCanvas.setAttribute('data-y', y);
|
|
||||||
}, 0);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
clearSignatureBtn.addEventListener('click', () => {
|
||||||
|
signaturePad.clear();
|
||||||
|
});
|
||||||
|
|
||||||
let usingSignaturePad = false;
|
$("input[name=signature-type]").change(function() {
|
||||||
|
const drawSignatureInput = document.getElementById('draw-signature');
|
||||||
|
signaturePadContainer.style.display = drawSignatureInput.checked ? 'block' : 'none';
|
||||||
|
document.querySelector('input[name=signature-upload]').closest(".custom-file-chooser").style.display = drawSignatureInput.checked ? 'none' : 'block';
|
||||||
|
|
||||||
toggleDrawSignatureBtn.addEventListener('click', () => {
|
if (drawSignatureInput.checked) {
|
||||||
usingSignaturePad = !usingSignaturePad;
|
populateSignatureFromPad();
|
||||||
if (usingSignaturePad) {
|
} else {
|
||||||
signatureCanvas.width = 0;
|
populateSignatureFromFileUpload();
|
||||||
signatureCanvas.height = 0;
|
}
|
||||||
}
|
});
|
||||||
document.querySelector('.signature-pad-container').style.display = usingSignaturePad ? 'block' : 'none';
|
|
||||||
toggleDrawSignatureBtn.textContent = usingSignaturePad
|
function populateSignature(imgUrl) {
|
||||||
? 'Import signature image'
|
|
||||||
: 'Draw signature';
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
clearSignatureBtn.addEventListener('click', () => {
|
|
||||||
signaturePad.clear();
|
|
||||||
});
|
|
||||||
|
|
||||||
saveSignatureBtn.addEventListener('click', async () => {
|
|
||||||
if (!signaturePad.isEmpty()) {
|
|
||||||
const dataURL = signaturePad.toDataURL();
|
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
img.src = dataURL;
|
|
||||||
img.onload = () => {
|
img.onload = () => {
|
||||||
const ctx = signatureCanvas.getContext('2d');
|
const ctx = signatureCanvas.getContext('2d');
|
||||||
ctx.clearRect(0, 0, signatureCanvas.width, signatureCanvas.height);
|
ctx.clearRect(0, 0, signatureCanvas.width, signatureCanvas.height);
|
||||||
|
@ -161,122 +145,151 @@
|
||||||
signatureCanvas.height = img.height;
|
signatureCanvas.height = img.height;
|
||||||
ctx.drawImage(img, 0, 0, img.width, img.height);
|
ctx.drawImage(img, 0, 0, img.width, img.height);
|
||||||
signatureCanvas.hidden = false;
|
signatureCanvas.hidden = false;
|
||||||
setTimeout(() => {
|
|
||||||
const x = 0;
|
const x = 0;
|
||||||
const y = 0;
|
const y = 0;
|
||||||
signatureCanvas.style.transform = `translate(${x}px, ${y}px)`;
|
signatureCanvas.style.transform = `translate(${x}px, ${y}px)`;
|
||||||
signatureCanvas.setAttribute('data-x', x);
|
signatureCanvas.setAttribute('data-x', x);
|
||||||
signatureCanvas.setAttribute('data-y', y);
|
signatureCanvas.setAttribute('data-y', y);
|
||||||
}, 0);
|
|
||||||
|
// calcualte the max size
|
||||||
|
const containerWidth = parseInt(getComputedStyle(pdfCanvas).width.replace('px',''));
|
||||||
|
const containerHeight = parseInt(getComputedStyle(pdfCanvas).height.replace('px',''));
|
||||||
|
const containerAspectRatio = containerWidth / containerHeight;
|
||||||
|
const imgAspectRatio = img.width / img.height;
|
||||||
|
if (imgAspectRatio > containerAspectRatio) {
|
||||||
|
const width = Math.min(img.width, containerWidth);
|
||||||
|
signatureCanvas.style.width = width+'px';
|
||||||
|
signatureCanvas.style.height = (width/imgAspectRatio)+'px';
|
||||||
|
} else {
|
||||||
|
const height = Math.min(img.height, containerHeight);
|
||||||
|
signatureCanvas.style.width = (height*imgAspectRatio)+'px';
|
||||||
|
signatureCanvas.style.height = height+'px';
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
img.src = imgUrl;
|
||||||
}
|
}
|
||||||
});
|
function populateSignatureFromFileUpload() {
|
||||||
|
const file = signatureUpload.files[0];
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (e) => populateSignature(e.target.result);
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
}
|
||||||
|
function populateSignatureFromPad() {
|
||||||
|
if (signaturePad.isEmpty()) return;
|
||||||
|
|
||||||
|
const dataURL = signaturePad.toDataURL();
|
||||||
|
populateSignature(dataURL);
|
||||||
|
}
|
||||||
|
signatureUpload.addEventListener('change', populateSignatureFromFileUpload);
|
||||||
|
saveSignatureBtn.addEventListener('click', populateSignatureFromPad);
|
||||||
|
|
||||||
|
|
||||||
function renderPage(pageNum) {
|
function renderPage(pageNum) {
|
||||||
pdfDoc.getPage(pageNum).then((page) => {
|
pdfDoc.getPage(pageNum).then((page) => {
|
||||||
const viewport = page.getViewport({ scale: 1 });
|
const viewport = page.getViewport({ scale: 1 });
|
||||||
pdfCanvas.width = viewport.width;
|
pdfCanvas.width = viewport.width;
|
||||||
pdfCanvas.height = viewport.height;
|
pdfCanvas.height = viewport.height;
|
||||||
|
|
||||||
const renderCtx = {
|
const renderCtx = {
|
||||||
canvasContext: pdfCtx,
|
canvasContext: pdfCtx,
|
||||||
viewport: viewport,
|
viewport: viewport,
|
||||||
};
|
};
|
||||||
|
|
||||||
page.render(renderCtx);
|
page.render(renderCtx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
interact('#signature-canvas')
|
||||||
|
.draggable({
|
||||||
|
listeners: {
|
||||||
|
move: (event) => {
|
||||||
|
const target = event.target;
|
||||||
|
const x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx;
|
||||||
|
const y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy;
|
||||||
|
|
||||||
|
target.style.transform = `translate(${x}px, ${y}px)`;
|
||||||
|
target.setAttribute('data-x', x);
|
||||||
|
target.setAttribute('data-y', y);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.resizable({
|
||||||
|
edges: { left: true, right: true, bottom: true, top: true },
|
||||||
|
listeners: {
|
||||||
|
move: (event) => {
|
||||||
|
const target = event.target;
|
||||||
|
const x = (parseFloat(target.getAttribute('data-x')) || 0);
|
||||||
|
const y = (parseFloat(target.getAttribute('data-y')) || 0);
|
||||||
|
|
||||||
|
const newWidth = event.rect.width;
|
||||||
|
const newHeight = event.rect.height;
|
||||||
|
const scale = newWidth / target.width;
|
||||||
|
|
||||||
|
target.style.width = newWidth + 'px';
|
||||||
|
target.style.height = newHeight + 'px';
|
||||||
|
target.setAttribute('data-scale', scale);
|
||||||
|
|
||||||
|
target.style.transform = `translate(${x}px, ${y}px)`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
modifiers: [
|
||||||
|
interact.modifiers.restrictSize({
|
||||||
|
min: { width: 50, height: 50 },
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
inertia: true,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
interact('#signature-canvas')
|
|
||||||
.draggable({
|
|
||||||
listeners: {
|
|
||||||
move: (event) => {
|
|
||||||
const target = event.target;
|
|
||||||
const x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx;
|
|
||||||
const y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy;
|
|
||||||
|
|
||||||
target.style.transform = `translate(${x}px, ${y}px)`;
|
|
||||||
target.setAttribute('data-x', x);
|
|
||||||
target.setAttribute('data-y', y);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.resizable({
|
|
||||||
edges: { left: true, right: true, bottom: true, top: true },
|
|
||||||
listeners: {
|
|
||||||
move: (event) => {
|
|
||||||
const target = event.target;
|
|
||||||
const x = (parseFloat(target.getAttribute('data-x')) || 0);
|
|
||||||
const y = (parseFloat(target.getAttribute('data-y')) || 0);
|
|
||||||
|
|
||||||
const newWidth = event.rect.width;
|
|
||||||
const newHeight = event.rect.height;
|
|
||||||
const scale = newWidth / target.width;
|
|
||||||
|
|
||||||
target.style.width = newWidth + 'px';
|
|
||||||
target.style.height = newHeight + 'px';
|
|
||||||
target.setAttribute('data-scale', scale);
|
|
||||||
|
|
||||||
target.style.transform = `translate(${x}px, ${y}px)`;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
modifiers: [
|
|
||||||
interact.modifiers.restrictSize({
|
|
||||||
min: { width: 50, height: 50 },
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
inertia: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
async function getSignatureImage() {
|
async function getSignatureImage() {
|
||||||
const dataURL = signatureCanvas.toDataURL();
|
const dataURL = signatureCanvas.toDataURL();
|
||||||
return dataURLToArrayBuffer(dataURL);
|
return dataURLToArrayBuffer(dataURL);
|
||||||
}
|
|
||||||
|
|
||||||
downloadPdfBtn.addEventListener('click', async () => {
|
|
||||||
if (pdfDoc) {
|
|
||||||
const pdfBytes = await pdfDoc.getData();
|
|
||||||
const pdfDocModified = await PDFLib.PDFDocument.load(pdfBytes);
|
|
||||||
|
|
||||||
if (signatureCanvas) {
|
|
||||||
const signatureBytes = await getSignatureImage();
|
|
||||||
const signatureImageObject = await pdfDocModified.embedPng(signatureBytes);
|
|
||||||
|
|
||||||
const pageIndex = 0; // Choose the page index where the signature should be added (0 is the first page)
|
|
||||||
const page = pdfDocModified.getPages()[pageIndex];
|
|
||||||
|
|
||||||
const targetElement = signatureCanvas;
|
|
||||||
const x = parseFloat(targetElement.getAttribute('data-x')) || 0;
|
|
||||||
const y = parseFloat(targetElement.getAttribute('data-y')) || 0;
|
|
||||||
const scale = parseFloat(targetElement.getAttribute('data-scale')) || 1;
|
|
||||||
|
|
||||||
page.drawImage(signatureImageObject, {
|
|
||||||
x: x,
|
|
||||||
y: page.getHeight() - y - (targetElement.height * scale),
|
|
||||||
width: targetElement.width * scale,
|
|
||||||
height: targetElement.height * scale,
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const modifiedPdfBytes = await pdfDocModified.save();
|
|
||||||
const blob = new Blob([modifiedPdfBytes], { type: 'application/pdf' });
|
|
||||||
const link = document.createElement('a');
|
|
||||||
link.href = URL.createObjectURL(blob);
|
|
||||||
link.download = 'signed-document.pdf';
|
|
||||||
link.click();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
downloadPdfBtn.addEventListener('click', async () => {
|
||||||
|
if (pdfDoc) {
|
||||||
|
const pdfBytes = await pdfDoc.getData();
|
||||||
|
const pdfDocModified = await PDFLib.PDFDocument.load(pdfBytes);
|
||||||
|
|
||||||
|
if (signatureCanvas) {
|
||||||
|
const signatureBytes = await getSignatureImage();
|
||||||
|
const signatureImageObject = await pdfDocModified.embedPng(signatureBytes);
|
||||||
|
|
||||||
|
const pageIndex = 0; // Choose the page index where the signature should be added (0 is the first page)
|
||||||
|
const page = pdfDocModified.getPages()[pageIndex];
|
||||||
|
|
||||||
|
const targetElement = signatureCanvas;
|
||||||
|
const x = parseFloat(targetElement.getAttribute('data-x')) || 0;
|
||||||
|
const y = parseFloat(targetElement.getAttribute('data-y')) || 0;
|
||||||
|
const scale = parseFloat(targetElement.getAttribute('data-scale')) || 1;
|
||||||
|
|
||||||
|
page.drawImage(signatureImageObject, {
|
||||||
|
x: x,
|
||||||
|
y: page.getHeight() - y - (targetElement.height * scale),
|
||||||
|
width: targetElement.width * scale,
|
||||||
|
height: targetElement.height * scale,
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const modifiedPdfBytes = await pdfDocModified.save();
|
||||||
|
const blob = new Blob([modifiedPdfBytes], { type: 'application/pdf' });
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = URL.createObjectURL(blob);
|
||||||
|
link.download = 'signed-document.pdf';
|
||||||
|
link.click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function dataURLToArrayBuffer(dataURL) {
|
||||||
|
const response = await fetch(dataURL);
|
||||||
|
return response.arrayBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
async function dataURLToArrayBuffer(dataURL) {
|
|
||||||
const response = await fetch(dataURL);
|
|
||||||
return response.arrayBuffer();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
</script>
|
|
Loading…
Reference in a new issue