1
0
mirror of https://github.com/Stirling-Tools/Stirling-PDF.git synced 2024-06-30 22:50:11 +02:00

further refactor js

Turn the div adapters into injectable files so that each PdfContainer can be customized. And the adapters can be used in different PdfContainers as well.
This commit is contained in:
jordy 2023-04-29 12:43:12 +02:00
parent e8a91d2631
commit 9a1510a4f1
6 changed files with 206 additions and 190 deletions

View File

@ -8,9 +8,8 @@ class DragDropManager {
draggedImageEl;
hoveredEl;
constructor(id, movePageTo) {
constructor(id) {
this.dragContainer = document.getElementById(id);
this.movePageTo = movePageTo;
this.pageDragging = false;
this.hoveredEl = undefined;
this.draggelEl = undefined
@ -19,8 +18,6 @@ class DragDropManager {
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) {
@ -34,9 +31,7 @@ class DragDropManager {
this.draggedImageEl.style.left = screenX;
this.draggedImageEl.style.right = screenY;
this.dragContainer.appendChild(imgEl);
window.addEventListener('mouseup', (e) => {
this.stopDraggingPage();
})
window.addEventListener('mouseup', this.stopDraggingPage)
window.addEventListener('mousemove', this.onDragEl)
}
@ -51,6 +46,7 @@ class DragDropManager {
stopDraggingPage() {
window.removeEventListener('mousemove', this.onDragEl);
window.removeEventListener('mouseup', this.stopDraggingPage)
this.draggedImageEl = undefined;
this.pageDragging = false;
this.draggedEl.classList.remove('dragging');
@ -61,10 +57,14 @@ class DragDropManager {
this.movePageTo(this.draggedEl, this.hoveredEl);
}
setActions({ movePageTo }) {
this.movePageTo = movePageTo;
}
attachDragDropCallbacks(div, imageSrc) {
adapt(div) {
const onDragStart = () => {
this.startDraggingPage(div, imageSrc);
this.startDraggingPage(div, div.querySelector('img').src);
}
const onMouseEnter = () => {
@ -82,6 +82,8 @@ class DragDropManager {
div.addEventListener('dragstart', onDragStart);
div.addEventListener('mouseenter', onMouseEnter);
div.addEventListener('mouseleave', onMouseLeave);
return div;
}
}

View File

@ -1,73 +1,75 @@
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)
};
pageDirection;
pagesContainer;
this.callbacks = {
moveUpButtonCallback,
moveDownButtonCallback,
rotateCCWButtonCallback,
rotateCWButtonCallback,
deletePageButtonCallback,
insertFileButtonCallback
constructor(id) {
this.pagesContainer = document.getElementById(id);
this.pageDirection = document.documentElement.getAttribute("lang-direction");
}
getPageContainer(element) {
var container = element
while (!container.classList.contains('page-container')) {
container = container.parentNode;
}
return container;
}
moveUpButtonCallback(e) {
var imgContainer = this.getPageContainer(e.target);
const sibling = imgContainer.previousSibling;
if (sibling) {
this.movePageTo(imgContainer, sibling, true);
}
}
attachPDFActions(div) {
moveDownButtonCallback(e) {
var imgContainer = this.getPageContainer(e.target);
const sibling = imgContainer.nextSibling;
if (sibling) {
this.movePageTo(imgContainer, sibling.nextSibling, true);
}
};
rotateCCWButtonCallback(e) {
var imgContainer = this.getPageContainer(e.target);
const img = imgContainer.querySelector("img");
this.rotateElement(img, -90)
};
rotateCWButtonCallback(e) {
var imgContainer = this.getPageContainer(e.target);
const img = imgContainer.querySelector("img");
this.rotateElement(img, 90)
};
deletePageButtonCallback(e) {
var imgContainer = this.getPageContainer(e.target);
this.pagesContainer.removeChild(imgContainer);
};
insertFileButtonCallback(e) {
var imgContainer = this.getPageContainer(e.target);
this.addPdfs(imgContainer)
};
setActions({ movePageTo, addPdfs, rotateElement }) {
this.movePageTo = movePageTo;
this.addPdfs = addPdfs;
this.rotateElement = rotateElement;
this.moveUpButtonCallback = this.moveUpButtonCallback.bind(this);
this.moveDownButtonCallback = this.moveDownButtonCallback.bind(this);
this.rotateCCWButtonCallback = this.rotateCCWButtonCallback.bind(this);
this.rotateCWButtonCallback = this.rotateCWButtonCallback.bind(this);
this.deletePageButtonCallback = this.deletePageButtonCallback.bind(this);
this.insertFileButtonCallback = this.insertFileButtonCallback.bind(this);
}
adapt(div) {
const leftDirection = this.pageDirection === 'rtl' ? 'right' : 'left'
const rightDirection = this.pageDirection === 'rtl' ? 'left' : 'right'
const buttonContainer = document.createElement('div');
@ -77,13 +79,13 @@ class PdfActionsManager {
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;
moveUp.onclick = this.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;
moveDown.onclick = this.moveDownButtonCallback;
buttonContainer.appendChild(moveDown);
const rotateCCW = document.createElement('button');
@ -92,7 +94,7 @@ class PdfActionsManager {
<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;
rotateCCW.onclick = this.rotateCCWButtonCallback;
buttonContainer.appendChild(rotateCCW);
const rotateCW = document.createElement('button');
@ -101,7 +103,7 @@ class PdfActionsManager {
<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;
rotateCW.onclick = this.rotateCWButtonCallback;
buttonContainer.appendChild(rotateCW);
const deletePage = document.createElement('button');
@ -110,7 +112,7 @@ class PdfActionsManager {
<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;
deletePage.onclick = this.deletePageButtonCallback;
buttonContainer.appendChild(deletePage);
div.appendChild(buttonContainer);
@ -128,7 +130,7 @@ class PdfActionsManager {
<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;
insertFileButton.onclick = this.insertFileButtonCallback;
insertFileButtonContainer.appendChild(insertFileButton);
div.appendChild(insertFileButtonContainer);
@ -150,6 +152,8 @@ class PdfActionsManager {
insertFileButtonRightContainer.appendChild(insertFileButtonRight);
div.appendChild(insertFileButtonRightContainer);
return div;
}
}

View File

@ -1,12 +1,10 @@
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);
const divToScrollHorizontally = document.getElementById(id)
function scrollLoop() {
// Scroll the div horizontally by a fraction of the accumulated scroll delta
pagesContainerWrapper.scrollLeft += scrollDelta * 0.1;
divToScrollHorizontally.scrollLeft += scrollDelta * 0.1;
// Reduce the accumulated scroll delta by a fraction
scrollDelta *= 0.9;
@ -19,7 +17,7 @@ const scrollDivHorizontally = (id) => {
}
}
const divToScrollHorizontally = document.getElementById(id)
divToScrollHorizontally.addEventListener("wheel", function(e) {
e.preventDefault(); // prevent default mousewheel behavior

View File

@ -1,27 +1,39 @@
const getImageHighlighterCallback = (id) => {
const imageHighlighter = document.getElementById(id);
imageHighlighter.onclick = () => {
imageHighlighter.childNodes.forEach((child) => {
child.classList.add('remove');
setTimeout(() => {
imageHighlighter.removeChild(child);
}, 100)
})
class ImageHiglighter {
imageHighlighter;
constructor(id) {
this.imageHighlighter = document.getElementById(id);
this.imageHighlightCallback = this.imageHighlightCallback.bind(this);
this.imageHighlighter.onclick = () => {
this.imageHighlighter.childNodes.forEach((child) => {
child.classList.add('remove');
setTimeout(() => {
this.imageHighlighter.removeChild(child);
}, 100)
})
}
}
const imageHighlightCallback = (highlightEvent) => {
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.
// 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);
this.imageHighlighter.appendChild(bigImg);
};
return imageHighlightCallback
setActions() {
// not needed in this case
}
adapt(div) {
const img = div.querySelector('.page-image');
img.addEventListener('click', this.imageHighlightCallback)
return div;
}
}
export default getImageHighlighterCallback;
export default ImageHiglighter;

View File

@ -1,23 +1,43 @@
import DragDropManager from "./dragDrop.js";
import scrollDivHorizontally from "./horizontalScroll.js";
import getImageHighlighterCallback from "./imageHighlighter.js";
import PdfActionsManager from './pdfActions.js';
class PdfContainer {
fileName;
pagesContainer;
pagesContainerWrapper;
pdfAdapters;
const createPdfContainer = (id, wrapperId, highlighterId, dragElId) => {
var fileName = null;
const pagesContainer = document.getElementById(id);
const pagesContainerWrapper = document.getElementById(wrapperId);
constructor(id, wrapperId, pdfAdapters) {
this.fileName = null;
this.pagesContainer = document.getElementById(id)
this.pagesContainerWrapper = document.getElementById(wrapperId);
this.movePageTo = this.movePageTo.bind(this);
this.addPdfs = this.addPdfs.bind(this);
this.rotateElement = this.rotateElement.bind(this);
this.rotateAll = this.rotateAll.bind(this);
this.exportPdf = this.exportPdf.bind(this);
this.pdfAdapters = pdfAdapters;
const movePageTo = (startElement, endElement, scrollTo = false) => {
const childArray = Array.from(pagesContainer.childNodes);
this.pdfAdapters.forEach(adapter => {
adapter.setActions({
movePageTo: this.movePageTo,
addPdfs: this.addPdfs,
rotateElement: this.rotateElement,
})
})
window.addPdfs = this.addPdfs;
window.exportPdf = this.exportPdf;
window.rotateAll = this.rotateAll;
}
movePageTo(startElement, endElement, scrollTo = false) {
const childArray = Array.from(this.pagesContainer.childNodes);
const startIndex = childArray.indexOf(startElement);
const endIndex = childArray.indexOf(endElement);
pagesContainer.removeChild(startElement);
this.pagesContainer.removeChild(startElement);
if(!endElement) {
pagesContainer.append(startElement);
this.pagesContainer.append(startElement);
} else {
pagesContainer.insertBefore(startElement, endElement);
this.pagesContainer.insertBefore(startElement, endElement);
}
if(scrollTo) {
@ -26,13 +46,13 @@ const createPdfContainer = (id, wrapperId, highlighterId, dragElId) => {
? 0-width
: width;
pagesContainerWrapper.scroll({
left: pagesContainerWrapper.scrollLeft + vector,
this.pagesContainerWrapper.scroll({
left: this.pagesContainerWrapper.scrollLeft + vector,
})
}
}
function addPdfs(nextSiblingElement) {
addPdfs(nextSiblingElement) {
var input = document.createElement('input');
input.type = 'file';
input.multiple = true;
@ -40,9 +60,9 @@ const createPdfContainer = (id, wrapperId, highlighterId, dragElId) => {
input.onchange = async(e) => {
const files = e.target.files;
fileName = files[0].name;
this.fileName = files[0].name;
for (var i=0; i < files.length; i++) {
addPdfFile(files[i], nextSiblingElement);
this.addPdfFile(files[i], nextSiblingElement);
}
document.querySelectorAll(".enable-on-file").forEach(element => {
@ -53,7 +73,7 @@ const createPdfContainer = (id, wrapperId, highlighterId, dragElId) => {
input.click();
}
function rotateElement(element, deg) {
rotateElement(element, deg) {
var lastTransform = element.style.rotate;
if (!lastTransform) {
lastTransform = "0";
@ -63,22 +83,9 @@ const createPdfContainer = (id, wrapperId, highlighterId, dragElId) => {
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);
async addPdfFile(file, nextSiblingElement) {
const { renderer, pdfDocument } = await this.loadFile(file);
for (var i=0; i < renderer.pageCount; i++) {
const div = document.createElement('div');
@ -94,32 +101,25 @@ const createPdfContainer = (id, wrapperId, highlighterId, dragElId) => {
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);
this.pdfAdapters.forEach((adapter) => {
adapter.adapt?.(div)
})
if (nextSiblingElement) {
pagesContainer.insertBefore(div, nextSiblingElement);
this.pagesContainer.insertBefore(div, nextSiblingElement);
} else {
pagesContainer.appendChild(div);
this.pagesContainer.appendChild(div);
}
}
}
async function toRenderer(objectUrl) {
async loadFile(file) {
var objectUrl = URL.createObjectURL(file);
var pdfDocument = await this.toPdfLib(objectUrl);
var renderer = await this.toRenderer(objectUrl);
return { renderer, pdfDocument };
}
async toRenderer(objectUrl) {
const pdf = await pdfjsLib.getDocument(objectUrl).promise;
return {
document: pdf,
@ -150,31 +150,26 @@ const createPdfContainer = (id, wrapperId, highlighterId, dragElId) => {
};
}
async function toPdfLib(objectUrl) {
async 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");
rotateAll(deg) {
for (var i=0; i<this.pagesContainer.childNodes.length; i++) {
const img = this.pagesContainer.childNodes[i].querySelector("img");
if (!img) continue;
rotateElement(img, deg)
this.rotateElement(img, deg)
}
}
async function exportPdf() {
async exportPdf() {
const pdfDoc = await PDFLib.PDFDocument.create();
for (var i=0; i<pagesContainer.childNodes.length; i++) {
const img = pagesContainer.childNodes[i].querySelector("img");
for (var i=0; i<this.pagesContainer.childNodes.length; i++) {
const img = this.pagesContainer.childNodes[i].querySelector("img");
if (!img) continue;
const pages = await pdfDoc.copyPages(img.doc, [img.pageIdx])
const page = pages[0];
@ -202,16 +197,10 @@ const createPdfContainer = (id, wrapperId, highlighterId, dragElId) => {
// Download the file
const downloadLink = document.createElement('a');
downloadLink.href = url;
downloadLink.download = fileName ? fileName : 'managed.pdf';
downloadLink.download = this.fileName ? this.fileName : 'managed.pdf';
downloadLink.click();
}
}
return {
addPdfs,
rotateAll,
exportPdf,
}
}
export default createPdfContainer;
export default PdfContainer;

View File

@ -52,7 +52,6 @@
</div>
</div>
</div>
</div>
</div>
@ -61,20 +60,32 @@
<div id="drag-container"></div>
<script type="module">
import createPdfContainer from '/js/multitool/pdfContainer.js';
const {
addPdfs,
exportPdf,
rotateAll,
} = createPdfContainer(
import PdfContainer from '/js/multitool/PdfContainer.js';
import DragDropManager from "/js/multitool/DragDropManager.js";
import scrollDivHorizontally from "/js/multitool/horizontalScroll.js";
import ImageHighlighter from "/js/multitool/ImageHighlighter.js";
import PdfActionsManager from '/js/multitool/PdfActionsManager.js';
// enables drag and drop
const dragDropManager = new DragDropManager('drag-container');
// enables image highlight on click
const imageHighlighter = new ImageHighlighter('image-highlighter');
// enables the default action buttons on each pdf
const pdfActionsManager = new PdfActionsManager('pages-container');
// Scroll the wrapper horizontally
scrollDivHorizontally('pages-container-wrapper');
// Automatically exposes rotateAll, addPdfs and exportPdf to the window for the global buttons.
const pdfContainer = new PdfContainer(
'pages-container',
'pages-container-wrapper',
'image-highlighter',
'drag-container'
);
window.addPdfs = addPdfs;
window.exportPdf = exportPdf;
window.rotateAll = rotateAll;
[
dragDropManager,
imageHighlighter,
pdfActionsManager,
]
)
</script>
<style>
@ -156,7 +167,7 @@
.page-container {
height:250px;
display: flex;
display: flex;
align-items: center;
flex-direction: column-reverse;
aspect-ratio: 1;