Image extraction and conversion to formats Multi parallel file execution for all forms so you can input multiple files quickly Any file at all pdf using libreoffice, super powerful Sadly makes docker image larger but worth it OCR PDF using ocr my pdf Works awesomely for adding text to a image Improved compression using ocr my pdf app Settings page with custom download options such as - open in same window - open in new window - download - download as zip Update detection in settings page it should show notification if there is a update (very hidden) UI cleanups Add other image formats to PDF to Image Various fies to icons, and pdf.js usage
385 lines
16 KiB
HTML
385 lines
16 KiB
HTML
<head th:fragment="head">
|
|
|
|
<!-- Metadata -->
|
|
<meta charset="UTF-8">
|
|
<title th:text="'S-PDF ' + ${title}"></title>
|
|
<link rel="shortcut icon" href="favicon.svg">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
<!-- jQuery -->
|
|
<script src="js/jquery.min.js"></script>
|
|
|
|
<!-- jQuery -->
|
|
<script src="js/jszip.min.js"></script>
|
|
|
|
<!-- Bootstrap -->
|
|
<script src="js/popper.min.js"></script>
|
|
<script src="js/bootstrap.min.js"></script>
|
|
<link rel="stylesheet" href="css/bootstrap.min.css">
|
|
<link rel="stylesheet" href="css/bootstrap-icons.css">
|
|
|
|
<!-- PDF.js -->
|
|
<script src="pdfjs/pdf.js"></script>
|
|
|
|
<!-- Custom -->
|
|
<link rel="stylesheet" href="css/general.css">
|
|
<link rel="stylesheet" th:href="@{css/dark-mode.css}" id="dark-mode-styles">
|
|
|
|
<script>
|
|
function toggleDarkMode() {
|
|
var checkbox = document.getElementById("toggle-dark-mode");
|
|
var darkModeStyles = document.getElementById("dark-mode-styles");
|
|
if (checkbox.checked) {
|
|
localStorage.setItem("dark-mode", "on");
|
|
darkModeStyles.disabled = false;
|
|
} else {
|
|
localStorage.setItem("dark-mode", "off");
|
|
darkModeStyles.disabled = true;
|
|
}
|
|
}
|
|
|
|
$(document).ready(function() {
|
|
var darkModeStyles = document.getElementById("dark-mode-styles");
|
|
var checkbox = document.getElementById("toggle-dark-mode");
|
|
|
|
// Check if the user has already set a preference
|
|
if (localStorage.getItem("dark-mode") == "on") {
|
|
darkModeStyles.disabled = false;
|
|
checkbox.checked = true;
|
|
} else if (localStorage.getItem("dark-mode") == "off") {
|
|
darkModeStyles.disabled = true;
|
|
checkbox.checked = false;
|
|
} else {
|
|
// Check the OS's default dark mode setting
|
|
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
|
darkModeStyles.disabled = false;
|
|
checkbox.checked = true;
|
|
} else {
|
|
darkModeStyles.disabled = true;
|
|
checkbox.checked = false;
|
|
}
|
|
}
|
|
});
|
|
|
|
</script>
|
|
</head>
|
|
<th:block th:fragment="fileSelector(name, multiple)">
|
|
<div class="custom-file-chooser">
|
|
<div class="custom-file">
|
|
<input type="file" class="custom-file-input" th:name="${name}" th:id="${name}+'-input'" multiple>
|
|
<label class="custom-file-label" th:for="${name}+'-input'" th:text="#{pdfPrompt}"></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>
|
|
|
|
<script>
|
|
|
|
$('form').submit(function(event) {
|
|
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;
|
|
if (!multiple && files.length > 1) {
|
|
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 = 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}`;
|
|
|
|
// Display the error message to the user
|
|
alert(message);
|
|
});
|
|
}
|
|
});
|
|
|
|
async function submitMultiPdfForm(event,url) {
|
|
// Get the selected PDF files
|
|
var files = $('#fileInput-input')[0].files;
|
|
|
|
// Get the existing form data
|
|
var formData = new FormData($('form')[0]);
|
|
formData.delete('fileInput');
|
|
|
|
// Show the progress bar
|
|
$('#progressBarContainer').show();
|
|
|
|
// Initialize the progress bar
|
|
var progressBar = $('#progressBar');
|
|
progressBar.css('width', '0%');
|
|
progressBar.attr('aria-valuenow', 0);
|
|
progressBar.attr('aria-valuemax', files.length);
|
|
|
|
// Check the flag in localStorage
|
|
const zipFiles = localStorage.getItem('zipParallelFiles') === 'true';
|
|
|
|
// Initialize JSZip instance if needed
|
|
let jszip = null;
|
|
if (zipFiles) {
|
|
jszip = new JSZip();
|
|
}
|
|
|
|
|
|
// Submit each PDF file in parallel
|
|
var promises = [];
|
|
for (var i = 0; i < files.length; i++) {
|
|
var promise = new Promise(function(resolve, reject) {
|
|
var fileFormData = new FormData();
|
|
fileFormData.append('fileInput', files[i]);
|
|
for (var pair of formData.entries()) {
|
|
fileFormData.append(pair[0], pair[1]);
|
|
}
|
|
console.log(fileFormData);
|
|
|
|
fetch(url, {
|
|
method: 'POST',
|
|
body: fileFormData
|
|
}).then(function(response) {
|
|
if (!response) {
|
|
throw new Error('Received null response for file ' + i);
|
|
}
|
|
console.log('Received response for file ' + i + ': ' + response);
|
|
|
|
var contentDisposition = response.headers.get('content-disposition');
|
|
var fileName = contentDisposition.split('filename=')[1].replace(/"/g, '');
|
|
|
|
response.blob().then(function (blob) {
|
|
if (zipFiles) {
|
|
// Add the file to the ZIP archive
|
|
jszip.file(fileName, blob);
|
|
resolve();
|
|
} else {
|
|
// Download the file directly
|
|
var url = window.URL.createObjectURL(blob);
|
|
var a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = fileName;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
a.remove();
|
|
resolve();
|
|
}
|
|
});
|
|
|
|
}).catch(function(error) {
|
|
console.error('Error submitting request for file ' + i + ': ' + error);
|
|
|
|
// Set default values or fallbacks for error properties
|
|
var status = error && error.status || 500;
|
|
var statusText = error && error.statusText || 'Internal Server Error';
|
|
var 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
|
|
var 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() {
|
|
var 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);
|
|
});
|
|
|
|
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) {
|
|
jszip.generateAsync({ type: "blob" }).then(function (content) {
|
|
var url = window.URL.createObjectURL(content);
|
|
var a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = "files.zip";
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
a.remove();
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
|
|
</script>
|
|
|
|
|
|
<script th:inline="javascript">
|
|
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
const fileInput = document.getElementById('fileInput-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() {
|
|
handleFileInputChange(this);
|
|
});
|
|
|
|
function handleFileInputChange(inputElement) {
|
|
const files = $(inputElement).get(0).files;
|
|
const fileNames = Array.from(files).map(f => f.name);
|
|
const selectedFilesContainer = $(inputElement).siblings(".selected-files");
|
|
selectedFilesContainer.empty();
|
|
fileNames.forEach(fileName => {
|
|
selectedFilesContainer.append("<div>" + fileName + "</div>");
|
|
});
|
|
if (fileNames.length === 1) {
|
|
$(inputElement).siblings(".custom-file-label").addClass("selected").html(fileNames[0]);
|
|
} else if (fileNames.length > 1) {
|
|
$(inputElement).siblings(".custom-file-label").addClass("selected").html(fileNames.length + " files selected");
|
|
} else {
|
|
$(inputElement).siblings(".custom-file-label").addClass("selected").html([[#{pdfPrompt}]]);
|
|
}
|
|
|
|
}
|
|
|
|
</script>
|
|
|
|
<style>
|
|
.custom-file-label {
|
|
padding-right: 90px;
|
|
}
|
|
|
|
.selected-files {
|
|
margin-top: 10px;
|
|
max-height: 150px;
|
|
overflow-y: auto;
|
|
white-space: pre-wrap;
|
|
}
|
|
|
|
</style>
|
|
|
|
|
|
</th:block> |