From 45b3e0aa6aa53c54f31a4a5bc288fdfd532bf49a Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Fri, 2 Jun 2023 20:15:10 +0100 Subject: [PATCH] JS and css cleanup --- src/main/resources/static/css/fileSelect.css | 10 + src/main/resources/static/js/darkmode.js | 76 ++++ src/main/resources/static/js/downloader.js | 242 ++++++++++++ src/main/resources/static/js/errorBanner.js | 50 +++ src/main/resources/static/js/favourites.js | 37 ++ src/main/resources/static/js/fileInput.js | 45 +++ src/main/resources/static/js/githubVersion.js | 41 ++ .../resources/static/js/languageSelection.js | 46 +++ src/main/resources/static/js/merge.js | 63 ++++ src/main/resources/static/js/settings.js | 42 +++ .../resources/templates/fragments/common.html | 355 +----------------- .../fragments/errorBannerPerPage.html | 53 +-- .../resources/templates/fragments/navbar.html | 186 +-------- src/main/resources/templates/merge-pdfs.html | 67 +--- 14 files changed, 669 insertions(+), 644 deletions(-) create mode 100644 src/main/resources/static/css/fileSelect.css create mode 100644 src/main/resources/static/js/darkmode.js create mode 100644 src/main/resources/static/js/downloader.js create mode 100644 src/main/resources/static/js/errorBanner.js create mode 100644 src/main/resources/static/js/favourites.js create mode 100644 src/main/resources/static/js/fileInput.js create mode 100644 src/main/resources/static/js/githubVersion.js create mode 100644 src/main/resources/static/js/languageSelection.js create mode 100644 src/main/resources/static/js/merge.js create mode 100644 src/main/resources/static/js/settings.js diff --git a/src/main/resources/static/css/fileSelect.css b/src/main/resources/static/css/fileSelect.css new file mode 100644 index 00000000..2cd2c682 --- /dev/null +++ b/src/main/resources/static/css/fileSelect.css @@ -0,0 +1,10 @@ +.custom-file-label { + padding-right: 90px; +} + +.selected-files { + margin-top: 10px; + max-height: 150px; + overflow-y: auto; + white-space: pre-wrap; +} \ No newline at end of file diff --git a/src/main/resources/static/js/darkmode.js b/src/main/resources/static/js/darkmode.js new file mode 100644 index 00000000..c91bfa68 --- /dev/null +++ b/src/main/resources/static/js/darkmode.js @@ -0,0 +1,76 @@ +var toggleCount = 0; +var lastToggleTime = Date.now(); + +function toggleDarkMode() { + var currentTime = Date.now(); + if (currentTime - lastToggleTime < 1000) { + toggleCount++; + } else { + toggleCount = 1; + } + lastToggleTime = currentTime; + + var lightModeStyles = document.getElementById("light-mode-styles"); + var darkModeStyles = document.getElementById("dark-mode-styles"); + var rainbowModeStyles = document.getElementById("rainbow-mode-styles"); + var darkModeIcon = document.getElementById("dark-mode-icon"); + + if (toggleCount >= 18) { + localStorage.setItem("dark-mode", "rainbow"); + lightModeStyles.disabled = true; + darkModeStyles.disabled = true; + rainbowModeStyles.disabled = false; + darkModeIcon.src = "rainbow.svg"; + } else if (localStorage.getItem("dark-mode") == "on") { + localStorage.setItem("dark-mode", "off"); + lightModeStyles.disabled = false; + darkModeStyles.disabled = true; + rainbowModeStyles.disabled = true; + darkModeIcon.src = "sun.svg"; + } else { + localStorage.setItem("dark-mode", "on"); + lightModeStyles.disabled = true; + darkModeStyles.disabled = false; + rainbowModeStyles.disabled = true; + darkModeIcon.src = "moon.svg"; + } +} + +document.addEventListener("DOMContentLoaded", function() { + var lightModeStyles = document.getElementById("light-mode-styles"); + var darkModeStyles = document.getElementById("dark-mode-styles"); + var rainbowModeStyles = document.getElementById("rainbow-mode-styles"); + var darkModeIcon = document.getElementById("dark-mode-icon"); + + if (localStorage.getItem("dark-mode") == "on") { + lightModeStyles.disabled = true; + darkModeStyles.disabled = false; + rainbowModeStyles.disabled = true; + darkModeIcon.src = "moon.svg"; + } else if (localStorage.getItem("dark-mode") == "off") { + lightModeStyles.disabled = false; + darkModeStyles.disabled = true; + rainbowModeStyles.disabled = true; + darkModeIcon.src = "sun.svg"; + } else if (localStorage.getItem("dark-mode") == "rainbow") { + lightModeStyles.disabled = true; + darkModeStyles.disabled = true; + rainbowModeStyles.disabled = false; + darkModeIcon.src = "rainbow.svg"; + } else { + if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) { + darkModeStyles.disabled = false; + rainbowModeStyles.disabled = true; + darkModeIcon.src = "moon.svg"; + } else { + darkModeStyles.disabled = true; + rainbowModeStyles.disabled = true; + darkModeIcon.src = "sun.svg"; + } + } + + document.getElementById("dark-mode-toggle").addEventListener("click", function(event) { + event.preventDefault(); + toggleDarkMode(); + }); +}); \ No newline at end of file diff --git a/src/main/resources/static/js/downloader.js b/src/main/resources/static/js/downloader.js new file mode 100644 index 00000000..5dc1912f --- /dev/null +++ b/src/main/resources/static/js/downloader.js @@ -0,0 +1,242 @@ +function showErrorBanner(message, stackTrace) { + const errorContainer = document.getElementById("errorContainer"); + errorContainer.style.display = "block"; // Display the banner + document.querySelector("#errorContainer .alert-heading").textContent = "Error"; + document.querySelector("#errorContainer p").textContent = message; + document.querySelector("#traceContent").textContent = stackTrace; +} + +$(document).ready(function() { + $('form').submit(async function(event) { + event.preventDefault(); + + const url = this.action; + const files = $('#fileInput-input')[0].files; + const formData = new FormData(this); + const override = $('#override').val() || ''; + + $('#submitBtn').text('Processing...'); + + try { + if (override === 'multi' || files.length > 1 && override !== 'single') { + // 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); + + await submitMultiPdfForm(url, files); + } else { + const downloadDetails = await handleSingleDownload(url, formData); + const downloadOption = localStorage.getItem('downloadOption'); + + // Handle the download action according to the selected option + //handleDownloadAction(downloadOption, downloadDetails.blob, downloadDetails.filename); + + // Update the progress bar + //updateProgressBar(progressBar, 1); + + } + + $('#submitBtn').text('Submit'); + } catch (error) { + handleDownloadError(error); + $('#submitBtn').text('Submit'); + console.error(error); + } + }); +}); + +function handleDownloadAction(downloadOption, blob, filename) { + const url = URL.createObjectURL(blob); + + switch (downloadOption) { + case 'sameWindow': + // Open the file in the same window + window.location.href = url; + break; + case 'newWindow': + // Open the file in a new window + window.open(url, '_blank'); + break; + default: + // Download the file + const link = document.createElement('a'); + link.href = url; + link.download = filename; + link.click(); + break; + } +} + +async function handleSingleDownload(url, formData) { + try { + const response = await fetch(url, { method: 'POST', body: formData }); + const contentType = response.headers.get('content-type'); + + if (!response.ok) { + if (contentType && contentType.includes('application/json')) { + return handleJsonResponse(response); + console.error('Throwing error banner, response was not okay'); + } + throw new Error(`HTTP error! status: ${response.status}`); + } + + const contentDisposition = response.headers.get('Content-Disposition'); + let filename = getFilenameFromContentDisposition(contentDisposition); + + const blob = await response.blob(); + + if (contentType.includes('application/pdf') || contentType.includes('image/')) { + return handleResponse(blob, filename, true); + } else { + return handleResponse(blob, filename); + } + } catch (error) { + console.error('Error in handleSingleDownload:', error); + throw error; // Re-throw the error if you want it to be handled higher up. + } +} + +function getFilenameFromContentDisposition(contentDisposition) { + let filename; + + if (contentDisposition && contentDisposition.indexOf('attachment') !== -1) { + filename = decodeURIComponent(contentDisposition.split('filename=')[1].replace(/"/g, '')).trim(); + } else { + // If the Content-Disposition header is not present or does not contain the filename, use a default filename + filename = 'download'; + } + + return filename; +} + + + +async function handleJsonResponse(response) { + const json = await response.json(); + const errorMessage = JSON.stringify(json, null, 2); + if (errorMessage.toLowerCase().includes('the password is incorrect') || errorMessage.toLowerCase().includes('Password is not provided')) { + alert('[[#{error.pdfPassword}]]'); + } else { + showErrorBanner(json.error + ':' + json.message, json.trace); + } +} + + +async function handleResponse(blob, filename, considerViewOptions = false) { + if (!blob) return; + const downloadOption = localStorage.getItem('downloadOption'); + if (considerViewOptions) { + if (downloadOption === 'sameWindow') { + const url = URL.createObjectURL(blob); + window.location.href = url; + return; + } else if (downloadOption === 'newWindow') { + const url = URL.createObjectURL(blob); + window.open(url, '_blank'); + return; + } + } + downloadFile(blob, filename); + return { filename, blob }; +} + +function handleDownloadError(error) { + const errorMessage = error.message; + showErrorBanner(errorMessage); +} + +let urls = []; // An array to hold all the URLs + +function downloadFile(blob, filename) { + if (!(blob instanceof Blob)) { + console.error('Invalid blob passed to downloadFile function'); + return; + } + + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = filename; + a.click(); + urls.push(url); // Store the URL so it doesn't get garbage collected too soon + + return { filename, blob }; +} + + + +async function submitMultiPdfForm(url, files) { + const zipThreshold = parseInt(localStorage.getItem('zipThreshold'), 10) || 4; + const zipFiles = files.length > zipThreshold; + let jszip = null; + //let progressBar = $('#progressBar'); + //progressBar.css('width', '0%'); + //progressBar.attr('aria-valuenow', 0); + //progressBar.attr('aria-valuemax', Array.from(files).length); + if (zipFiles) { + jszip = new JSZip(); + } + + // Get existing form data + let formData = new FormData($('form')[0]); + formData.delete('fileInput'); + + const CONCURRENCY_LIMIT = 8; + const chunks = []; + for (let i = 0; i < Array.from(files).length; i += CONCURRENCY_LIMIT) { + chunks.push(Array.from(files).slice(i, i + CONCURRENCY_LIMIT)); + } + + for (const chunk of chunks) { + const promises = chunk.map(async file => { + let fileFormData = new FormData(); + fileFormData.append('fileInput', file); + + // Add other form data + for (let pair of formData.entries()) { + fileFormData.append(pair[0], pair[1]); + } + + try { + const downloadDetails = await handleSingleDownload(url, fileFormData); + console.log(downloadDetails); + if (zipFiles) { + jszip.file(downloadDetails.filename, downloadDetails.blob); + } else { + downloadFile(downloadDetails.blob, downloadDetails.filename); + } + //updateProgressBar(progressBar, Array.from(files).length); + } catch (error) { + handleDownloadError(error); + console.error(error); + } + }); + await Promise.all(promises); + } + + if (zipFiles) { + try { + const content = await jszip.generateAsync({ type: "blob" }); + downloadFile(content, "files.zip"); + } 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); +} +window.addEventListener('unload', () => { + for (const url of urls) { + URL.revokeObjectURL(url); + } +}); diff --git a/src/main/resources/static/js/errorBanner.js b/src/main/resources/static/js/errorBanner.js new file mode 100644 index 00000000..9d151407 --- /dev/null +++ b/src/main/resources/static/js/errorBanner.js @@ -0,0 +1,50 @@ +var traceVisible = false; + +function toggletrace() { + var traceDiv = document.getElementById("trace"); + if (!traceVisible) { + traceDiv.style.maxHeight = "500px"; + traceVisible = true; + } else { + traceDiv.style.maxHeight = "0px"; + traceVisible = false; + } + adjustContainerHeight(); +} + +function copytrace() { + var flip = false + if (!traceVisible) { + toggletrace() + flip = true + } + var traceContent = document.getElementById("traceContent"); + var range = document.createRange(); + range.selectNode(traceContent); + window.getSelection().removeAllRanges(); + window.getSelection().addRange(range); + document.execCommand("copy"); + window.getSelection().removeAllRanges(); + if (flip) { + toggletrace() + } +} + +function dismissError() { + var errorContainer = document.getElementById("errorContainer"); + errorContainer.style.display = "none"; + errorContainer.style.height = "0"; +} + +function adjustContainerHeight() { + var errorContainer = document.getElementById("errorContainer"); + var traceDiv = document.getElementById("trace"); + if (traceVisible) { + errorContainer.style.height = errorContainer.scrollHeight - traceDiv.scrollHeight + traceDiv.offsetHeight + "px"; + } else { + errorContainer.style.height = "auto"; + } +} +function showHelp() { + $('#helpModal').modal('show'); +} \ No newline at end of file diff --git a/src/main/resources/static/js/favourites.js b/src/main/resources/static/js/favourites.js new file mode 100644 index 00000000..7372ad30 --- /dev/null +++ b/src/main/resources/static/js/favourites.js @@ -0,0 +1,37 @@ +function updateFavoritesDropdown() { + var dropdown = document.querySelector('#favoritesDropdown'); + dropdown.innerHTML = ''; // Clear the current favorites + + + + var hasFavorites = false; + + for (var i = 0; i < localStorage.length; i++) { + var key = localStorage.key(i); + if (localStorage.getItem(key) === 'favorite') { + // Find the corresponding navbar entry + var navbarEntry = document.querySelector(`a[href='${key}']`); + if (navbarEntry) { + // Create a new dropdown entry + var dropdownItem = document.createElement('a'); + dropdownItem.className = 'dropdown-item'; + dropdownItem.href = navbarEntry.href; + dropdownItem.innerHTML = navbarEntry.innerHTML; + dropdown.appendChild(dropdownItem); + hasFavorites = true; + } + } + } + + // Show or hide the default item based on whether there are any favorites + if (!hasFavorites) { + var defaultItem = document.createElement('a'); + defaultItem.className = 'dropdown-item'; + defaultItem.textContent = noFavourites; + dropdown.appendChild(defaultItem); + } +} +document.addEventListener('DOMContentLoaded', function() { + + updateFavoritesDropdown(); +}); \ No newline at end of file diff --git a/src/main/resources/static/js/fileInput.js b/src/main/resources/static/js/fileInput.js new file mode 100644 index 00000000..16111d24 --- /dev/null +++ b/src/main/resources/static/js/fileInput.js @@ -0,0 +1,45 @@ +document.addEventListener('DOMContentLoaded', function() { + const fileInput = document.getElementById(elementID); + + // 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) + } +}); + +$(elementID).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("