1
0
mirror of https://github.com/Stirling-Tools/Stirling-PDF.git synced 2024-11-04 15:00:14 +01:00

refactor JS

Rather than having all the JS all in one big file, separate the code into modules.
This commit is contained in:
jordy 2023-04-28 21:20:56 +02:00
parent 6cce2ee70a
commit e8a91d2631
6 changed files with 541 additions and 455 deletions

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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>