mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-02-08 00:21:11 +01:00
Merge pull request #20582 from calixteman/reorg_save
Add a manage button in the thumbnail view in order to save an edited pdf (bug 2010830)
This commit is contained in:
commit
5505201930
@ -1182,10 +1182,39 @@ class PagesMapper {
|
||||
// Finally insert the moved pages.
|
||||
pageNumberToId.set(mappedPagesToMove, adjustedTarget);
|
||||
|
||||
let hasChanged = false;
|
||||
for (let i = 0, ii = pagesNumber; i < ii; i++) {
|
||||
idToPageNumber[pageNumberToId[i] - 1] = i + 1;
|
||||
const id = pageNumberToId[i];
|
||||
hasChanged ||= id !== i + 1;
|
||||
idToPageNumber[id - 1] = i + 1;
|
||||
}
|
||||
this.#updateListeners();
|
||||
|
||||
if (!hasChanged) {
|
||||
// Reset.
|
||||
this.pagesNumber = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the page mappings have been altered from their initial state.
|
||||
* @returns {boolean} True if the mappings have been altered, false otherwise.
|
||||
*/
|
||||
hasBeenAltered() {
|
||||
return PagesMapper.#pageNumberToId !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current page mapping suitable for saving.
|
||||
* @returns {Object} An object containing the page indices.
|
||||
*/
|
||||
getPageMappingForSaving() {
|
||||
// Saving is index-based.
|
||||
return {
|
||||
pageIndices: PagesMapper.#idToPageNumber
|
||||
? PagesMapper.#idToPageNumber.map(x => x - 1)
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
getPrevPageNumber(pageNumber) {
|
||||
|
||||
@ -568,4 +568,66 @@ describe("Reorganize Pages View", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Save a pdf", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"page_with_number.pdf",
|
||||
"#viewsManagerToggleButton",
|
||||
"1",
|
||||
null,
|
||||
{ enableSplitMerge: true }
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("should check that a save is triggered", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await waitForThumbnailVisible(page, 1);
|
||||
await page.waitForSelector("#viewsManagerStatusActionButton", {
|
||||
visible: true,
|
||||
});
|
||||
const rect1 = await getRect(page, getThumbnailSelector(1));
|
||||
const rect2 = await getRect(page, getThumbnailSelector(2));
|
||||
|
||||
await dragAndDrop(
|
||||
page,
|
||||
getThumbnailSelector(1),
|
||||
[[0, rect2.y - rect1.y + rect2.height / 2]],
|
||||
10
|
||||
);
|
||||
|
||||
const handleSaveAs = await createPromise(page, resolve => {
|
||||
window.PDFViewerApplication.eventBus.on(
|
||||
"savepageseditedpdf",
|
||||
({ data }) => {
|
||||
resolve(Array.from(data.pageIndices));
|
||||
},
|
||||
{
|
||||
once: true,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
await page.click("#viewsManagerStatusActionButton");
|
||||
await page.waitForSelector("#viewsManagerStatusActionSaveAs", {
|
||||
visible: true,
|
||||
});
|
||||
await page.click("#viewsManagerStatusActionSaveAs");
|
||||
const pageIndices = await awaitPromise(handleSaveAs);
|
||||
expect(pageIndices)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual([
|
||||
1, 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
|
||||
]);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -124,6 +124,13 @@ describe("PDF Thumbnail View", () => {
|
||||
.withContext(`In ${browserName}`)
|
||||
.toBe(true);
|
||||
|
||||
await kbFocusNext(page);
|
||||
expect(
|
||||
await isElementFocused(page, "#viewsManagerStatusActionButton")
|
||||
)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toBe(true);
|
||||
|
||||
await kbFocusNext(page);
|
||||
expect(
|
||||
await isElementFocused(
|
||||
|
||||
35
web/app.js
35
web/app.js
@ -605,6 +605,7 @@ const PDFViewerApplication = {
|
||||
abortSignal,
|
||||
enableHWA,
|
||||
enableSplitMerge: AppOptions.get("enableSplitMerge"),
|
||||
manageMenu: appConfig.viewsManager.manageMenu,
|
||||
});
|
||||
renderingQueue.setThumbnailViewer(this.pdfThumbnailViewer);
|
||||
}
|
||||
@ -2194,6 +2195,11 @@ const PDFViewerApplication = {
|
||||
this.onBeforePagesEdited.bind(this),
|
||||
opts
|
||||
);
|
||||
eventBus._on(
|
||||
"savepageseditedpdf",
|
||||
this.onSavePagesEditedPDF.bind(this),
|
||||
opts
|
||||
);
|
||||
},
|
||||
|
||||
bindWindowEvents() {
|
||||
@ -2376,6 +2382,35 @@ const PDFViewerApplication = {
|
||||
this.pdfViewer.onPagesEdited(data);
|
||||
},
|
||||
|
||||
async onSavePagesEditedPDF({
|
||||
data: { includePages, excludePages, pageIndices },
|
||||
}) {
|
||||
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("TESTING")) {
|
||||
return;
|
||||
}
|
||||
if (!this.pdfDocument) {
|
||||
return;
|
||||
}
|
||||
const pageInfo = {
|
||||
document: null, // For now, no merge.
|
||||
includePages,
|
||||
excludePages,
|
||||
pageIndices,
|
||||
};
|
||||
const modifiedPdfBytes = await this.pdfDocument.extractPages([pageInfo]);
|
||||
if (!modifiedPdfBytes) {
|
||||
console.error(
|
||||
"Something wrong happened when saving the edited PDF.\nPlease file a bug."
|
||||
);
|
||||
return;
|
||||
}
|
||||
this.downloadManager.download(
|
||||
modifiedPdfBytes,
|
||||
this._downloadUrl,
|
||||
this._docFilename
|
||||
);
|
||||
},
|
||||
|
||||
_accumulateTicks(ticks, prop) {
|
||||
// If the direction changed, reset the accumulated ticks.
|
||||
if ((this[prop] > 0 && ticks < 0) || (this[prop] < 0 && ticks > 0)) {
|
||||
|
||||
@ -28,6 +28,7 @@ import {
|
||||
watchScroll,
|
||||
} from "./ui_utils.js";
|
||||
import { MathClamp, noContextMenu, PagesMapper, stopEvent } from "pdfjs-lib";
|
||||
import { Menu } from "./menu.js";
|
||||
import { PDFThumbnailView } from "./pdf_thumbnail_view.js";
|
||||
|
||||
const SCROLL_OPTIONS = {
|
||||
@ -67,6 +68,8 @@ const SPACE_FOR_DRAG_MARKER_WHEN_NO_NEXT_ELEMENT = 15;
|
||||
* rendering. The default value is `false`.
|
||||
* @property {boolean} [enableSplitMerge] - Enables split and merge features.
|
||||
* The default value is `false`.
|
||||
* @property {Object} [manageMenu] - The menu elements to manage saving edited
|
||||
* PDF.
|
||||
*/
|
||||
|
||||
/**
|
||||
@ -109,6 +112,8 @@ class PDFThumbnailViewer {
|
||||
|
||||
#pagesMapper = PagesMapper.instance;
|
||||
|
||||
#manageSaveAsButton = null;
|
||||
|
||||
/**
|
||||
* @param {PDFThumbnailViewerOptions} options
|
||||
*/
|
||||
@ -123,6 +128,7 @@ class PDFThumbnailViewer {
|
||||
abortSignal,
|
||||
enableHWA,
|
||||
enableSplitMerge,
|
||||
manageMenu,
|
||||
}) {
|
||||
this.scrollableContainer = container.parentElement;
|
||||
this.container = container;
|
||||
@ -135,6 +141,20 @@ class PDFThumbnailViewer {
|
||||
this.enableHWA = enableHWA || false;
|
||||
this.#enableSplitMerge = enableSplitMerge || false;
|
||||
|
||||
if (this.#enableSplitMerge && manageMenu) {
|
||||
const { button, menu, copy, cut, delete: del, saveAs } = manageMenu;
|
||||
this._manageMenu = new Menu(menu, button, [copy, cut, del, saveAs]);
|
||||
this.#manageSaveAsButton = saveAs;
|
||||
saveAs.addEventListener("click", () => {
|
||||
this.eventBus.dispatch("savepageseditedpdf", {
|
||||
source: this,
|
||||
data: this.#pagesMapper.getPageMappingForSaving(),
|
||||
});
|
||||
});
|
||||
} else {
|
||||
manageMenu.button.hidden = true;
|
||||
}
|
||||
|
||||
this.scroll = watchScroll(
|
||||
this.scrollableContainer,
|
||||
this.#scrollUpdated.bind(this),
|
||||
@ -519,10 +539,16 @@ class PDFThumbnailViewer {
|
||||
selectedPages.clear();
|
||||
this.#pageNumberToRemove = NaN;
|
||||
|
||||
this.eventBus.dispatch("pagesedited", {
|
||||
source: this,
|
||||
pagesMapper,
|
||||
});
|
||||
const isIdentity = (this.#manageSaveAsButton.disabled =
|
||||
!this.#pagesMapper.hasBeenAltered());
|
||||
if (!isIdentity) {
|
||||
this.eventBus.dispatch("pagesedited", {
|
||||
source: this,
|
||||
pagesMapper,
|
||||
index: newIndex,
|
||||
pagesToMove,
|
||||
});
|
||||
}
|
||||
|
||||
const newCurrentPageNumber = pagesMapper.getPageNumber(newCurrentPageId);
|
||||
setTimeout(() => {
|
||||
|
||||
@ -187,7 +187,7 @@ See https://github.com/adobe-type-tools/cmap-resources
|
||||
</button>
|
||||
</div>
|
||||
<div id="viewsManagerStatus">
|
||||
<div id="viewsManagerStatusAction" class="hidden">
|
||||
<div id="viewsManagerStatusAction">
|
||||
<span
|
||||
id="viewsManagerStatusActionLabel"
|
||||
class="viewsManagerStatusLabel"
|
||||
@ -207,22 +207,22 @@ See https://github.com/adobe-type-tools/cmap-resources
|
||||
</button>
|
||||
<menu id="viewsManagerStatusActionOptions" class="popupMenu">
|
||||
<li>
|
||||
<button id="viewsManagerStatusActionCopy" class="noIcon" role="menuitem" type="button" tabindex="0">
|
||||
<button id="viewsManagerStatusActionCopy" class="noIcon" role="menuitem" type="button" tabindex="0" disabled>
|
||||
<span data-l10n-id="pdfjs-views-manager-pages-status-copy-button-label"></span>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button id="viewsManagerStatusActionCut" class="noIcon" role="menuitem" type="button" tabindex="0">
|
||||
<button id="viewsManagerStatusActionCut" class="noIcon" role="menuitem" type="button" tabindex="0" disabled>
|
||||
<span data-l10n-id="pdfjs-views-manager-pages-status-cut-button-label"></span>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button id="viewsManagerStatusActionDelete" class="noIcon" role="menuitem" type="button" tabindex="0">
|
||||
<button id="viewsManagerStatusActionDelete" class="noIcon" role="menuitem" type="button" tabindex="0" disabled>
|
||||
<span data-l10n-id="pdfjs-views-manager-pages-status-delete-button-label"></span>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button id="viewsManagerStatusActionSaveAs" class="noIcon" role="menuitem" type="button" tabindex="0">
|
||||
<button id="viewsManagerStatusActionSaveAs" class="noIcon" role="menuitem" type="button" tabindex="0" disabled>
|
||||
<span data-l10n-id="pdfjs-views-manager-pages-status-save-as-button-label"></span>
|
||||
</button>
|
||||
</li>
|
||||
|
||||
@ -132,6 +132,14 @@ function getViewerConfiguration() {
|
||||
viewsManagerHeaderLabel: document.getElementById(
|
||||
"viewsManagerHeaderLabel"
|
||||
),
|
||||
manageMenu: {
|
||||
button: document.getElementById("viewsManagerStatusActionButton"),
|
||||
menu: document.getElementById("viewsManagerStatusActionOptions"),
|
||||
copy: document.getElementById("viewsManagerStatusActionCopy"),
|
||||
cut: document.getElementById("viewsManagerStatusActionCut"),
|
||||
delete: document.getElementById("viewsManagerStatusActionDelete"),
|
||||
saveAs: document.getElementById("viewsManagerStatusActionSaveAs"),
|
||||
},
|
||||
},
|
||||
findBar: {
|
||||
bar: document.getElementById("findbar"),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user