diff --git a/src/main/resources/static/css/sign.css b/src/main/resources/static/css/sign.css index 6de4efcd6..39ea4c9be 100644 --- a/src/main/resources/static/css/sign.css +++ b/src/main/resources/static/css/sign.css @@ -32,7 +32,7 @@ select#font-select option { margin-left: -2.2rem; } -.draggable-buttons-box>button { +.draggable-buttons-box > button { z-index: 4; background-color: rgba(13, 110, 253, 0.1); flex: 1 1 auto; @@ -40,7 +40,6 @@ select#font-select option { max-width: 4rem; } - .rotation-handle { width: 20px; height: 20px; @@ -163,3 +162,76 @@ select#font-select option { .small-file-container-saved:hover .drag-icon { display: flex; } + +/* The container must be positioned relative: */ +.custom-select { + position: relative; + font-family: inherit; +} + +.custom-select select { + display: none; /*hide original SELECT element: */ +} + +.select-selected { + background-color: inherit; + line-height: 30px; + font-size: 30px; + border-radius: 3rem !important; +} + +/* Style the arrow inside the select element: */ +.select-selected:after { + position: absolute; + content: ""; + top: 50%; + right: 10px; + translate: 0 -50%; + width: 0; + height: 0; + border: 6px solid transparent; + border-color: #fff transparent transparent transparent; +} + +/* Point the arrow upwards when the select box is open (active): */ +.select-selected.select-arrow-active:after { + border-color: transparent transparent #fff transparent; + translate: 0 -75%; +} + +/* style the items (options), including the selected item: */ +.select-items div, +.select-selected { + color: inherit; + padding: 8px 16px; + cursor: pointer; +} + +.select-items div { + border: 1px solid transparent; + border-color: transparent transparent transparent transparent; + + line-height: 30px; + font-size: 30px; +} + +/* Style items (options): */ +.select-items { + position: absolute; + background-color: inherit; + top: 100%; + left: 0; + right: 0; + z-index: 101; + border: inherit; +} + +/* Hide the items when the select box is closed: */ +.select-hide { + display: none; +} + +.select-items div:hover, +.same-as-selected { + background-color: rgba(54, 54, 54, 0.1); +} diff --git a/src/main/resources/static/js/pages/sign.js b/src/main/resources/static/js/pages/sign.js index 736ca1cd5..36902aa7b 100644 --- a/src/main/resources/static/js/pages/sign.js +++ b/src/main/resources/static/js/pages/sign.js @@ -8,21 +8,21 @@ window.goToFirstOrLastPage = goToFirstOrLastPage; let currentPreviewSrc = null; function toggleSignatureView() { - const gridView = document.getElementById('gridView'); - const listView = document.getElementById('listView'); - const gridText = document.querySelector('.grid-view-text'); - const listText = document.querySelector('.list-view-text'); + const gridView = document.getElementById("gridView"); + const listView = document.getElementById("listView"); + const gridText = document.querySelector(".grid-view-text"); + const listText = document.querySelector(".list-view-text"); - if (gridView.style.display !== 'none') { - gridView.style.display = 'none'; - listView.style.display = 'block'; - gridText.style.display = 'none'; - listText.style.display = 'inline'; + if (gridView.style.display !== "none") { + gridView.style.display = "none"; + listView.style.display = "block"; + gridText.style.display = "none"; + listText.style.display = "inline"; } else { - gridView.style.display = 'block'; - listView.style.display = 'none'; - gridText.style.display = 'inline'; - listText.style.display = 'none'; + gridView.style.display = "block"; + listView.style.display = "none"; + gridText.style.display = "inline"; + listText.style.display = "none"; } } @@ -30,63 +30,204 @@ function previewSignature(element) { const src = element.dataset.src; currentPreviewSrc = src; - const filename = element.querySelector('.signature-list-name').textContent; + const filename = element.querySelector(".signature-list-name").textContent; - const previewImage = document.getElementById('previewImage'); - const previewFileName = document.getElementById('previewFileName'); + const previewImage = document.getElementById("previewImage"); + const previewFileName = document.getElementById("previewFileName"); previewImage.src = src; previewFileName.textContent = filename; - const modal = new bootstrap.Modal(document.getElementById('signaturePreview')); + const modal = new bootstrap.Modal( + document.getElementById("signaturePreview") + ); modal.show(); } function addSignatureFromPreview() { if (currentPreviewSrc) { DraggableUtils.createDraggableCanvasFromUrl(currentPreviewSrc); - bootstrap.Modal.getInstance(document.getElementById('signaturePreview')).hide(); + bootstrap.Modal.getInstance( + document.getElementById("signaturePreview") + ).hide(); } } -let originalFileName = ''; -document.querySelector('input[name=pdf-upload]').addEventListener('change', async (event) => { - const fileInput = event.target; - fileInput.addEventListener('file-input-change', async (e) => { - const {allFiles} = e.detail; - if (allFiles && allFiles.length > 0) { - const file = allFiles[0]; - originalFileName = file.name.replace(/\.[^/.]+$/, ''); - const pdfData = await file.arrayBuffer(); - pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs'; - const pdfDoc = await pdfjsLib.getDocument({data: pdfData}).promise; - await DraggableUtils.renderPage(pdfDoc, 0); +let originalFileName = ""; +document + .querySelector("input[name=pdf-upload]") + .addEventListener("change", async (event) => { + const fileInput = event.target; + fileInput.addEventListener("file-input-change", async (e) => { + const { allFiles } = e.detail; + if (allFiles && allFiles.length > 0) { + const file = allFiles[0]; + originalFileName = file.name.replace(/\.[^/.]+$/, ""); + const pdfData = await file.arrayBuffer(); + pdfjsLib.GlobalWorkerOptions.workerSrc = + "./pdfjs-legacy/pdf.worker.mjs"; + const pdfDoc = await pdfjsLib.getDocument({ data: pdfData }).promise; + await DraggableUtils.renderPage(pdfDoc, 0); - document.querySelectorAll('.show-on-file-selected').forEach((el) => { - el.style.cssText = ''; - }); - } - }); -}); - -document.addEventListener('DOMContentLoaded', () => { - document.querySelectorAll('.show-on-file-selected').forEach((el) => { - el.style.cssText = 'display:none !important'; - }); - document.querySelectorAll('.small-file-container-saved img ').forEach((img) => { - img.addEventListener('dragstart', (e) => { - e.dataTransfer.setData('fileUrl', img.src); + document.querySelectorAll(".show-on-file-selected").forEach((el) => { + el.style.cssText = ""; + }); + } }); }); - document.addEventListener('keydown', (e) => { - if (e.key === 'Delete') { + +document.addEventListener("DOMContentLoaded", () => { + document.querySelectorAll(".show-on-file-selected").forEach((el) => { + el.style.cssText = "display:none !important"; + }); + document + .querySelectorAll(".small-file-container-saved img ") + .forEach((img) => { + img.addEventListener("dragstart", (e) => { + e.dataTransfer.setData("fileUrl", img.src); + }); + }); + document.addEventListener("keydown", (e) => { + if (e.key === "Delete") { DraggableUtils.deleteDraggableCanvas(DraggableUtils.getLastInteracted()); } }); + + addCustomSelect(); }); -const imageUpload = document.querySelector('input[name=image-upload]'); -imageUpload.addEventListener('change', (e) => { +function addCustomSelect() { + let customSelectElementContainer = + document.getElementById("signFontSelection"); + let originalSelectElement = + customSelectElementContainer.querySelector("select"); + + let optionsCount = originalSelectElement.length; + + let selectedItem = createAndStyleSelectedItem(); + + customSelectElementContainer.appendChild(selectedItem); + + let customSelectionsOptionsContainer = createCustomOptionsContainer(); + createAndAddCustomOptions(); + customSelectElementContainer.appendChild(customSelectionsOptionsContainer); + + selectedItem.addEventListener("click", function (e) { + /* When the select box is clicked, close any other select boxes, + and open/close the current select box: */ + e.stopPropagation(); + closeAllSelect(this); + this.nextSibling.classList.toggle("select-hide"); + this.classList.toggle("select-arrow-active"); + }); + + function createAndAddCustomOptions() { + for (let j = 0; j < optionsCount; j++) { + /* For each option in the original select element, + create a new DIV that will act as an option item: */ + let customOptionItem = createAndStyleCustomOption(j); + + customOptionItem.addEventListener("click", onCustomOptionClick); + customSelectionsOptionsContainer.appendChild(customOptionItem); + } + } + + function createCustomOptionsContainer() { + let customSelectionsOptionsContainer = document.createElement("DIV"); + customSelectionsOptionsContainer.setAttribute( + "class", + "select-items select-hide" + ); + return customSelectionsOptionsContainer; + } + + function createAndStyleSelectedItem() { + let selectedItem = document.createElement("DIV"); + selectedItem.setAttribute("class", "select-selected"); + selectedItem.innerHTML = + originalSelectElement.options[ + originalSelectElement.selectedIndex + ].innerHTML; + + selectedItem.style.fontFamily = window.getComputedStyle( + originalSelectElement.options[originalSelectElement.selectedIndex] + ).fontFamily; + return selectedItem; + } + + function onCustomOptionClick(e) { + /* When an item is clicked, update the original select box, + and the selected item: */ + let selectElement = + this.parentNode.parentNode.getElementsByTagName("select")[0]; + let optionsCount = selectElement.length; + let currentlySelectedCustomOption = this.parentNode.previousSibling; + for (let i = 0; i < optionsCount; i++) { + if (selectElement.options[i].innerHTML == this.innerHTML) { + selectElement.selectedIndex = i; + currentlySelectedCustomOption.innerHTML = this.innerHTML; + currentlySelectedCustomOption.style.fontFamily = this.style.fontFamily; + + let previouslySelectedOption = + this.parentNode.getElementsByClassName("same-as-selected"); + + if (previouslySelectedOption && previouslySelectedOption.length > 0) + previouslySelectedOption[0].classList.remove("same-as-selected"); + + this.classList.add("same-as-selected"); + break; + } + } + currentlySelectedCustomOption.click(); + } + + function createAndStyleCustomOption(j) { + let customOptionItem = document.createElement("DIV"); + customOptionItem.innerHTML = originalSelectElement.options[j].innerHTML; + customOptionItem.classList.add(originalSelectElement.options[j].className); + customOptionItem.style.fontFamily = window.getComputedStyle( + originalSelectElement.options[j] + ).fontFamily; + + if (j == originalSelectElement.selectedIndex) + customOptionItem.classList.add("same-as-selected"); + return customOptionItem; + } + + function closeAllSelect(element) { + /* A function that will close all select boxes in the document, + except the current select box: */ + let allSelectedOptions = document.getElementsByClassName("select-selected"); + let allSelectedOptionsCount = allSelectedOptions.length; + let indicesOfContainersToHide = []; + for (let i = 0; i < allSelectedOptionsCount; i++) { + if (element == allSelectedOptions[i]) { + indicesOfContainersToHide.push(i); + } else { + allSelectedOptions[i].classList.remove("select-arrow-active"); + } + } + + hideOptionsContainers(indicesOfContainersToHide); + } + + /* If the user clicks anywhere outside the select box, + then close all select boxes: */ + document.addEventListener("click", closeAllSelect); + + function hideOptionsContainers(containersIndices) { + let allOptionsContainers = document.getElementsByClassName("select-items"); + let allSelectionListsContainerCount = allOptionsContainers.length; + for (let i = 0; i < allSelectionListsContainerCount; i++) { + if (containersIndices.indexOf(i)) { + allOptionsContainers[i].classList.add("select-hide"); + } + } + } +} + +const imageUpload = document.querySelector("input[name=image-upload]"); +imageUpload.addEventListener("change", (e) => { if (!e.target.files) return; for (const imageFile of e.target.files) { var reader = new FileReader(); @@ -97,11 +238,11 @@ imageUpload.addEventListener('change', (e) => { } }); -const signaturePadCanvas = document.getElementById('drawing-pad-canvas'); +const signaturePadCanvas = document.getElementById("drawing-pad-canvas"); const signaturePad = new SignaturePad(signaturePadCanvas, { minWidth: 1, maxWidth: 2, - penColor: 'black', + penColor: "black", }); function addDraggableFromPad() { @@ -113,7 +254,7 @@ function addDraggableFromPad() { } function getCroppedCanvasDataUrl(canvas) { - let originalCtx = canvas.getContext('2d'); + let originalCtx = canvas.getContext("2d"); let originalWidth = canvas.width; let originalHeight = canvas.height; let imageData = originalCtx.getImageData(0, 0, originalWidth, originalHeight); @@ -129,7 +270,8 @@ function getCroppedCanvasDataUrl(canvas) { for (y = 0; y < originalHeight; y++) { for (x = 0; x < originalWidth; x++) { currentPixelColorValueIndex = (y * originalWidth + x) * 4; - let currentPixelAlphaValue = imageData.data[currentPixelColorValueIndex + 3]; + let currentPixelAlphaValue = + imageData.data[currentPixelColorValueIndex + 3]; if (currentPixelAlphaValue > 0) { if (minX > x) minX = x; if (maxX < x) maxX = x; @@ -142,10 +284,15 @@ function getCroppedCanvasDataUrl(canvas) { let croppedWidth = maxX - minX; let croppedHeight = maxY - minY; if (croppedWidth < 0 || croppedHeight < 0) return null; - let cuttedImageData = originalCtx.getImageData(minX, minY, croppedWidth, croppedHeight); + let cuttedImageData = originalCtx.getImageData( + minX, + minY, + croppedWidth, + croppedHeight + ); - let croppedCanvas = document.createElement('canvas'), - croppedCtx = croppedCanvas.getContext('2d'); + let croppedCanvas = document.createElement("canvas"), + croppedCtx = croppedCanvas.getContext("2d"); croppedCanvas.width = croppedWidth; croppedCanvas.height = croppedHeight; @@ -158,9 +305,13 @@ function resizeCanvas() { var ratio = Math.max(window.devicePixelRatio || 1, 1); var additionalFactor = 10; - signaturePadCanvas.width = signaturePadCanvas.offsetWidth * ratio * additionalFactor; - signaturePadCanvas.height = signaturePadCanvas.offsetHeight * ratio * additionalFactor; - signaturePadCanvas.getContext('2d').scale(ratio * additionalFactor, ratio * additionalFactor); + signaturePadCanvas.width = + signaturePadCanvas.offsetWidth * ratio * additionalFactor; + signaturePadCanvas.height = + signaturePadCanvas.offsetHeight * ratio * additionalFactor; + signaturePadCanvas + .getContext("2d") + .scale(ratio * additionalFactor, ratio * additionalFactor); signaturePad.clear(); } @@ -174,12 +325,12 @@ new IntersectionObserver((entries, observer) => { new ResizeObserver(resizeCanvas).observe(signaturePadCanvas); function addDraggableFromText() { - const sigText = document.getElementById('sigText').value; - const font = document.querySelector('select[name=font]').value; + const sigText = document.getElementById("sigText").value; + const font = document.querySelector("select[name=font]").value; const fontSize = 100; - const canvas = document.createElement('canvas'); - const ctx = canvas.getContext('2d'); + const canvas = document.createElement("canvas"); + const ctx = canvas.getContext("2d"); ctx.font = `${fontSize}px ${font}`; const textWidth = ctx.measureText(sigText).width; const textHeight = fontSize; @@ -190,7 +341,7 @@ function addDraggableFromText() { canvas.height = paragraphs.length * textHeight * 1.35; // for tails ctx.font = `${fontSize}px ${font}`; - ctx.textBaseline = 'top'; + ctx.textBaseline = "top"; let y = 0; @@ -212,8 +363,8 @@ async function goToFirstOrLastPage(page) { } } -document.getElementById('download-pdf').addEventListener('click', async () => { - const downloadButton = document.getElementById('download-pdf'); +document.getElementById("download-pdf").addEventListener("click", async () => { + const downloadButton = document.getElementById("download-pdf"); const originalContent = downloadButton.innerHTML; downloadButton.disabled = true; @@ -224,13 +375,13 @@ document.getElementById('download-pdf').addEventListener('click', async () => { try { const modifiedPdf = await DraggableUtils.getOverlayedPdfDocument(); const modifiedPdfBytes = await modifiedPdf.save(); - const blob = new Blob([modifiedPdfBytes], {type: 'application/pdf'}); - const link = document.createElement('a'); + const blob = new Blob([modifiedPdfBytes], { type: "application/pdf" }); + const link = document.createElement("a"); link.href = URL.createObjectURL(blob); - link.download = originalFileName + '_signed.pdf'; + link.download = originalFileName + "_signed.pdf"; link.click(); } catch (error) { - console.error('Error downloading PDF:', error); + console.error("Error downloading PDF:", error); } finally { downloadButton.disabled = false; downloadButton.innerHTML = originalContent; diff --git a/src/main/resources/templates/sign.html b/src/main/resources/templates/sign.html index 5c7ecf7ea..46a1588fb 100644 --- a/src/main/resources/templates/sign.html +++ b/src/main/resources/templates/sign.html @@ -15,7 +15,8 @@ #font-select option[value="[[${font.name}]]"] { font-family: "[[${font.name}]]", - cursive; + cursive + !important; } @@ -133,10 +134,12 @@ - +
+ +