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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user