From e8a91d263180770a4289a70a3534bee158d4034b Mon Sep 17 00:00:00 2001 From: jordy <jordyvandertang@hotmail.com> Date: Fri, 28 Apr 2023 21:20:56 +0200 Subject: [PATCH] refactor JS Rather than having all the JS all in one big file, separate the code into modules. --- .../resources/static/js/multitool/dragDrop.js | 88 ++++ .../static/js/multitool/horizontalScroll.js | 37 ++ .../static/js/multitool/imageHighlighter.js | 27 + .../static/js/multitool/pdfActions.js | 156 ++++++ .../static/js/multitool/pdfContainer.js | 217 ++++++++ src/main/resources/templates/multi-tool.html | 471 +----------------- 6 files changed, 541 insertions(+), 455 deletions(-) create mode 100644 src/main/resources/static/js/multitool/dragDrop.js create mode 100644 src/main/resources/static/js/multitool/horizontalScroll.js create mode 100644 src/main/resources/static/js/multitool/imageHighlighter.js create mode 100644 src/main/resources/static/js/multitool/pdfActions.js create mode 100644 src/main/resources/static/js/multitool/pdfContainer.js diff --git a/src/main/resources/static/js/multitool/dragDrop.js b/src/main/resources/static/js/multitool/dragDrop.js new file mode 100644 index 00000000..7a1066fd --- /dev/null +++ b/src/main/resources/static/js/multitool/dragDrop.js @@ -0,0 +1,88 @@ + + +class DragDropManager { + dragContainer; + movePageTo; + pageDragging; + draggelEl; + draggedImageEl; + hoveredEl; + + constructor(id, movePageTo) { + this.dragContainer = document.getElementById(id); + this.movePageTo = movePageTo; + this.pageDragging = false; + this.hoveredEl = undefined; + this.draggelEl = undefined + this.draggedImageEl = undefined; + + this.startDraggingPage = this.startDraggingPage.bind(this); + this.onDragEl = this.onDragEl.bind(this); + this.stopDraggingPage = this.stopDraggingPage.bind(this); + this.attachDragDropCallbacks = this.attachDragDropCallbacks.bind(this); + + } + + startDraggingPage(div, imageSrc) { + this.pageDragging = true; + this.draggedEl = div; + div.classList.add('dragging'); + const imgEl = document.createElement('img'); + imgEl.classList.add('dragged-img'); + imgEl.src = imageSrc; + this.draggedImageEl = imgEl; + this.draggedImageEl.style.left = screenX; + this.draggedImageEl.style.right = screenY; + this.dragContainer.appendChild(imgEl); + window.addEventListener('mouseup', (e) => { + this.stopDraggingPage(); + }) + window.addEventListener('mousemove', this.onDragEl) + } + + onDragEl(mouseEvent) { + const { clientX, clientY } = mouseEvent; + if(this.draggedImageEl) { + this.draggedImageEl.style.left = `${clientX}px`; + this.draggedImageEl.style.top = `${clientY}px`; + } + } + + + stopDraggingPage() { + window.removeEventListener('mousemove', this.onDragEl); + this.draggedImageEl = undefined; + this.pageDragging = false; + this.draggedEl.classList.remove('dragging'); + this.hoveredEl.classList.remove('draghover'); + this.dragContainer.childNodes.forEach((dragChild) => { + this.dragContainer.removeChild(dragChild); + }) + this.movePageTo(this.draggedEl, this.hoveredEl); + } + + + attachDragDropCallbacks(div, imageSrc) { + const onDragStart = () => { + this.startDraggingPage(div, imageSrc); + } + + const onMouseEnter = () => { + if (this.pageDragging) { + this.hoveredEl = div; + div.classList.add('draghover'); + } + } + + const onMouseLeave = () => { + this.hoveredEl = undefined + div.classList.remove('draghover'); + } + + div.addEventListener('dragstart', onDragStart); + div.addEventListener('mouseenter', onMouseEnter); + div.addEventListener('mouseleave', onMouseLeave); + } +} + +export default DragDropManager; \ No newline at end of file diff --git a/src/main/resources/static/js/multitool/horizontalScroll.js b/src/main/resources/static/js/multitool/horizontalScroll.js new file mode 100644 index 00000000..c1c5ca87 --- /dev/null +++ b/src/main/resources/static/js/multitool/horizontalScroll.js @@ -0,0 +1,37 @@ + + +const scrollDivHorizontally = (id) => { + var scrollDelta = 0; // variable to store the accumulated scroll delta + var isScrolling = false; // variable to track if scroll is already in progress + const pagesContainerWrapper = document.getElementById(id); + function scrollLoop() { + // Scroll the div horizontally by a fraction of the accumulated scroll delta + pagesContainerWrapper.scrollLeft += scrollDelta * 0.1; + + // Reduce the accumulated scroll delta by a fraction + scrollDelta *= 0.9; + + // If scroll delta is still significant, continue the scroll loop + if (Math.abs(scrollDelta) > 0.1) { + requestAnimationFrame(scrollLoop); + } else { + isScrolling = false; // Reset scroll in progress flag + } + } + + const divToScrollHorizontally = document.getElementById(id) + divToScrollHorizontally.addEventListener("wheel", function(e) { + e.preventDefault(); // prevent default mousewheel behavior + + // Accumulate the horizontal scroll delta + scrollDelta -= e.deltaX || e.wheelDeltaX || -e.deltaY || -e.wheelDeltaY; + + // If scroll is not already in progress, start the scroll loop + if (!isScrolling) { + isScrolling = true; + requestAnimationFrame(scrollLoop); + } + }); +} + +export default scrollDivHorizontally; diff --git a/src/main/resources/static/js/multitool/imageHighlighter.js b/src/main/resources/static/js/multitool/imageHighlighter.js new file mode 100644 index 00000000..15f54c98 --- /dev/null +++ b/src/main/resources/static/js/multitool/imageHighlighter.js @@ -0,0 +1,27 @@ +const getImageHighlighterCallback = (id) => { + const imageHighlighter = document.getElementById(id); + imageHighlighter.onclick = () => { + imageHighlighter.childNodes.forEach((child) => { + child.classList.add('remove'); + setTimeout(() => { + imageHighlighter.removeChild(child); + }, 100) + }) + } + + const imageHighlightCallback = (highlightEvent) => { + var bigImg = document.createElement('img'); + bigImg.onclick = (imageClickEvent) => { + // This prevents the highlighter's onClick from closing the image when clicking on the image + // instead of next to it. + imageClickEvent.preventDefault(); + imageClickEvent.stopPropagation(); + }; + bigImg.src = highlightEvent.target.src; + imageHighlighter.appendChild(bigImg); + }; + + return imageHighlightCallback +} + +export default getImageHighlighterCallback; \ No newline at end of file diff --git a/src/main/resources/static/js/multitool/pdfActions.js b/src/main/resources/static/js/multitool/pdfActions.js new file mode 100644 index 00000000..14bfc349 --- /dev/null +++ b/src/main/resources/static/js/multitool/pdfActions.js @@ -0,0 +1,156 @@ +class PdfActionsManager { + callbacks; + pageDirection; + constructor(id, { movePageTo, addPdfs, rotateElement }) { + this.pageDirection = document.documentElement.getAttribute("lang-direction"); + const moveUpButtonCallback = e => { + var imgContainer = e.target; + while (!imgContainer.classList.contains(id)) { + imgContainer = imgContainer.parentNode; + } + + const sibling = imgContainer.previousSibling; + if (sibling) { + movePageTo(imgContainer, sibling, true); + } + }; + const moveDownButtonCallback = e => { + var imgContainer = e.target; + while (!imgContainer.classList.contains(id)) { + imgContainer = imgContainer.parentNode; + } + const sibling = imgContainer.nextSibling; + if (sibling) { + movePageTo(imgContainer, sibling.nextSibling, true); + } + }; + const rotateCCWButtonCallback = e => { + var imgContainer = e.target; + while (!imgContainer.classList.contains(id)) { + imgContainer = imgContainer.parentNode; + } + const img = imgContainer.querySelector("img"); + + rotateElement(img, -90) + }; + const rotateCWButtonCallback = e => { + var imgContainer = e.target; + while (!imgContainer.classList.contains(id)) { + imgContainer = imgContainer.parentNode; + } + const img = imgContainer.querySelector("img"); + + rotateElement(img, 90) + }; + const deletePageButtonCallback = e => { + var imgContainer = e.target; + while (!imgContainer.classList.contains(id)) { + imgContainer = imgContainer.parentNode; + } + pagesContainer.removeChild(imgContainer); + }; + const insertFileButtonCallback = e => { + var imgContainer = e.target; + while (!imgContainer.classList.contains(id)) { + imgContainer = imgContainer.parentNode; + } + addPdfs(imgContainer) + }; + + this.callbacks = { + moveUpButtonCallback, + moveDownButtonCallback, + rotateCCWButtonCallback, + rotateCWButtonCallback, + deletePageButtonCallback, + insertFileButtonCallback + } + } + + attachPDFActions(div) { + const leftDirection = this.pageDirection === 'rtl' ? 'right' : 'left' + const rightDirection = this.pageDirection === 'rtl' ? 'left' : 'right' + const buttonContainer = document.createElement('div'); + + buttonContainer.classList.add("button-container"); + + const moveUp = document.createElement('button'); + moveUp.classList.add("move-left-button","btn", "btn-secondary"); + moveUp.innerHTML = `<i class="bi bi-arrow-${leftDirection}-short"></i>`; + moveUp.onclick = this.callbacks.moveUpButtonCallback; + buttonContainer.appendChild(moveUp); + + const moveDown = document.createElement('button'); + moveDown.classList.add("move-right-button","btn", "btn-secondary"); + moveDown.innerHTML = `<i class="bi bi-arrow-${rightDirection}-short"></i>`; + moveDown.onclick = this.callbacks.moveDownButtonCallback; + buttonContainer.appendChild(moveDown); + + const rotateCCW = document.createElement('button'); + rotateCCW.classList.add("btn", "btn-secondary"); + rotateCCW.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-counterclockwise" viewBox="0 0 16 16"> + <path fill-rule="evenodd" d="M8 3a5 5 0 1 1-4.546 2.914.5.5 0 0 0-.908-.417A6 6 0 1 0 8 2v1z" /> + <path d="M8 4.466V.534a.25.25 0 0 0-.41-.192L5.23 2.308a.25.25 0 0 0 0 .384l2.36 1.966A.25.25 0 0 0 8 4.466z" /> + </svg>`; + rotateCCW.onclick = this.callbacks.rotateCCWButtonCallback; + buttonContainer.appendChild(rotateCCW); + + const rotateCW = document.createElement('button'); + rotateCW.classList.add("btn", "btn-secondary"); + rotateCW.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-clockwise" viewBox="0 0 16 16"> + <path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z" /> + <path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z" /> + </svg>`; + rotateCW.onclick = this.callbacks.rotateCWButtonCallback; + buttonContainer.appendChild(rotateCW); + + const deletePage = document.createElement('button'); + deletePage.classList.add("btn", "btn-danger"); + deletePage.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16"> + <path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5Zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5Zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6Z"/> + <path d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1ZM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118ZM2.5 3h11V2h-11v1Z"/> + </svg>`; + deletePage.onclick = this.callbacks.deletePageButtonCallback; + buttonContainer.appendChild(deletePage); + + div.appendChild(buttonContainer); + + const insertFileButtonContainer = document.createElement('div'); + + insertFileButtonContainer.classList.add( + "insert-file-button-container", + leftDirection, + `align-center-${leftDirection}`); + + const insertFileButton = document.createElement('button'); + insertFileButton.classList.add("btn", "btn-primary", "insert-file-button"); + insertFileButton.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-file-earmark-plus" viewBox="0 0 16 16"> + <path d="M8 6.5a.5.5 0 0 1 .5.5v1.5H10a.5.5 0 0 1 0 1H8.5V11a.5.5 0 0 1-1 0V9.5H6a.5.5 0 0 1 0-1h1.5V7a.5.5 0 0 1 .5-.5z"/> + <path d="M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5L14 4.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5h-2z"/> + </svg>`; + insertFileButton.onclick = this.callbacks.insertFileButtonCallback; + insertFileButtonContainer.appendChild(insertFileButton); + + div.appendChild(insertFileButtonContainer); + + // add this button to every element, but only show it on the last one :D + const insertFileButtonRightContainer = document.createElement('div'); + insertFileButtonRightContainer.classList.add( + "insert-file-button-container", + rightDirection, + `align-center-${rightDirection}`); + + const insertFileButtonRight = document.createElement('button'); + insertFileButtonRight.classList.add("btn", "btn-primary", "insert-file-button"); + insertFileButtonRight.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-file-earmark-plus" viewBox="0 0 16 16"> + <path d="M8 6.5a.5.5 0 0 1 .5.5v1.5H10a.5.5 0 0 1 0 1H8.5V11a.5.5 0 0 1-1 0V9.5H6a.5.5 0 0 1 0-1h1.5V7a.5.5 0 0 1 .5-.5z"/> + <path d="M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5L14 4.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5h-2z"/> + insertFileButtonRight</svg>`; + insertFileButtonRight.onclick = () => addPdfs(); + insertFileButtonRightContainer.appendChild(insertFileButtonRight); + + div.appendChild(insertFileButtonRightContainer); + } +} + +export default PdfActionsManager; \ No newline at end of file diff --git a/src/main/resources/static/js/multitool/pdfContainer.js b/src/main/resources/static/js/multitool/pdfContainer.js new file mode 100644 index 00000000..64c5efa4 --- /dev/null +++ b/src/main/resources/static/js/multitool/pdfContainer.js @@ -0,0 +1,217 @@ +import DragDropManager from "./dragDrop.js"; +import scrollDivHorizontally from "./horizontalScroll.js"; +import getImageHighlighterCallback from "./imageHighlighter.js"; +import PdfActionsManager from './pdfActions.js'; + +const createPdfContainer = (id, wrapperId, highlighterId, dragElId) => { + var fileName = null; + const pagesContainer = document.getElementById(id); + const pagesContainerWrapper = document.getElementById(wrapperId); + + + const movePageTo = (startElement, endElement, scrollTo = false) => { + const childArray = Array.from(pagesContainer.childNodes); + const startIndex = childArray.indexOf(startElement); + const endIndex = childArray.indexOf(endElement); + pagesContainer.removeChild(startElement); + if(!endElement) { + pagesContainer.append(startElement); + } else { + pagesContainer.insertBefore(startElement, endElement); + } + + if(scrollTo) { + const { width } = startElement.getBoundingClientRect(); + const vector = (endIndex !== -1 && startIndex > endIndex) + ? 0-width + : width; + + pagesContainerWrapper.scroll({ + left: pagesContainerWrapper.scrollLeft + vector, + }) + } + } + + function addPdfs(nextSiblingElement) { + var input = document.createElement('input'); + input.type = 'file'; + input.multiple = true; + input.setAttribute("accept", "application/pdf"); + + input.onchange = async(e) => { + const files = e.target.files; + fileName = files[0].name; + for (var i=0; i < files.length; i++) { + addPdfFile(files[i], nextSiblingElement); + } + + document.querySelectorAll(".enable-on-file").forEach(element => { + element.disabled = false; + }); + } + + input.click(); + } + + function rotateElement(element, deg) { + var lastTransform = element.style.rotate; + if (!lastTransform) { + lastTransform = "0"; + } + const lastAngle = parseInt(lastTransform.replace(/[^\d-]/g, '')); + const newAngle = lastAngle + deg; + + element.style.rotate = newAngle + "deg"; + } + + scrollDivHorizontally(wrapperId); + + var imageHighlighterCallback; + if (highlighterId) { + imageHighlighterCallback = getImageHighlighterCallback(highlighterId); + } + var dragDropManager; + if(dragElId) { + dragDropManager = new DragDropManager('drag-container', movePageTo); + } + + var pdfActionManager = new PdfActionsManager('page-container', { movePageTo, addPdfs, rotateElement }); + + async function addPdfFile(file, nextSiblingElement) { + const { renderer, pdfDocument } = await loadFile(file); + + for (var i=0; i < renderer.pageCount; i++) { + const div = document.createElement('div'); + + div.classList.add("page-container"); + + var img = document.createElement('img'); + img.classList.add('page-image') + const imageSrc = await renderer.renderPage(i) + img.src = imageSrc; + img.pageIdx = i; + img.rend = renderer; + img.doc = pdfDocument; + div.appendChild(img); + + + if(dragDropManager) { + dragDropManager.attachDragDropCallbacks(div, imageSrc); + } + + /** + * Making pages larger when clicking on them + */ + if(imageHighlighterCallback) { + img.addEventListener('click', imageHighlighterCallback) + } + + /** + * Rendering the various buttons to manipulate and move pdf pages + */ + pdfActionManager.attachPDFActions(div); + + if (nextSiblingElement) { + pagesContainer.insertBefore(div, nextSiblingElement); + } else { + pagesContainer.appendChild(div); + } + } + } + + async function toRenderer(objectUrl) { + const pdf = await pdfjsLib.getDocument(objectUrl).promise; + return { + document: pdf, + pageCount: pdf.numPages, + renderPage: async function(pageIdx) { + const page = await this.document.getPage(pageIdx+1); + + const canvas = document.createElement("canvas"); + + // set the canvas size to the size of the page + if (page.rotate == 90 || page.rotate == 270) { + canvas.width = page.view[3]; + canvas.height = page.view[2]; + } else { + canvas.width = page.view[2]; + canvas.height = page.view[3]; + } + + // render the page onto the canvas + var renderContext = { + canvasContext: canvas.getContext("2d"), + viewport: page.getViewport({ scale: 1 }) + }; + + await page.render(renderContext).promise; + return canvas.toDataURL(); + } + }; + } + + async function toPdfLib(objectUrl) { + const existingPdfBytes = await fetch(objectUrl).then(res => res.arrayBuffer()); + const pdfDoc = await PDFLib.PDFDocument.load(existingPdfBytes); + return pdfDoc; + } + + async function loadFile(file) { + var objectUrl = URL.createObjectURL(file); + var pdfDocument = await toPdfLib(objectUrl); + var renderer = await toRenderer(objectUrl); + return { renderer, pdfDocument }; + } + + function rotateAll(deg) { + for (var i=0; i<pagesContainer.childNodes.length; i++) { + const img = pagesContainer.childNodes[i].querySelector("img"); + if (!img) continue; + rotateElement(img, deg) + } + } + + async function exportPdf() { + const pdfDoc = await PDFLib.PDFDocument.create(); + for (var i=0; i<pagesContainer.childNodes.length; i++) { + const img = pagesContainer.childNodes[i].querySelector("img"); + if (!img) continue; + const pages = await pdfDoc.copyPages(img.doc, [img.pageIdx]) + const page = pages[0]; + + const rotation = img.style.rotate; + if (rotation) { + const rotationAngle = parseInt(rotation.replace(/[^\d-]/g, '')); + page.setRotation(PDFLib.degrees(page.getRotation().angle + rotationAngle)) + } + + pdfDoc.addPage(page); + } + const pdfBytes = await pdfDoc.save(); + const pdfBlob = new Blob([pdfBytes], { type: 'application/pdf' }); + const url = URL.createObjectURL(pdfBlob); + const downloadOption = localStorage.getItem('downloadOption'); + + if (downloadOption === 'sameWindow') { + // Open the file in the same window + window.location.href = url; + } else if (downloadOption === 'newWindow') { + // Open the file in a new window + window.open(url, '_blank'); + } else { + // Download the file + const downloadLink = document.createElement('a'); + downloadLink.href = url; + downloadLink.download = fileName ? fileName : 'managed.pdf'; + downloadLink.click(); + } + } + + return { + addPdfs, + rotateAll, + exportPdf, + } +} + +export default createPdfContainer; diff --git a/src/main/resources/templates/multi-tool.html b/src/main/resources/templates/multi-tool.html index 44f31007..ef8900c3 100644 --- a/src/main/resources/templates/multi-tool.html +++ b/src/main/resources/templates/multi-tool.html @@ -3,7 +3,6 @@ <th:block th:insert="~{fragments/common :: head(title=#{multiTool.title})}"></th:block> - <body> <div id="image-highlighter"></div> <div id="page-container"> @@ -60,460 +59,22 @@ <div th:insert="~{fragments/footer.html :: footer}"></div> </div> <div id="drag-container"></div> - <script> - var fileName = null; - - /** - * - * Manage Page container scroll - * - */ - const pagesContainer = document.getElementById("pages-container"); - const pagesContainerWrapper = document.getElementById("pages-container-wrapper") - - const pageDirection = document.documentElement.getAttribute("lang-direction"); - var pageDragging = false; - var scrollDelta = 0; // variable to store the accumulated scroll delta - var isScrolling = false; // variable to track if scroll is already in progress - - pagesContainerWrapper.addEventListener("wheel", function(e) { - e.preventDefault(); // prevent default mousewheel behavior - - // Accumulate the horizontal scroll delta - scrollDelta -= e.deltaX || e.wheelDeltaX || -e.deltaY || -e.wheelDeltaY; - - // If scroll is not already in progress, start the scroll loop - if (!isScrolling) { - isScrolling = true; - requestAnimationFrame(scrollLoop); - } - }); - - function scrollLoop() { - // Scroll the div horizontally by a fraction of the accumulated scroll delta - pagesContainerWrapper.scrollLeft += scrollDelta * 0.1; - - // Reduce the accumulated scroll delta by a fraction - scrollDelta *= 0.9; - - // If scroll delta is still significant, continue the scroll loop - if (Math.abs(scrollDelta) > 0.1) { - requestAnimationFrame(scrollLoop); - } else { - isScrolling = false; // Reset scroll in progress flag - } - } - - /** - * - * Manage image highlighter clearing - * - */ - const imageHighlighter = document.getElementById('image-highlighter'); - imageHighlighter.onclick = () => { - imageHighlighter.childNodes.forEach((child) => { - child.classList.add('remove'); - setTimeout(() => { - imageHighlighter.removeChild(child); - }, 100) - }) - } - - const imageHighlightCallback = (highlightEvent) => { - var bigImg = document.createElement('img'); - bigImg.onclick = (imageClickEvent) => { - // This prevents the highlighter's onClick from closing the image when clicking on the image - // instead of next to it. - imageClickEvent.preventDefault(); - imageClickEvent.stopPropagation(); - }; - bigImg.src = highlightEvent.target.src; - imageHighlighter.appendChild(bigImg); - } - - - /** - * - * Manage visual aspect of dragging images - * - */ - const dragContainer = document.getElementById('drag-container'); - var draggedEl; - var draggedImageEl; - var hoveredEl; - - const startDraggingPage = (div, imageSrc) => { - pageDragging = true; - draggedEl = div; - div.classList.add('dragging'); - const imgEl = document.createElement('img'); - imgEl.classList.add('dragged-img'); - imgEl.src = imageSrc; - draggedImageEl = imgEl; - draggedImageEl.style.left = screenX; - draggedImageEl.style.right = screenY; - dragContainer.appendChild(imgEl); - window.addEventListener('mouseup', (e) => { - stopDraggingPage(); - }) - window.addEventListener('mousemove', onDragEl) - - } - - const onDragEl = (mouseEvent) => { - const { clientX, clientY } = mouseEvent; - if(draggedImageEl) { - draggedImageEl.style.left = `${clientX}px`; - draggedImageEl.style.top = `${clientY}px`; - } - } - - const stopDraggingPage = () => { - window.removeEventListener('mousemove', onDragEl); - draggedImageEl = undefined; - pageDragging = false; - draggedEl.classList.remove('dragging'); - hoveredEl.classList.remove('draghover'); - dragContainer.childNodes.forEach((dragChild) => { - dragContainer.removeChild(dragChild); - }) - movePageTo(draggedEl, hoveredEl); - } - - - /** - * - * Methods for managing PDFs - * - */ - function addPdfs(nextSiblingElement) { - var input = document.createElement('input'); - input.type = 'file'; - input.multiple = true; - input.setAttribute("accept", "application/pdf"); - - input.onchange = async(e) => { - const files = e.target.files; - fileName = files[0].name; - for (var i=0; i < files.length; i++) { - addPdfFile(files[i], nextSiblingElement); - } - - document.querySelectorAll(".enable-on-file").forEach(element => { - element.disabled = false; - }); - } - - input.click(); - } - - - const movePageTo = (startElement, endElement, scrollTo = false) => { - const childArray = Array.from(pagesContainer.childNodes); - const startIndex = childArray.indexOf(startElement); - const endIndex = childArray.indexOf(endElement); - pagesContainer.removeChild(startElement); - if(!endElement) { - pagesContainer.append(startElement); - } else { - pagesContainer.insertBefore(startElement, endElement); - } - - if(scrollTo) { - const { width } = startElement.getBoundingClientRect(); - const vector = (endIndex !== -1 && startIndex > endIndex) - ? 0-width - : width; - - pagesContainerWrapper.scroll({ - left: pagesContainerWrapper.scrollLeft + vector, - }) - } - } - - async function addPdfFile(file, nextSiblingElement) { - const { renderer, pdfDocument } = await loadFile(file); - - const moveUpButtonCallback = e => { - var imgContainer = e.target; - const startingPosition = imgContainer.getBoundingClientRect(); - while (!imgContainer.classList.contains("page-container")) { - imgContainer = imgContainer.parentNode; - } - - const sibling = imgContainer.previousSibling; - if (sibling) { - movePageTo(imgContainer, sibling, true); - } - }; - const moveDownButtonCallback = e => { - var imgContainer = e.target; - while (!imgContainer.classList.contains("page-container")) { - imgContainer = imgContainer.parentNode; - } - const sibling = imgContainer.nextSibling; - if (sibling) { - movePageTo(imgContainer, sibling.nextSibling, true); - } - }; - const rotateCCWButtonCallback = e => { - var imgContainer = e.target; - while (!imgContainer.classList.contains("page-container")) { - imgContainer = imgContainer.parentNode; - } - const img = imgContainer.querySelector("img"); - - rotateElement(img, -90) - }; - const rotateCWButtonCallback = e => { - var imgContainer = e.target; - while (!imgContainer.classList.contains("page-container")) { - imgContainer = imgContainer.parentNode; - } - const img = imgContainer.querySelector("img"); - - rotateElement(img, 90) - }; - const deletePageButtonCallback = e => { - var imgContainer = e.target; - while (!imgContainer.classList.contains("page-container")) { - imgContainer = imgContainer.parentNode; - } - pagesContainer.removeChild(imgContainer); - }; - const insertFileButtonCallback = e => { - var imgContainer = e.target; - while (!imgContainer.classList.contains("page-container")) { - imgContainer = imgContainer.parentNode; - } - addPdfs(imgContainer) - }; - - for (var i=0; i < renderer.pageCount; i++) { - const div = document.createElement('div'); - - div.addEventListener('dragstart', (e) => { - startDraggingPage(div, imageSrc); - }); - - div.addEventListener('mouseenter', () => { - if (pageDragging) { - hoveredEl = div; - div.classList.add('draghover'); - } - }) - - div.addEventListener('mouseleave', () => { - hoveredEl = undefined; - div.classList.remove('draghover'); - }) - - div.classList.add("page-container"); - - var img = document.createElement('img'); - img.classList.add('page-image') - const imageSrc = await renderer.renderPage(i) - img.src = imageSrc; - img.pageIdx = i; - img.rend = renderer; - img.doc = pdfDocument; - div.appendChild(img); - - /** - * Making pages larger when clicking on them - */ - img.addEventListener('click',imageHighlightCallback) - - /** - * Rendering the various buttons to manipulate and move pdf pages - */ - - const leftDirection = pageDirection === 'rtl' ? 'right' : 'left' - const rightDirection = pageDirection === 'rtl' ? 'left' : 'right' - const buttonContainer = document.createElement('div'); - buttonContainer.classList.add("button-container"); - - const moveUp = document.createElement('button'); - moveUp.classList.add("move-left-button","btn", "btn-secondary"); - moveUp.innerHTML = `<i class="bi bi-arrow-${leftDirection}-short"></i>`; - moveUp.onclick = moveUpButtonCallback; - buttonContainer.appendChild(moveUp); - - const moveDown = document.createElement('button'); - moveDown.classList.add("move-right-button","btn", "btn-secondary"); - moveDown.innerHTML = `<i class="bi bi-arrow-${rightDirection}-short"></i>`; - moveDown.onclick = moveDownButtonCallback; - buttonContainer.appendChild(moveDown); - - const rotateCCW = document.createElement('button'); - rotateCCW.classList.add("btn", "btn-secondary"); - rotateCCW.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-counterclockwise" viewBox="0 0 16 16"> - <path fill-rule="evenodd" d="M8 3a5 5 0 1 1-4.546 2.914.5.5 0 0 0-.908-.417A6 6 0 1 0 8 2v1z" /> - <path d="M8 4.466V.534a.25.25 0 0 0-.41-.192L5.23 2.308a.25.25 0 0 0 0 .384l2.36 1.966A.25.25 0 0 0 8 4.466z" /> - </svg>`; - rotateCCW.onclick = rotateCCWButtonCallback; - buttonContainer.appendChild(rotateCCW); - - const rotateCW = document.createElement('button'); - rotateCW.classList.add("btn", "btn-secondary"); - rotateCW.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-clockwise" viewBox="0 0 16 16"> - <path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z" /> - <path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z" /> - </svg>`; - rotateCW.onclick = rotateCWButtonCallback; - buttonContainer.appendChild(rotateCW); - - const deletePage = document.createElement('button'); - deletePage.classList.add("btn", "btn-danger"); - deletePage.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16"> - <path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5Zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5Zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6Z"/> - <path d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1ZM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118ZM2.5 3h11V2h-11v1Z"/> - </svg>`; - deletePage.onclick = deletePageButtonCallback; - buttonContainer.appendChild(deletePage); - - div.appendChild(buttonContainer); - - const insertFileButtonContainer = document.createElement('div'); - - insertFileButtonContainer.classList.add( - "insert-file-button-container", - leftDirection, - `align-center-${leftDirection}`); - - const insertFileButton = document.createElement('button'); - insertFileButton.classList.add("btn", "btn-primary", "insert-file-button"); - insertFileButton.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-file-earmark-plus" viewBox="0 0 16 16"> - <path d="M8 6.5a.5.5 0 0 1 .5.5v1.5H10a.5.5 0 0 1 0 1H8.5V11a.5.5 0 0 1-1 0V9.5H6a.5.5 0 0 1 0-1h1.5V7a.5.5 0 0 1 .5-.5z"/> - <path d="M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5L14 4.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5h-2z"/> - </svg>`; - insertFileButton.onclick = insertFileButtonCallback; - insertFileButtonContainer.appendChild(insertFileButton); - - div.appendChild(insertFileButtonContainer); - - // add this button to every element, but only show it on the last one :D - const insertFileButtonRightContainer = document.createElement('div'); - insertFileButtonRightContainer.classList.add( - "insert-file-button-container", - rightDirection, - `align-center-${rightDirection}`); - - const insertFileButtonRight = document.createElement('button'); - insertFileButtonRight.classList.add("btn", "btn-primary", "insert-file-button"); - insertFileButtonRight.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-file-earmark-plus" viewBox="0 0 16 16"> - <path d="M8 6.5a.5.5 0 0 1 .5.5v1.5H10a.5.5 0 0 1 0 1H8.5V11a.5.5 0 0 1-1 0V9.5H6a.5.5 0 0 1 0-1h1.5V7a.5.5 0 0 1 .5-.5z"/> - <path d="M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5L14 4.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5h-2z"/> - insertFileButtonRight</svg>`; - insertFileButtonRight.onclick = () => addPdfs(); - insertFileButtonRightContainer.appendChild(insertFileButtonRight); - - div.appendChild(insertFileButtonRightContainer); - - if (nextSiblingElement) { - pagesContainer.insertBefore(div, nextSiblingElement); - } else { - pagesContainer.appendChild(div); - } - } - } - - function rotateElement(element, deg) { - var lastTransform = element.style.rotate; - if (!lastTransform) { - lastTransform = "0"; - } - const lastAngle = parseInt(lastTransform.replace(/[^\d-]/g, '')); - const newAngle = lastAngle + deg; - - element.style.rotate = newAngle + "deg"; - } - - function rotateAll(deg) { - for (var i=0; i<pagesContainer.childNodes.length; i++) { - const img = pagesContainer.childNodes[i].querySelector("img"); - if (!img) continue; - rotateElement(img, deg) - } - } - - async function exportPdf() { - const pdfDoc = await PDFLib.PDFDocument.create(); - for (var i=0; i<pagesContainer.childNodes.length; i++) { - const img = pagesContainer.childNodes[i].querySelector("img"); - if (!img) continue; - const pages = await pdfDoc.copyPages(img.doc, [img.pageIdx]) - const page = pages[0]; - - const rotation = img.style.rotate; - if (rotation) { - const rotationAngle = parseInt(rotation.replace(/[^\d-]/g, '')); - page.setRotation(PDFLib.degrees(page.getRotation().angle + rotationAngle)) - } - - pdfDoc.addPage(page); - } - const pdfBytes = await pdfDoc.save(); - const pdfBlob = new Blob([pdfBytes], { type: 'application/pdf' }); - const url = URL.createObjectURL(pdfBlob); - const downloadOption = localStorage.getItem('downloadOption'); - - if (downloadOption === 'sameWindow') { - // Open the file in the same window - window.location.href = url; - } else if (downloadOption === 'newWindow') { - // Open the file in a new window - window.open(url, '_blank'); - } else { - // Download the file - const downloadLink = document.createElement('a'); - downloadLink.href = url; - downloadLink.download = fileName ? fileName : 'managed.pdf'; - downloadLink.click(); - } - } - - async function loadFile(file) { - var objectUrl = URL.createObjectURL(file); - var pdfDocument = await toPdfLib(objectUrl); - var renderer = await toRenderer(objectUrl); - return { renderer, pdfDocument }; - } - async function toPdfLib(objectUrl) { - const existingPdfBytes = await fetch(objectUrl).then(res => res.arrayBuffer()); - const pdfDoc = await PDFLib.PDFDocument.load(existingPdfBytes); - return pdfDoc; - } - async function toRenderer(objectUrl) { - const pdf = await pdfjsLib.getDocument(objectUrl).promise; - return { - document: pdf, - pageCount: pdf.numPages, - renderPage: async function(pageIdx) { - const page = await this.document.getPage(pageIdx+1); - - const canvas = document.createElement("canvas"); - - // set the canvas size to the size of the page - if (page.rotate == 90 || page.rotate == 270) { - canvas.width = page.view[3]; - canvas.height = page.view[2]; - } else { - canvas.width = page.view[2]; - canvas.height = page.view[3]; - } - - // render the page onto the canvas - var renderContext = { - canvasContext: canvas.getContext("2d"), - viewport: page.getViewport({ scale: 1 }) - }; - - await page.render(renderContext).promise; - return canvas.toDataURL(); - } - }; - } + + <script type="module"> + import createPdfContainer from '/js/multitool/pdfContainer.js'; + const { + addPdfs, + exportPdf, + rotateAll, + } = createPdfContainer( + 'pages-container', + 'pages-container-wrapper', + 'image-highlighter', + 'drag-container' + ); + window.addPdfs = addPdfs; + window.exportPdf = exportPdf; + window.rotateAll = rotateAll; </script> <style>