diff --git a/test/integration/reorganize_pages_spec.mjs b/test/integration/reorganize_pages_spec.mjs index 647bea1cfabf8..c20cad727602c 100644 --- a/test/integration/reorganize_pages_spec.mjs +++ b/test/integration/reorganize_pages_spec.mjs @@ -630,4 +630,136 @@ describe("Reorganize Pages View", () => { ); }); }); + + describe("Dynamic status label", () => { + let pages; + + beforeEach(async () => { + pages = await loadAndWait( + "page_with_number.pdf", + "#viewsManagerToggleButton", + "page-fit", + null, + { enableSplitMerge: true } + ); + }); + + afterEach(async () => { + await closePages(pages); + }); + + it("should update the status label based on page selection", async () => { + await Promise.all( + pages.map(async ([browserName, page]) => { + await waitForThumbnailVisible(page, 1); + + // Initially, the label should show "Select pages" + const initialLabel = await page.evaluate(() => { + const label = document.getElementById( + "viewsManagerStatusActionLabel" + ); + return { + text: label.textContent, + l10nId: label.getAttribute("data-l10n-id"), + l10nArgs: label.getAttribute("data-l10n-args"), + }; + }); + + expect(initialLabel.l10nId) + .withContext(`In ${browserName}`) + .toBe("pdfjs-views-manager-pages-status-none-action-label"); + expect(initialLabel.l10nArgs) + .withContext(`In ${browserName}`) + .toBeNull(); + + // Select the first page and check that the label updates to + // "1 selected" + await page.evaluate(() => { + const checkbox = document.querySelector( + '#thumbnailsView input[type="checkbox"]' + ); + checkbox.click(); + }); + + // Check that the label now shows "1 selected" + const oneSelectedLabel = await page.evaluate(() => { + const label = document.getElementById( + "viewsManagerStatusActionLabel" + ); + return { + text: label.textContent, + l10nId: label.getAttribute("data-l10n-id"), + l10nArgs: label.getAttribute("data-l10n-args"), + }; + }); + + expect(oneSelectedLabel.l10nId) + .withContext(`In ${browserName}`) + .toBe("pdfjs-views-manager-pages-status-action-label"); + expect(oneSelectedLabel.l10nArgs) + .withContext(`In ${browserName}`) + .toBe('{"count":1}'); + + // Select two more pages and check that the label updates to + // "3 selected" + await page.evaluate(() => { + const checkboxes = document.querySelectorAll( + '#thumbnailsView input[type="checkbox"]' + ); + checkboxes[1].click(); + checkboxes[2].click(); + }); + + // Check that the label now shows "3 selected" + const threeSelectedLabel = await page.evaluate(() => { + const label = document.getElementById( + "viewsManagerStatusActionLabel" + ); + return { + text: label.textContent, + l10nId: label.getAttribute("data-l10n-id"), + l10nArgs: label.getAttribute("data-l10n-args"), + }; + }); + + expect(threeSelectedLabel.l10nId) + .withContext(`In ${browserName}`) + .toBe("pdfjs-views-manager-pages-status-action-label"); + expect(threeSelectedLabel.l10nArgs) + .withContext(`In ${browserName}`) + .toBe('{"count":3}'); + + // Unselect all pages and check that the label is back to + // "Select pages" + await page.evaluate(() => { + const checkboxes = document.querySelectorAll( + '#thumbnailsView input[type="checkbox"]' + ); + checkboxes[0].click(); + checkboxes[1].click(); + checkboxes[2].click(); + }); + + // Check that the label is back to "Select pages" + const finalLabel = await page.evaluate(() => { + const label = document.getElementById( + "viewsManagerStatusActionLabel" + ); + return { + text: label.textContent, + l10nId: label.getAttribute("data-l10n-id"), + l10nArgs: label.getAttribute("data-l10n-args"), + }; + }); + + expect(finalLabel.l10nId) + .withContext(`In ${browserName}`) + .toBe("pdfjs-views-manager-pages-status-none-action-label"); + expect(finalLabel.l10nArgs) + .withContext(`In ${browserName}`) + .toBeNull(); + }) + ); + }); + }); }); diff --git a/web/app.js b/web/app.js index b482c84f0b191..b92c284d1b824 100644 --- a/web/app.js +++ b/web/app.js @@ -2162,6 +2162,11 @@ const PDFViewerApplication = { onUpdateFindControlState.bind(this), opts ); + eventBus._on( + "pagesselectionchanged", + onPagesSelectionChanged.bind(this), + opts + ); if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) { eventBus._on("fileinputchange", onFileInputChange.bind(this), opts); @@ -2745,6 +2750,29 @@ function onUpdateFindControlState({ } } +function onPagesSelectionChanged({ count }) { + const label = document.getElementById("viewsManagerStatusActionLabel"); + if (!label) { + return; + } + + if (count === 0) { + // No pages selected - show "Select pages" + label.setAttribute( + "data-l10n-id", + "pdfjs-views-manager-pages-status-none-action-label" + ); + label.removeAttribute("data-l10n-args"); + } else { + // Pages selected - show count "X selected" + label.setAttribute( + "data-l10n-id", + "pdfjs-views-manager-pages-status-action-label" + ); + label.setAttribute("data-l10n-args", JSON.stringify({ count })); + } +} + function onScaleChanging(evt) { this.toolbar?.setPageScale(evt.presetValue, evt.scale); diff --git a/web/pdf_thumbnail_viewer.js b/web/pdf_thumbnail_viewer.js index a11636c4c315f..5be1ca2e080c1 100644 --- a/web/pdf_thumbnail_viewer.js +++ b/web/pdf_thumbnail_viewer.js @@ -539,6 +539,12 @@ class PDFThumbnailViewer { selectedPages.clear(); this.#pageNumberToRemove = NaN; + // Dispatch event to notify about selection being cleared + this.eventBus.dispatch("pagesselectionchanged", { + source: this, + count: 0, + }); + const isIdentity = (this.#manageSaveAsButton.disabled = !this.#pagesMapper.hasBeenAltered()); if (!isIdentity) { @@ -748,6 +754,12 @@ class PDFThumbnailViewer { } else { set.delete(pageNumber); } + + // Dispatch event to notify about selection changes + this.eventBus.dispatch("pagesselectionchanged", { + source: this, + count: set.size, + }); } #addDragListeners() {