Image stuff (#77)

Features
---------
Image to PDF supports multiple images, stretching and auto rotation
File inputs now only search for wanted file type
Settings now has a zip range so it can zip if you have more than x downloads (default 4)

extras
---------
DevTools support for easier development
Fix for temporary files for thread safety
This commit is contained in:
Anthony Stirling
2023-03-25 22:16:26 +00:00
committed by GitHub
parent f866c8a61f
commit a2a27e2216
34 changed files with 243 additions and 153 deletions

View File

@@ -15,9 +15,9 @@
<div class="col-md-6">
<h2 th:text="#{addImage.header}"></h2>
<form method="post" th:action="@{add-image}" enctype="multipart/form-data">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
<div class="custom-file">
<input type="file" class="custom-file-input" id="fileInput2" name="fileInput2" required>
<input type="file" class="custom-file-input" id="fileInput2" name="fileInput2" accept="image/*" required>
<label class="custom-file-label" for="fileInput2" th:text="#{imgPrompt}"></label>
</div>
<div class="form-group">

View File

@@ -15,7 +15,7 @@
<div class="col-md-6">
<h2 th:text="#{compress.header}"></h2>
<form action="#" th:action="@{/compress-pdf}" method="post" enctype="multipart/form-data">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
<div>
<label for="optimizeLevel" th:text="#{compress.selectText.1}"></label>
<select name="optimizeLevel" id="optimizeLevel">

View File

@@ -15,13 +15,56 @@
<h2 th:text="#{imageToPDF.header}"></h2>
<form method="post" enctype="multipart/form-data" th:action="@{img-to-pdf}">
<div class="custom-file">
<input type="file" class="custom-file-input" id="fileInput" name="fileInput" required>
<label class="custom-file-label" for="fileInput" th:text="#{imgPrompt}"></label>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='image/*')}"></div>
<div class="form-check">
<input type="checkbox" class="form-check-input" name="stretchToFit" id="stretchToFit">
<label class="ml-3" for="stretchToFit" th:text=#{imageToPDF.selectText.1}></label>
</div>
<div class="form-check">
<input type="checkbox" class="form-check-input" name="autoRotate" id="autoRotate">
<label class="ml-3" for="autoRotate" th:text=#{imageToPDF.selectText.2}></label>
</div>
<br>
<input type="hidden" id="override" name="override" value="multi">
<div class="form-group">
<label th:text=#{imageToPDF.selectText.3}></label>
<select class="form-control" id="conversionType" name="conversionType" disabled>
<option value="merge" th:text=#{imageToPDF.selectText.4}></option>
<option value="convert" th:text=#{imageToPDF.selectText.5} selected></option>
</select>
</div>
<br> <br>
<button type="submit" class="btn btn-primary" th:text="#{imageToPDF.submit}"></button>
<script>
$('#fileInput-input').on('change', function() {
var files = document.getElementById("fileInput-input").files;
var conversionType = document.getElementById("conversionType");
console.log("files.length=" + files.length)
if (files.length > 1) {
conversionType.disabled = false;
} else {
conversionType.disabled = true;
}
});
$('#conversionType').change(function() {
var selectedValue = $(this).val();
var override = document.getElementById("override");
console.log("selectedValue=" + selectedValue)
if (selectedValue === 'merge') {
override.value = "single";
} else if (selectedValue === 'convert') {
override.value = "multi";
}
});
</script>
</form>
</div>
</div>

View File

@@ -16,7 +16,7 @@
<h2 th:text="#{pdfToImage.header}"></h2>
<p th:text="#{processTimeWarning}"></p>
<form method="post" enctype="multipart/form-data" th:action="@{pdf-to-img}">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
<div class="form-group">
<label th:text="#{pdfToImage.selectText}"></label>
<select class="form-control" name="imageFormat">

View File

@@ -15,7 +15,7 @@
<h2 th:text="#{extractImages.header}"></h2>
<form id="multiPdfForm" th:action="@{extract-images}" method="post" enctype="multipart/form-data">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
<div class="form-group">
<label th:text="#{extractImages.selectText}"></label>
<select class="form-control" name="format">

View File

