Restyled the sign page and cleaned up code in various places.

This commit is contained in:
Saud Fatayerji 2023-05-04 00:07:51 +03:00
parent fc0af56136
commit b2a29e2b13
16 changed files with 685 additions and 674 deletions

4
.gitignore vendored
View file

@ -109,4 +109,6 @@ local.properties
*.tar.gz *.tar.gz
*.rar *.rar
/build /build
/.vscode

View file

@ -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;
}

View file

@ -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;

View file

@ -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>

View file

@ -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");

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>