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>