@@ -2,6 +2,7 @@
<!-- 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">
@@ -63,10 +64,10 @@ function toggleDarkMode() {
</script>
</head>
<th:block th:fragment="fileSelector(name, multiple)">
<th:block th:fragment="fileSelector(name, multiple)" th:with="accept=${accept} ?: '*/*'">
<div class="custom-file-chooser">
<div class="custom-file">
<input type="file" class="custom-file-input" th:name="${name}" th:id="${name}+'-input'" multiple>
<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="#{pdfPrompt}"></label>
</div>
<div class="selected-files"></div>
@@ -90,7 +91,9 @@ function toggleDarkMode() {
event.preventDefault(); // Prevent the default form handling behavior
/* Check if ${multiple} is false */
var multiple = [[${multiple}]] || false;
if (!multiple && files.length > 1) {
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 {
@@ -208,8 +211,9 @@ function toggleDarkMode() {
progressBar.attr('aria-valuenow', 0);
progressBar.attr('aria-valuemax', files.length);
// Check the flag in localStorage
const zipFiles = localStorage.getItem('zipParallelFiles') === 'true';
// 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;
@@ -355,6 +359,7 @@ function toggleDarkMode() {
fileNames.forEach(fileName => {
selectedFilesContainer.append("<div>" + fileName + "</div>");
});
console.log("fileNames.length=" + fileNames.length)
if (fileNames.length === 1) {
$(inputElement).siblings(".custom-file-label").addClass("selected").html(fileNames[0]);
} else if (fileNames.length > 1) {

View File

@@ -206,25 +206,24 @@ function compareVersions(version1, version2) {
</div>
<div class="modal-body">
<div class="d-flex justify-content-between align-items-center mb-3">
<p class="mb-0" th:text="#{settings.appVersion} + ' ' + ${@appVersion}"></p>
<p class="mb-0" th:utext="#{settings.appVersion} + ' ' + ${@appVersion}"></p>
<a href="https://github.com/Frooodle/Stirling-PDF/releases" target="_blank">
<button type="button" class="btn btn-sm btn-outline-primary" id="update-btn" th:text="#{settings.update}"></button>
<button type="button" class="btn btn-sm btn-outline-primary" id="update-btn" th:utext="#{settings.update}"></button>
</a>
</div>
<div class="form-group">
<label for="downloadOption" th:text="#{settings.downloadOption.title}"></label>
<label for="downloadOption" th:utext="#{settings.downloadOption.title}"></label>
<select class="form-control" id="downloadOption">
<option value="sameWindow" th:text="#{settings.downloadOption.1}"></option>
<option value="newWindow" th:text="#{settings.downloadOption.2}"></option>
<option value="downloadFile" th:text="#{settings.downloadOption.3}"></option>
<option value="sameWindow" th:utext="#{settings.downloadOption.1}"></option>
<option value="newWindow" th:utext="#{settings.downloadOption.2}"></option>
<option value="downloadFile" th:utext="#{settings.downloadOption.3}"></option>
</select>
</div>
<div class="form-group">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="zipParallelFiles">
<label class="custom-control-label" for="zipParallelFiles" th:text="#{settings.zip}"></label>
</div>
<label for="zipThreshold" th:utext="#{settings.zipThreshold}"></label>
<input type="range" class="custom-range" min="0" max="9" step="1" id="zipThreshold" value="4">
<span id="zipThresholdValue" class="ml-2"></span>
</div>
</div>
<div class="modal-footer">
@@ -253,17 +252,20 @@ function compareVersions(version1, version2) {
});
// Get the zipParallelFiles flag from local storage, or set it to false if it doesn't exist
var zipParallelFiles = localStorage.getItem('zipParallelFiles') === 'true';
// Get the zipThreshold value from local storage, or set it to 0 if it doesn't exist
var zipThreshold = parseInt(localStorage.getItem('zipThreshold'), 10) || 4;
// Set the checked state of the checkbox
document.getElementById('zipParallelFiles').checked = zipParallelFiles;
// Set the value of the slider and the display span
document.getElementById('zipThreshold').value = zipThreshold;
document.getElementById('zipThresholdValue').textContent = zipThreshold;
// Save the checked state of the checkbox to local storage when it changes
document.getElementById('zipParallelFiles').addEventListener('change', function () {
zipParallelFiles = this.checked;
localStorage.setItem('zipParallelFiles', zipParallelFiles);
// Save the selected value to local storage when the slider value changes
document.getElementById('zipThreshold').addEventListener('input', function () {
zipThreshold = this.value;
document.getElementById('zipThresholdValue').textContent = zipThreshold;
localStorage.setItem('zipThreshold', zipThreshold);
});
</script>

View File

@@ -40,7 +40,9 @@
<!-- Features -->
<div class="features-container container">
<div th:replace="~{fragments/card :: card(cardTitle=#{home.merge.title}, cardText=#{home.merge.desc}, cardLink='merge-pdfs')}"></div>
<div th:replace="~{fragments/card :: card(cardTitle=#{home.split.title}, cardText=#{home.split.desc}, cardLink='split-pdfs')}"></div>
<div th:replace="~{fragments/card :: card(cardTitle=#{home.rotate.title}, cardText=#{home.rotate.desc}, cardLink='rotate-pdf')}"></div>

View File

@@ -17,7 +17,7 @@
<div class="form-group">
<label th:text="#{multiPdfDropPrompt}"></label>
<div class="custom-file">
<input type="file" class="custom-file-input" id="fileInput" name="fileInput" multiple required>
<input type="file" class="custom-file-input" id="fileInput" name="fileInput" accept="application/pdf" multiple required>
<label class="custom-file-label" th:text="#{pdfPrompt}"></label>
</div>
</div>

View File

@@ -16,7 +16,7 @@
<h2 th:text="#{ocr.header}"></h2>
<form action="#" th:action="@{/ocr-pdf}" method="post" enctype="multipart/form-data" class="mb-3">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
<div class="form-group">
<label for="languages" class="form-label" th:text="#{ocr.selectText.1}"></label>
<div id="languages">

View File

@@ -15,7 +15,7 @@
<h2 th:text="#{pdfOrganiser.header}"></h2>
<form th:action="@{rearrange-pages}" method="post" enctype="multipart/form-data">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
<div class="form-group">
<label for="pageOrder" th:text="#{pageOrderPrompt}"></label>
<input type="text" class="form-control" id="fileInput" name="pageOrder" placeholder="(e.g. 1,3,2 or 4-8,2,10-12)" required>

View File

@@ -15,7 +15,7 @@
<h2 th:text="#{pageRemover.header}"></h2>
<form th:action="@{remove-pages}" method="post" enctype="multipart/form-data">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
<div class="form-group">
<label for="pagesToDelete" th:text="#{pageRemover.pagesToDelete}"></label>
<input type="text" class="form-control" id="fileInput" name="pagesToDelete" placeholder="(e.g. 1,2,6 or 1-10,15-30)" required>

View File

@@ -16,7 +16,7 @@
<h2 th:text="#{rotate.header}"></h2>
<form action="#" th:action="@{rotate-pdf}" th:object="${rotateForm}" method="post" enctype="multipart/form-data">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
<input type="hidden" id="angleInput" name="angle" value="0">
<div id="editSection" style="display: none">

View File

@@ -16,7 +16,7 @@
<form action="add-password" method="post" enctype="multipart/form-data">
<div class="form-group">
<label th:text="#{addPassword.selectText.1}"></label>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
</div>
<div class="form-group">
<label th:text="#{addPassword.selectText.2}"></label> <input type="password" class="form-control" id="password" name="password" required>

View File

@@ -16,7 +16,7 @@
<form method="post" enctype="multipart/form-data" action="add-watermark">
<div class="form-group">
<label th:text="#{watermark.selectText.1}"></label>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
</div>
<div class="form-group">
<label for="watermarkText" th:text="#{watermark.selectText.2}"></label>

View File

@@ -14,7 +14,7 @@
<h2 th:text="#{changeMetadata.header}"></h2>
<form method="post" id="form1" enctype="multipart/form-data" th:action="@{/update-metadata}">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
<p class="text-muted" th:text="#{changeMetadata.selectText.1}"></p>
<div class="form-group-inline form-check">

View File

@@ -17,7 +17,7 @@
<form action="add-password" method="post" enctype="multipart/form-data">
<div class="form-group">
<label th:text="#{permissions.selectText.1}"></label>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
</div>
<div class="form-group">
<label th:text="#{permissions.selectText.2}"></label>

View File

@@ -16,7 +16,7 @@
<form action="remove-password" method="post" enctype="multipart/form-data">
<div class="form-group">
<label th:text="#{removePassword.selectText.1}"></label>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
</div>
<div class="form-group">
<label th:text="#{removePassword.selectText.2}"></label>

View File

@@ -16,7 +16,7 @@
<form method="post" enctype="multipart/form-data" action="remove-watermark">
<div class="form-group">
<label th:text="#{remove-watermark.selectText.1}"></label>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
</div>
<div class="form-group">
<label for="watermarkText" th:text="#{remove-watermark.selectText.2}"></label>

View File

@@ -24,7 +24,7 @@
<p th:text="#{split.desc.8}"></p>
<form th:action="@{split-pages}" method="post" enctype="multipart/form-data">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false)}"></div>
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
<div class="form-group">
<label for="pages" th:text="#{split.splitPages}"></label>