Feature/1976/multi tool multiple pages (#2200)

* Multitool - Select multiple pages for rotation tool

* Multitool multi select delete feature

* Multitool multi select UI improvements and big fixes

* Multitool multi select select all and UI improvements

* Multi tool multi select, download selected, clean up and bug fixes

* Comments

* Update buttons for page selection

* Update translation files

Signed-off-by: GitHub Action <action@github.com>

* Multitool multiselect split functionality and UI updates

* Download selected button, additional tooltips

* Update translation files

Signed-off-by: GitHub Action <action@github.com>

* revert CertSignController

* remove material icons

* restore to previous certsigncontroller

* Update CertSignController.java

---------

Signed-off-by: GitHub Action <action@github.com>
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
This commit is contained in:
reecebrowne
2024-11-14 20:00:36 +00:00
committed by GitHub
parent 7f30882e5e
commit b27e1f254c
42 changed files with 797 additions and 94 deletions

View File

@@ -22,6 +22,11 @@ class PdfContainer {
this.nameAndArchiveFiles = this.nameAndArchiveFiles.bind(this);
this.splitPDF = this.splitPDF.bind(this);
this.splitAll = this.splitAll.bind(this);
this.deleteSelected = this.deleteSelected.bind(this);
this.toggleSelectAll = this.toggleSelectAll.bind(this);
this.updateSelectedPagesDisplay = this.updateSelectedPagesDisplay.bind(this);
this.toggleSelectPageVisibility = this.toggleSelectPageVisibility.bind(this);
this.updatePagesFromCSV = this.updatePagesFromCSV.bind(this);
this.addFilesBlankAll = this.addFilesBlankAll.bind(this)
this.pdfAdapters = pdfAdapters;
@@ -32,6 +37,7 @@ class PdfContainer {
addFiles: this.addFiles,
rotateElement: this.rotateElement,
updateFilename: this.updateFilename,
deleteSelected: this.deleteSelected,
});
});
@@ -39,6 +45,13 @@ class PdfContainer {
window.exportPdf = this.exportPdf;
window.rotateAll = this.rotateAll;
window.splitAll = this.splitAll;
window.deleteSelected = this.deleteSelected;
window.toggleSelectAll = this.toggleSelectAll;
window.updateSelectedPagesDisplay = this.updateSelectedPagesDisplay;
window.toggleSelectPageVisibility = this.toggleSelectPageVisibility;
window.updatePagesFromCSV = this.updatePagesFromCSV;
window.updateSelectedPagesDisplay = this.updateSelectedPagesDisplay;
window.updatePageNumbersAndCheckboxes = this.updatePageNumbersAndCheckboxes;
window.addFilesBlankAll = this.addFilesBlankAll
const filenameInput = document.getElementById("filename-input");
@@ -94,6 +107,8 @@ class PdfContainer {
this.addFilesFromFiles(files, nextSiblingElement);
this.updateFilename(files ? files[0].name : "");
const selectAll = document.getElementById("select-pages-container");
selectAll.classList.toggle("hidden", false);
};
input.click();
@@ -264,15 +279,206 @@ class PdfContainer {
}
rotateAll(deg) {
for (var i = 0; i < this.pagesContainer.childNodes.length; i++) {
for (let i = 0; i < this.pagesContainer.childNodes.length; i++) {
const child = this.pagesContainer.children[i];
if (!child) continue;
const pageIndex = i + 1;
//if in page select mode is active rotate only selected pages
if (window.selectPage && !window.selectedPages.includes(pageIndex)) continue;
const img = child.querySelector("img");
if (!img) continue;
this.rotateElement(img, deg);
}
}
deleteSelected() {
window.selectedPages.sort((a, b) => a - b);
let deletions = 0;
window.selectedPages.forEach((pageIndex) => {
const adjustedIndex = pageIndex - 1 - deletions;
const child = this.pagesContainer.children[adjustedIndex];
if (child) {
this.pagesContainer.removeChild(child);
deletions++;
}
});
if (this.pagesContainer.childElementCount === 0) {
const filenameInput = document.getElementById("filename-input");
const filenameParagraph = document.getElementById("filename");
const downloadBtn = document.getElementById("export-button");
if (filenameInput)
filenameInput.disabled = true;
filenameInput.value = "";
if (filenameParagraph)
filenameParagraph.innerText = "";
downloadBtn.disabled = true;
}
window.selectedPages = [];
this.updatePageNumbersAndCheckboxes();
document.dispatchEvent(new Event("selectedPagesUpdated"));
}
toggleSelectAll() {
const checkboxes = document.querySelectorAll(".pdf-actions_checkbox");
window.selectAll = !window.selectAll;
const selectIcon = document.getElementById("select-icon");
const deselectIcon = document.getElementById("deselect-icon");
if (selectIcon.style.display === "none") {
selectIcon.style.display = "inline";
deselectIcon.style.display = "none";
} else {
selectIcon.style.display = "none";
deselectIcon.style.display = "inline";
}
checkboxes.forEach((checkbox) => {
checkbox.checked = window.selectAll;
const pageNumber = Array.from(checkbox.parentNode.parentNode.children).indexOf(checkbox.parentNode) + 1;
if (checkbox.checked) {
if (!window.selectedPages.includes(pageNumber)) {
window.selectedPages.push(pageNumber);
}
} else {
const index = window.selectedPages.indexOf(pageNumber);
if (index !== -1) {
window.selectedPages.splice(index, 1);
}
}
});
this.updateSelectedPagesDisplay();
}
parseCSVInput(csvInput, maxPageIndex) {
const pages = new Set();
csvInput.split(",").forEach((item) => {
const range = item.split("-").map((p) => parseInt(p.trim()));
if (range.length === 2) {
const [start, end] = range;
for (let i = start; i <= end && i <= maxPageIndex; i++) {
if (i > 0) { // Ensure the page number is greater than 0
pages.add(i);
}
}
} else if (range.length === 1 && Number.isInteger(range[0])) {
const page = range[0];
if (page > 0 && page <= maxPageIndex) { // Ensure page is within valid range
pages.add(page);
}
}
});
return Array.from(pages).sort((a, b) => a - b);
}
updatePagesFromCSV() {
const csvInput = document.getElementById("csv-input").value;
const allPages = this.pagesContainer.querySelectorAll(".page-container");
const maxPageIndex = allPages.length;
window.selectedPages = this.parseCSVInput(csvInput, maxPageIndex);
this.updateSelectedPagesDisplay();
const allCheckboxes = document.querySelectorAll(".pdf-actions_checkbox");
allCheckboxes.forEach((checkbox) => {
const page = parseInt(checkbox.getAttribute("data-page-number"));
checkbox.checked = window.selectedPages.includes(page);
});
}
formatSelectedPages(pages) {
if (pages.length === 0) return "";
pages.sort((a, b) => a - b); // Sort the page numbers in ascending order
const ranges = [];
let start = pages[0];
let end = start;
for (let i = 1; i < pages.length; i++) {
if (pages[i] === end + 1) {
// Consecutive page, update end
end = pages[i];
} else {
// Non-consecutive page, finalize current range
ranges.push(start === end ? `${start}` : `${start}-${end}`);
start = pages[i];
end = start;
}
}
// Add the last range
ranges.push(start === end ? `${start}` : `${start}-${end}`);
return ranges.join(", ");
}
updateSelectedPagesDisplay() {
const selectedPagesList = document.getElementById("selected-pages-list");
const selectedPagesInput = document.getElementById("csv-input");
selectedPagesList.innerHTML = ""; // Clear the list
window.selectedPages.forEach((page) => {
const pageItem = document.createElement("div");
pageItem.className = "page-item";
const pageNumber = document.createElement("span");
const pagelabel = /*[[#{multiTool.page}]]*/ 'Page';
pageNumber.className = "selected-page-number";
pageNumber.innerText = `${pagelabel} ${page}`;
pageItem.appendChild(pageNumber);
const removeBtn = document.createElement("span");
removeBtn.className = "remove-btn";
removeBtn.innerHTML = "✕";
// Remove page from selected pages list and update display and checkbox
removeBtn.onclick = () => {
window.selectedPages = window.selectedPages.filter((p) => p !== page);
this.updateSelectedPagesDisplay();
const checkbox = document.getElementById(`selectPageCheckbox-${page}`);
if (checkbox) {
checkbox.checked = false;
}
};
pageItem.appendChild(removeBtn);
selectedPagesList.appendChild(pageItem);
});
// Update the input field with the formatted page list
selectedPagesInput.value = this.formatSelectedPages(window.selectedPages);
}
parsePageRanges(ranges) {
const pages = new Set();
ranges.split(',').forEach(range => {
const [start, end] = range.split('-').map(Number);
if (end) {
for (let i = start; i <= end; i++) {
pages.add(i);
}
} else {
pages.add(start);
}
});
return Array.from(pages).sort((a, b) => a - b);
}
addFilesBlankAll() {
const allPages = this.pagesContainer.querySelectorAll(".page-container");
@@ -283,20 +489,36 @@ class PdfContainer {
});
}
splitAll() {
const allPages = this.pagesContainer.querySelectorAll(".page-container");
if (this.pagesContainer.querySelectorAll(".split-before").length > 0) {
allPages.forEach(page => {
page.classList.remove("split-before");
});
} else {
allPages.forEach(page => {
page.classList.add("split-before");
});
if (!window.selectPage) {
const hasSplit = this.pagesContainer.querySelectorAll(".split-before").length > 0;
if (hasSplit) {
allPages.forEach(page => {
page.classList.remove("split-before");
});
} else {
allPages.forEach(page => {
page.classList.add("split-before");
});
}
return;
}
allPages.forEach((page, index) => {
const pageIndex = index;
if (window.selectPage && !window.selectedPages.includes(pageIndex)) return;
if (page.classList.contains("split-before")) {
page.classList.remove("split-before");
} else {
page.classList.add("split-before");
}
});
}
async splitPDF(baseDocBytes, splitters) {
const baseDocument = await PDFLib.PDFDocument.load(baseDocBytes);
const pageNum = baseDocument.getPages().length;
@@ -339,52 +561,54 @@ class PdfContainer {
return zip;
}
async exportPdf() {
async exportPdf(selected) {
const pdfDoc = await PDFLib.PDFDocument.create();
const pageContainers = this.pagesContainer.querySelectorAll(".page-container"); // Select all .page-container elements
for (var i = 0; i < pageContainers.length; i++) {
const img = pageContainers[i].querySelector("img"); // Find the img element within each .page-container
if (!img) continue;
let page;
if (img.doc) {
const pages = await pdfDoc.copyPages(img.doc, [img.pageIdx]);
page = pages[0];
pdfDoc.addPage(page);
} else {
page = pdfDoc.addPage([img.naturalWidth, img.naturalHeight]);
const imageBytes = await fetch(img.src).then((res) => res.arrayBuffer());
const uint8Array = new Uint8Array(imageBytes);
const imageType = detectImageType(uint8Array);
if (!selected || window.selectedPages.includes(i + 1)) {
const img = pageContainers[i].querySelector("img"); // Find the img element within each .page-container
if (!img) continue;
let page;
if (img.doc) {
const pages = await pdfDoc.copyPages(img.doc, [img.pageIdx]);
page = pages[0];
pdfDoc.addPage(page);
} else {
page = pdfDoc.addPage([img.naturalWidth, img.naturalHeight]);
const imageBytes = await fetch(img.src).then((res) => res.arrayBuffer());
const uint8Array = new Uint8Array(imageBytes);
const imageType = detectImageType(uint8Array);
let image;
switch (imageType) {
case 'PNG':
image = await pdfDoc.embedPng(imageBytes);
break;
case 'JPEG':
image = await pdfDoc.embedJpg(imageBytes);
break;
case 'TIFF':
image = await pdfDoc.embedTiff(imageBytes);
break;
case 'GIF':
console.warn(`Unsupported image type: ${imageType}`);
continue; // Skip this image
default:
console.warn(`Unsupported image type: ${imageType}`);
continue; // Skip this image
let image;
switch (imageType) {
case 'PNG':
image = await pdfDoc.embedPng(imageBytes);
break;
case 'JPEG':
image = await pdfDoc.embedJpg(imageBytes);
break;
case 'TIFF':
image = await pdfDoc.embedTiff(imageBytes);
break;
case 'GIF':
console.warn(`Unsupported image type: ${imageType}`);
continue; // Skip this image
default:
console.warn(`Unsupported image type: ${imageType}`);
continue; // Skip this image
}
page.drawImage(image, {
x: 0,
y: 0,
width: img.naturalWidth,
height: img.naturalHeight,
});
}
const rotation = img.style.rotate;
if (rotation) {
const rotationAngle = parseInt(rotation.replace(/[^\d-]/g, ""));
page.setRotation(PDFLib.degrees(page.getRotation().angle + rotationAngle));
}
page.drawImage(image, {
x: 0,
y: 0,
width: img.naturalWidth,
height: img.naturalHeight,
});
}
const rotation = img.style.rotate;
if (rotation) {
const rotationAngle = parseInt(rotation.replace(/[^\d-]/g, ""));
page.setRotation(PDFLib.degrees(page.getRotation().angle + rotationAngle));
}
}
pdfDoc.setCreator(stirlingPDFLabel);
@@ -496,7 +720,44 @@ class PdfContainer {
// filenameInput.value.replace('.','');
// }
}
toggleSelectPageVisibility() {
window.selectPage = !window.selectPage;
const checkboxes = document.querySelectorAll(".pdf-actions_checkbox");
checkboxes.forEach(checkbox => {
checkbox.classList.toggle("hidden", !window.selectPage);
});
const deleteButton = document.getElementById("delete-button");
deleteButton.classList.toggle("hidden", !window.selectPage);
const selectedPages = document.getElementById("selected-pages-display");
selectedPages.classList.toggle("hidden", !window.selectPage);
const selectAll = document.getElementById("select-All-Container");
selectedPages.classList.toggle("hidden", !window.selectPage);
const exportSelected = document.getElementById("export-selected-button");
exportSelected.classList.toggle("hidden", !window.selectPage);
const selectPagesButton = document.getElementById("select-pages-button");
selectPagesButton.style.opacity = window.selectPage ? "1" : "0.5";
if (window.selectPage) {
this.updatePageNumbersAndCheckboxes();
}
}
updatePageNumbersAndCheckboxes() {
const pageDivs = document.querySelectorAll(".pdf-actions_container");
pageDivs.forEach((div, index) => {
const pageNumber = index + 1;
const checkbox = div.querySelector(".pdf-actions_checkbox");
checkbox.id = `selectPageCheckbox-${pageNumber}`;
checkbox.setAttribute("data-page-number", pageNumber);
checkbox.checked = window.selectedPages.includes(pageNumber);
});
}
}
function detectImageType(uint8Array) {
// Check for PNG signature
if (uint8Array[0] === 137 && uint8Array[1] === 80 && uint8Array[2] === 78 && uint8Array[3] === 71) {
@@ -521,4 +782,7 @@ function detectImageType(uint8Array) {
return 'UNKNOWN';
}
export default PdfContainer;