Change the sidebar for a views manager
Update the styles and HTML to reflect the new views manager concept. For now, nothing about split/merge functionality is implemented or visible. The new styles for the outline, attachments, and layers will be added later. The thumbnail view is now accessible with the keyboard.
@ -211,13 +211,13 @@ function createWebpackAlias(defines) {
|
||||
"web-pdf_layer_viewer": "web/pdf_layer_viewer.js",
|
||||
"web-pdf_outline_viewer": "web/pdf_outline_viewer.js",
|
||||
"web-pdf_presentation_mode": "web/pdf_presentation_mode.js",
|
||||
"web-pdf_sidebar": "web/pdf_sidebar.js",
|
||||
"web-pdf_thumbnail_viewer": "web/pdf_thumbnail_viewer.js",
|
||||
"web-preferences": "",
|
||||
"web-print_service": "",
|
||||
"web-secondary_toolbar": "web/secondary_toolbar.js",
|
||||
"web-signature_manager": "web/signature_manager.js",
|
||||
"web-toolbar": "web/toolbar.js",
|
||||
"web-views_manager": "web/views_manager.js",
|
||||
};
|
||||
|
||||
if (defines.CHROME) {
|
||||
|
||||
@ -180,23 +180,6 @@ pdfjs-printing-not-ready = Warning: The PDF is not fully loaded for printing.
|
||||
|
||||
## Tooltips and alt text for side panel toolbar buttons
|
||||
|
||||
pdfjs-toggle-sidebar-button =
|
||||
.title = Toggle Sidebar
|
||||
pdfjs-toggle-sidebar-notification-button =
|
||||
.title = Toggle Sidebar (document contains outline/attachments/layers)
|
||||
pdfjs-toggle-sidebar-button-label = Toggle Sidebar
|
||||
pdfjs-document-outline-button =
|
||||
.title = Show Document Outline (double-click to expand/collapse all items)
|
||||
pdfjs-document-outline-button-label = Document Outline
|
||||
pdfjs-attachments-button =
|
||||
.title = Show Attachments
|
||||
pdfjs-attachments-button-label = Attachments
|
||||
pdfjs-layers-button =
|
||||
.title = Show Layers (double-click to reset all layers to the default state)
|
||||
pdfjs-layers-button-label = Layers
|
||||
pdfjs-thumbs-button =
|
||||
.title = Show Thumbnails
|
||||
pdfjs-thumbs-button-label = Thumbnails
|
||||
pdfjs-current-outline-item-button =
|
||||
.title = Find Current Outline Item
|
||||
pdfjs-current-outline-item-button-label = Current Outline Item
|
||||
@ -702,3 +685,85 @@ pdfjs-editor-edit-comment-dialog-cancel-button = Cancel
|
||||
|
||||
pdfjs-editor-add-comment-button =
|
||||
.title = Add comment
|
||||
|
||||
## The view manager is a sidebar displaying different views:
|
||||
## - thumbnails;
|
||||
## - outline;
|
||||
## - attachments;
|
||||
## - layers.
|
||||
## The thumbnails view is used to edit the pdf: remove/insert pages, ...
|
||||
|
||||
pdfjs-toggle-views-manager-button =
|
||||
.title = Toggle Sidebar
|
||||
pdfjs-toggle-views-manager-notification-button =
|
||||
.title = Toggle Sidebar (document contains thumbnails/outline/attachments/layers)
|
||||
pdfjs-toggle-views-manager-button-label = Toggle Sidebar
|
||||
|
||||
pdfjs-views-manager-sidebar =
|
||||
.aria-label = Sidebar
|
||||
pdfjs-views-manager-view-selector-button =
|
||||
.title = Views
|
||||
pdfjs-views-manager-view-selector-button-label = Views
|
||||
pdfjs-views-manager-pages-title = Pages
|
||||
pdfjs-views-manager-outlines-title = Document outline
|
||||
pdfjs-views-manager-attachments-title = Attachments
|
||||
pdfjs-views-manager-layers-title = Layers
|
||||
|
||||
pdfjs-views-manager-pages-option-label = Pages
|
||||
pdfjs-views-manager-outlines-option-label = Document outline
|
||||
pdfjs-views-manager-attachments-option-label = Attachments
|
||||
pdfjs-views-manager-layers-option-label = Layers
|
||||
|
||||
pdfjs-views-manager-add-file-button =
|
||||
.title = Add file
|
||||
pdfjs-views-manager-add-file-button-label = Add file
|
||||
|
||||
# Variables:
|
||||
# $count (Number) - the number of selected pages.
|
||||
pdfjs-views-manager-pages-status-action-label =
|
||||
{ $count ->
|
||||
[one] { $count } selected
|
||||
*[other] { $count } selected
|
||||
}
|
||||
pdfjs-views-manager-pages-status-none-action-label = Select pages
|
||||
pdfjs-views-manager-pages-status-action-button-label = Manage
|
||||
pdfjs-views-manager-pages-status-copy-button-label = Copy
|
||||
pdfjs-views-manager-pages-status-cut-button-label = Cut
|
||||
pdfjs-views-manager-pages-status-delete-button-label = Delete
|
||||
pdfjs-views-manager-pages-status-save-as-button-label = Save as…
|
||||
|
||||
# Variables:
|
||||
# $count (Number) - the number of selected pages to be cut.
|
||||
pdfjs-views-manager-status-undo-cut-label =
|
||||
{ $count ->
|
||||
[one] 1 page cut
|
||||
*[other] { $count } pages cut
|
||||
}
|
||||
|
||||
# Variables:
|
||||
# $count (Number) - the number of selected pages to be copied.
|
||||
pdfjs-views-manager-pages-status-undo-copy-label =
|
||||
{ $count ->
|
||||
[one] 1 page copied
|
||||
*[other] { $count } pages copied
|
||||
}
|
||||
|
||||
# Variables:
|
||||
# $count (Number) - the number of selected pages to be deleted.
|
||||
pdfjs-views-manager-pages-status-undo-delete-label =
|
||||
{ $count ->
|
||||
[one] 1 page deleted
|
||||
*[other] { $count } pages deleted
|
||||
}
|
||||
|
||||
pdfjs-views-manager-pages-status-waiting-ready-label = Getting your file ready…
|
||||
pdfjs-views-manager-pages-status-waiting-uploading-label = Uploading file…
|
||||
|
||||
pdfjs-views-manager-status-warning-cut-label = Couldn’t cut. Refresh page and try again.
|
||||
pdfjs-views-manager-status-warning-copy-label = Couldn’t copy. Refresh page and try again.
|
||||
pdfjs-views-manager-status-warning-delete-label = Couldn’t delete. Refresh page and try again.
|
||||
pdfjs-views-manager-status-warning-save-label = Couldn’t save. Refresh page and try again.
|
||||
pdfjs-views-manager-status-undo-button-label = Undo
|
||||
pdfjs-views-manager-status-close-button =
|
||||
.title = Close
|
||||
pdfjs-views-manager-status-close-button-label = Close
|
||||
|
||||
@ -1,11 +1,23 @@
|
||||
import { awaitPromise, closePages, loadAndWait } from "./test_utils.mjs";
|
||||
import {
|
||||
awaitPromise,
|
||||
closePages,
|
||||
kbFocusNext,
|
||||
loadAndWait,
|
||||
} from "./test_utils.mjs";
|
||||
|
||||
function waitForThumbnailVisible(page, pageNum) {
|
||||
return page.waitForSelector(
|
||||
`.thumbnailImage[data-l10n-args='{"page":${pageNum}}']`,
|
||||
{ visible: true }
|
||||
);
|
||||
}
|
||||
|
||||
describe("PDF Thumbnail View", () => {
|
||||
describe("Works without errors", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("tracemonkey.pdf", "#sidebarToggleButton");
|
||||
pages = await loadAndWait("tracemonkey.pdf", "#viewsManagerToggleButton");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
@ -15,14 +27,12 @@ describe("PDF Thumbnail View", () => {
|
||||
it("should render thumbnails without errors", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await page.click("#sidebarToggleButton");
|
||||
await page.click("#viewsManagerToggleButton");
|
||||
|
||||
const thumbSelector = "#thumbnailView .thumbnailImage";
|
||||
const thumbSelector = "#thumbnailsView .thumbnailImage";
|
||||
await page.waitForSelector(thumbSelector, { visible: true });
|
||||
|
||||
await page.waitForSelector(
|
||||
"#thumbnailView .thumbnail:not(.missingThumbnailImage)"
|
||||
);
|
||||
await waitForThumbnailVisible(page, 1);
|
||||
|
||||
const src = await page.$eval(thumbSelector, el => el.src);
|
||||
expect(src)
|
||||
@ -37,7 +47,7 @@ describe("PDF Thumbnail View", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("tracemonkey.pdf", "#sidebarToggleButton");
|
||||
pages = await loadAndWait("tracemonkey.pdf", "#viewsManagerToggleButton");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
@ -48,7 +58,7 @@ describe("PDF Thumbnail View", () => {
|
||||
const handle = await page.evaluateHandle(
|
||||
num => [
|
||||
new Promise(resolve => {
|
||||
const container = document.getElementById("thumbnailView");
|
||||
const container = document.getElementById("viewsManagerContent");
|
||||
container.addEventListener("scrollend", resolve, { once: true });
|
||||
// eslint-disable-next-line no-undef
|
||||
PDFViewerApplication.pdfLinkService.goToPage(num);
|
||||
@ -62,13 +72,15 @@ describe("PDF Thumbnail View", () => {
|
||||
it("should scroll the view", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await page.click("#sidebarToggleButton");
|
||||
await page.click("#viewsManagerToggleButton");
|
||||
|
||||
await waitForThumbnailVisible(page, 1);
|
||||
|
||||
for (const pageNum of [14, 1, 13, 2]) {
|
||||
await goToPage(page, pageNum);
|
||||
const thumbSelector = `.thumbnailImage[data-l10n-args='{"page":${pageNum}}']`;
|
||||
await page.waitForSelector(
|
||||
`.thumbnail:has(${thumbSelector}).selected`,
|
||||
`.thumbnail ${thumbSelector}[aria-current="page"]`,
|
||||
{ visible: true }
|
||||
);
|
||||
const src = await page.$eval(thumbSelector, el => el.src);
|
||||
@ -80,4 +92,106 @@ describe("PDF Thumbnail View", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("The view is accessible with the keyboard", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("tracemonkey.pdf", "#viewsManagerToggleButton");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
async function isElementFocused(page, selector) {
|
||||
await page.waitForSelector(selector, { visible: true });
|
||||
|
||||
return page.$eval(selector, el => el === document.activeElement);
|
||||
}
|
||||
|
||||
it("should navigate with the keyboard", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await page.click("#viewsManagerToggleButton");
|
||||
|
||||
await waitForThumbnailVisible(page, 1);
|
||||
await waitForThumbnailVisible(page, 2);
|
||||
await waitForThumbnailVisible(page, 3);
|
||||
|
||||
await kbFocusNext(page);
|
||||
expect(await isElementFocused(page, "#viewsManagerSelectorButton"))
|
||||
.withContext(`In ${browserName}`)
|
||||
.toBe(true);
|
||||
|
||||
await kbFocusNext(page);
|
||||
expect(
|
||||
await isElementFocused(
|
||||
page,
|
||||
`#thumbnailsView .thumbnailImage[data-l10n-args='{"page":1}']`
|
||||
)
|
||||
)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toBe(true);
|
||||
|
||||
await page.keyboard.press("ArrowDown");
|
||||
expect(
|
||||
await isElementFocused(
|
||||
page,
|
||||
`#thumbnailsView .thumbnailImage[data-l10n-args='{"page":2}']`
|
||||
)
|
||||
)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toBe(true);
|
||||
|
||||
await page.keyboard.press("ArrowUp");
|
||||
expect(
|
||||
await isElementFocused(
|
||||
page,
|
||||
`#thumbnailsView .thumbnailImage[data-l10n-args='{"page":1}']`
|
||||
)
|
||||
)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toBe(true);
|
||||
|
||||
await page.keyboard.press("ArrowDown");
|
||||
await page.keyboard.press("ArrowDown");
|
||||
expect(
|
||||
await isElementFocused(
|
||||
page,
|
||||
`#thumbnailsView .thumbnailImage[data-l10n-args='{"page":3}']`
|
||||
)
|
||||
)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toBe(true);
|
||||
await page.keyboard.press("Enter");
|
||||
const currentPage = await page.$eval(
|
||||
"#pageNumber",
|
||||
el => el.valueAsNumber
|
||||
);
|
||||
expect(currentPage).withContext(`In ${browserName}`).toBe(3);
|
||||
|
||||
await page.keyboard.press("End");
|
||||
expect(
|
||||
await isElementFocused(
|
||||
page,
|
||||
`#thumbnailsView .thumbnailImage[data-l10n-args='{"page":14}']`
|
||||
)
|
||||
)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toBe(true);
|
||||
|
||||
await page.keyboard.press("Home");
|
||||
expect(
|
||||
await isElementFocused(
|
||||
page,
|
||||
`#thumbnailsView .thumbnailImage[data-l10n-args='{"page":1}']`
|
||||
)
|
||||
)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toBe(true);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -40,12 +40,12 @@
|
||||
"web-pdf_layer_viewer": "../../web/pdf_layer_viewer.js",
|
||||
"web-pdf_outline_viewer": "../../web/pdf_outline_viewer.js",
|
||||
"web-pdf_presentation_mode": "../../web/pdf_presentation_mode.js",
|
||||
"web-pdf_sidebar": "../../web/pdf_sidebar.js",
|
||||
"web-pdf_thumbnail_viewer": "../../web/pdf_thumbnail_viewer.js",
|
||||
"web-preferences": "../../web/genericcom.js",
|
||||
"web-print_service": "../../web/pdf_print_service.js",
|
||||
"web-secondary_toolbar": "../../web/secondary_toolbar.js",
|
||||
"web-toolbar": "../../web/toolbar.js"
|
||||
"web-views_manager": "../../web/views_manager.js"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
46
web/app.js
@ -88,7 +88,6 @@ import { PDFPresentationMode } from "web-pdf_presentation_mode";
|
||||
import { PDFPrintServiceFactory } from "web-print_service";
|
||||
import { PDFRenderingQueue } from "./pdf_rendering_queue.js";
|
||||
import { PDFScriptingManager } from "./pdf_scripting_manager.js";
|
||||
import { PDFSidebar } from "web-pdf_sidebar";
|
||||
import { PdfTextExtractor } from "./pdf_text_extractor.js";
|
||||
import { PDFThumbnailViewer } from "web-pdf_thumbnail_viewer";
|
||||
import { PDFViewer } from "./pdf_viewer.js";
|
||||
@ -97,6 +96,7 @@ import { SecondaryToolbar } from "web-secondary_toolbar";
|
||||
import { SignatureManager } from "web-signature_manager";
|
||||
import { Toolbar } from "web-toolbar";
|
||||
import { ViewHistory } from "./view_history.js";
|
||||
import { ViewsManager } from "web-views_manager";
|
||||
|
||||
const FORCE_PAGES_LOADED_TIMEOUT = 10000; // ms
|
||||
|
||||
@ -134,8 +134,8 @@ const PDFViewerApplication = {
|
||||
pdfTextExtractor: null,
|
||||
/** @type {PDFHistory} */
|
||||
pdfHistory: null,
|
||||
/** @type {PDFSidebar} */
|
||||
pdfSidebar: null,
|
||||
/** @type {ViewsManager} */
|
||||
viewsManager: null,
|
||||
/** @type {PDFOutlineViewer} */
|
||||
pdfOutlineViewer: null,
|
||||
/** @type {PDFAttachmentViewer} */
|
||||
@ -591,9 +591,9 @@ const PDFViewerApplication = {
|
||||
linkService.setViewer(pdfViewer);
|
||||
pdfScriptingManager.setViewer(pdfViewer);
|
||||
|
||||
if (appConfig.sidebar?.thumbnailView) {
|
||||
if (appConfig.viewsManager?.thumbnailsView) {
|
||||
this.pdfThumbnailViewer = new PDFThumbnailViewer({
|
||||
container: appConfig.sidebar.thumbnailView,
|
||||
container: appConfig.viewsManager.thumbnailsView,
|
||||
eventBus,
|
||||
renderingQueue,
|
||||
linkService,
|
||||
@ -729,9 +729,9 @@ const PDFViewerApplication = {
|
||||
);
|
||||
}
|
||||
|
||||
if (appConfig.sidebar?.outlineView) {
|
||||
if (appConfig.viewsManager?.outlinesView) {
|
||||
this.pdfOutlineViewer = new PDFOutlineViewer({
|
||||
container: appConfig.sidebar.outlineView,
|
||||
container: appConfig.viewsManager.outlinesView,
|
||||
eventBus,
|
||||
l10n,
|
||||
linkService,
|
||||
@ -739,31 +739,31 @@ const PDFViewerApplication = {
|
||||
});
|
||||
}
|
||||
|
||||
if (appConfig.sidebar?.attachmentsView) {
|
||||
if (appConfig.viewsManager?.attachmentsView) {
|
||||
this.pdfAttachmentViewer = new PDFAttachmentViewer({
|
||||
container: appConfig.sidebar.attachmentsView,
|
||||
container: appConfig.viewsManager.attachmentsView,
|
||||
eventBus,
|
||||
l10n,
|
||||
downloadManager,
|
||||
});
|
||||
}
|
||||
|
||||
if (appConfig.sidebar?.layersView) {
|
||||
if (appConfig.viewsManager?.layersView) {
|
||||
this.pdfLayerViewer = new PDFLayerViewer({
|
||||
container: appConfig.sidebar.layersView,
|
||||
container: appConfig.viewsManager.layersView,
|
||||
eventBus,
|
||||
l10n,
|
||||
});
|
||||
}
|
||||
|
||||
if (appConfig.sidebar) {
|
||||
this.pdfSidebar = new PDFSidebar({
|
||||
elements: appConfig.sidebar,
|
||||
if (appConfig.viewsManager) {
|
||||
this.viewsManager = new ViewsManager({
|
||||
elements: appConfig.viewsManager,
|
||||
eventBus,
|
||||
l10n,
|
||||
});
|
||||
this.pdfSidebar.onToggled = this.forceRendering.bind(this);
|
||||
this.pdfSidebar.onUpdateThumbnails = () => {
|
||||
this.viewsManager.onToggled = this.forceRendering.bind(this);
|
||||
this.viewsManager.onUpdateThumbnails = () => {
|
||||
// Use the rendered pages to set the corresponding thumbnail images.
|
||||
for (const pageView of pdfViewer.getCachedPageViews()) {
|
||||
if (pageView.renderingState === RenderingStates.FINISHED) {
|
||||
@ -1170,7 +1170,7 @@ const PDFViewerApplication = {
|
||||
);
|
||||
|
||||
this.setTitle();
|
||||
this.pdfSidebar?.reset();
|
||||
this.viewsManager?.reset();
|
||||
this.pdfOutlineViewer?.reset();
|
||||
this.pdfAttachmentViewer?.reset();
|
||||
this.pdfLayerViewer?.reset();
|
||||
@ -1910,7 +1910,7 @@ const PDFViewerApplication = {
|
||||
}
|
||||
};
|
||||
this.isInitialViewSet = true;
|
||||
this.pdfSidebar?.setInitialView(sidebarView);
|
||||
this.viewsManager?.setInitialView(sidebarView);
|
||||
|
||||
setViewerModes(scrollMode, spreadMode);
|
||||
|
||||
@ -1959,7 +1959,7 @@ const PDFViewerApplication = {
|
||||
forceRendering() {
|
||||
this.pdfRenderingQueue.printing = !!this.printService;
|
||||
this.pdfRenderingQueue.isThumbnailViewEnabled =
|
||||
this.pdfSidebar?.visibleView === SidebarView.THUMBS;
|
||||
this.viewsManager?.visibleView === SidebarView.THUMBS;
|
||||
this.pdfRenderingQueue.renderHighestPriority();
|
||||
},
|
||||
|
||||
@ -2480,7 +2480,7 @@ function onPageRendered({ pageNumber, isDetailView, error }) {
|
||||
}
|
||||
|
||||
// Use the rendered page to set the corresponding thumbnail image.
|
||||
if (!isDetailView && this.pdfSidebar?.visibleView === SidebarView.THUMBS) {
|
||||
if (!isDetailView && this.viewsManager?.visibleView === SidebarView.THUMBS) {
|
||||
const pageView = this.pdfViewer.getPageView(/* index = */ pageNumber - 1);
|
||||
const thumbnailView = this.pdfThumbnailViewer?.getThumbnail(
|
||||
/* index = */ pageNumber - 1
|
||||
@ -2519,7 +2519,7 @@ function onPageMode({ mode }) {
|
||||
console.error('Invalid "pagemode" hash parameter: ' + mode);
|
||||
return;
|
||||
}
|
||||
this.pdfSidebar?.switchView(view, /* forceOpen = */ true);
|
||||
this.viewsManager?.switchView(view, /* forceOpen = */ true);
|
||||
}
|
||||
|
||||
function onNamedAction(evt) {
|
||||
@ -2713,7 +2713,7 @@ function onPageChanging({ pageNumber, pageLabel }) {
|
||||
this.toolbar?.setPageNumber(pageNumber, pageLabel);
|
||||
this.secondaryToolbar?.setPageNumber(pageNumber);
|
||||
|
||||
if (this.pdfSidebar?.visibleView === SidebarView.THUMBS) {
|
||||
if (this.viewsManager?.visibleView === SidebarView.THUMBS) {
|
||||
this.pdfThumbnailViewer?.scrollThumbnailIntoView(pageNumber);
|
||||
}
|
||||
|
||||
@ -3133,7 +3133,7 @@ function onKeyDown(evt) {
|
||||
break;
|
||||
|
||||
case 115: // F4
|
||||
this.pdfSidebar?.toggle();
|
||||
this.viewsManager?.toggle();
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
5
web/images/checkmark.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.79253 0.14683C10.2086 0.418844 10.3253 0.976607 10.0533 1.39263L4.56097 9.79263C4.4081 10.0264 4.15533 10.176 3.87682 10.1974C3.5983 10.2189 3.32561 10.1098 3.13874 9.90217L0.231043 6.6714C-0.10147 6.30194 -0.0715196 5.73288 0.29794 5.40037C0.667399 5.06786 1.23646 5.09781 1.56897 5.46727L3.69438 7.82883L8.54674 0.407579C8.81875 -0.00844234 9.37651 -0.125183 9.79253 0.14683Z" fill="black"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 565 B |
3
web/images/pages_closeButton.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14 3.06055L9.06055 8L14 12.9385L12.9395 14L8 9.06055L3.06152 14L2.00098 12.9395L6.93848 8L2 3.06152L3.06055 2.00098L7.99902 6.93945L12.9385 2L14 3.06055Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 284 B |
7
web/images/pages_selected.svg
Normal file
@ -0,0 +1,7 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<mask id="mask">
|
||||
<rect width="16" height="16" rx="2" fill="white"/>
|
||||
<path d="M12 8L4 8" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</mask>
|
||||
<rect width="16" height="16" fill="black" mask="url(#mask)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 355 B |
3
web/images/pages_viewArrow.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="8" height="10" viewBox="0 0 8 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.23336 7.46639L7.84736 3.85339C7.89401 3.8071 7.93104 3.75203 7.9563 3.69136C7.98157 3.63069 7.99458 3.56562 7.99458 3.49989C7.99458 3.43417 7.98157 3.3691 7.9563 3.30843C7.93104 3.24776 7.89401 3.19269 7.84736 3.14639C7.75359 3.05266 7.62644 3 7.49386 3C7.36127 3 7.23412 3.05266 7.14036 3.14639L3.99236 6.29339L0.847356 3.14739C0.753055 3.05631 0.626754 3.00592 0.495655 3.00706C0.364557 3.0082 0.239151 3.06078 0.146447 3.15348C0.0537425 3.24619 0.00115811 3.37159 1.89013e-05 3.50269C-0.00112031 3.63379 0.0492769 3.76009 0.140356 3.85439L3.75236 7.46739L4.23336 7.46639Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 705 B |
3
web/images/pages_viewButton.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3 7H5C5.53043 7 6.03914 6.78929 6.41421 6.41421C6.78929 6.03914 7 5.53043 7 5V3C7 2.46957 6.78929 1.96086 6.41421 1.58579C6.03914 1.21071 5.53043 1 5 1H3C2.46957 1 1.96086 1.21071 1.58579 1.58579C1.21071 1.96086 1 2.46957 1 3V5C1 5.53043 1.21071 6.03914 1.58579 6.41421C1.96086 6.78929 2.46957 7 3 7ZM11 7H13C13.5304 7 14.0391 6.78929 14.4142 6.41421C14.7893 6.03914 15 5.53043 15 5V3C15 2.46957 14.7893 1.96086 14.4142 1.58579C14.0391 1.21071 13.5304 1 13 1H11C10.4696 1 9.96086 1.21071 9.58579 1.58579C9.21071 1.96086 9 2.46957 9 3V5C9 5.53043 9.21071 6.03914 9.58579 6.41421C9.96086 6.78929 10.4696 7 11 7ZM5 15H3C2.46957 15 1.96086 14.7893 1.58579 14.4142C1.21071 14.0391 1 13.5304 1 13V11C1 10.4696 1.21071 9.96086 1.58579 9.58579C1.96086 9.21071 2.46957 9 3 9H5C5.53043 9 6.03914 9.21071 6.41421 9.58579C6.78929 9.96086 7 10.4696 7 11V13C7 13.5304 6.78929 14.0391 6.41421 14.4142C6.03914 14.7893 5.53043 15 5 15ZM11 15H13C13.5304 15 14.0391 14.7893 14.4142 14.4142C14.7893 14.0391 15 13.5304 15 13V11C15 10.4696 14.7893 9.96086 14.4142 9.58579C14.0391 9.21071 13.5304 9 13 9H11C10.4696 9 9.96086 9.21071 9.58579 9.58579C9.21071 9.96086 9 10.4696 9 11V13C9 13.5304 9.21071 14.0391 9.58579 14.4142C9.96086 14.7893 10.4696 15 11 15Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
@ -118,20 +118,18 @@ class PDFAttachmentViewer extends BaseTreeViewer {
|
||||
}
|
||||
|
||||
const fragment = document.createDocumentFragment();
|
||||
const ul = document.createElement("ul");
|
||||
fragment.append(ul);
|
||||
let attachmentsCount = 0;
|
||||
for (const name in attachments) {
|
||||
const item = attachments[name];
|
||||
|
||||
const div = document.createElement("div");
|
||||
div.className = "treeItem";
|
||||
|
||||
const li = document.createElement("li");
|
||||
ul.append(li);
|
||||
const element = document.createElement("a");
|
||||
li.append(element);
|
||||
this._bindLink(element, item);
|
||||
element.textContent = this._normalizeTextContent(item.filename);
|
||||
|
||||
div.append(element);
|
||||
|
||||
fragment.append(div);
|
||||
attachmentsCount++;
|
||||
}
|
||||
|
||||
|
||||
@ -33,7 +33,7 @@ import { RenderingStates } from "./ui_utils.js";
|
||||
|
||||
const DRAW_UPSCALE_FACTOR = 2; // See comment in `PDFThumbnailView.draw` below.
|
||||
const MAX_NUM_SCALING_STEPS = 3;
|
||||
const THUMBNAIL_WIDTH = 98; // px
|
||||
const THUMBNAIL_WIDTH = 126; // px
|
||||
|
||||
/**
|
||||
* @typedef {Object} PDFThumbnailViewOptions
|
||||
@ -119,26 +119,22 @@ class PDFThumbnailView {
|
||||
this.renderingState = RenderingStates.INITIAL;
|
||||
this.resume = null;
|
||||
|
||||
const anchor = (this.anchor = document.createElement("a"));
|
||||
anchor.href = linkService.getAnchorUrl(`#page=${id}`);
|
||||
anchor.setAttribute("data-l10n-id", "pdfjs-thumb-page-title");
|
||||
anchor.setAttribute("data-l10n-args", this.#pageL10nArgs);
|
||||
anchor.onclick = () => {
|
||||
linkService.goToPage(id);
|
||||
return false;
|
||||
};
|
||||
const imageContainer = (this.div = document.createElement("div"));
|
||||
imageContainer.className = "thumbnail";
|
||||
imageContainer.setAttribute("page-number", this.#pageNumber);
|
||||
|
||||
const div = (this.div = document.createElement("div"));
|
||||
div.classList.add("thumbnail", "missingThumbnailImage");
|
||||
div.setAttribute("data-page-number", this.id);
|
||||
this.#updateDims();
|
||||
const checkbox = (this.checkbox = document.createElement("input"));
|
||||
checkbox.type = "checkbox";
|
||||
checkbox.tabIndex = -1;
|
||||
|
||||
const image = (this.image = document.createElement("img"));
|
||||
image.className = "thumbnailImage";
|
||||
image.classList.add("thumbnailImage", "missingThumbnailImage");
|
||||
image.role = "button";
|
||||
image.tabIndex = -1;
|
||||
this.#updateDims();
|
||||
|
||||
div.append(image);
|
||||
anchor.append(div);
|
||||
container.append(anchor);
|
||||
imageContainer.append(checkbox, image);
|
||||
container.append(imageContainer);
|
||||
}
|
||||
|
||||
#updateDims() {
|
||||
@ -149,7 +145,7 @@ class PDFThumbnailView {
|
||||
const canvasHeight = (this.canvasHeight = (canvasWidth / ratio) | 0);
|
||||
this.scale = canvasWidth / width;
|
||||
|
||||
this.div.style.height = `${canvasHeight}px`;
|
||||
this.image.style.height = `${canvasHeight}px`;
|
||||
}
|
||||
|
||||
setPdfPage(pdfPage) {
|
||||
@ -172,7 +168,7 @@ class PDFThumbnailView {
|
||||
image.removeAttribute("data-l10n-id");
|
||||
image.removeAttribute("data-l10n-args");
|
||||
image.src = "";
|
||||
this.div.classList.add("missingThumbnailImage");
|
||||
this.image.classList.add("missingThumbnailImage");
|
||||
}
|
||||
}
|
||||
|
||||
@ -188,6 +184,16 @@ class PDFThumbnailView {
|
||||
this.reset();
|
||||
}
|
||||
|
||||
toggleCurrent(isCurrent) {
|
||||
if (isCurrent) {
|
||||
this.image.ariaCurrent = "page";
|
||||
this.image.tabIndex = 0;
|
||||
} else {
|
||||
this.image.ariaCurrent = false;
|
||||
this.image.tabIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PLEASE NOTE: Most likely you want to use the `this.reset()` method,
|
||||
* rather than calling this one directly.
|
||||
@ -238,7 +244,7 @@ class PDFThumbnailView {
|
||||
image.src = URL.createObjectURL(blob);
|
||||
image.setAttribute("data-l10n-id", "pdfjs-thumb-page-canvas");
|
||||
image.setAttribute("data-l10n-args", this.#pageL10nArgs);
|
||||
this.div.classList.remove("missingThumbnailImage");
|
||||
image.classList.remove("missingThumbnailImage");
|
||||
if (!FeatureTest.isOffscreenCanvasSupported) {
|
||||
// Clean up the canvas element since it is no longer needed.
|
||||
reducedCanvas.width = reducedCanvas.height = 0;
|
||||
@ -434,6 +440,10 @@ class PDFThumbnailView {
|
||||
return JSON.stringify({ page: this.pageLabel ?? this.id });
|
||||
}
|
||||
|
||||
get #pageNumber() {
|
||||
return this.pageLabel ?? this.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string|null} label
|
||||
*/
|
||||
|
||||
@ -26,13 +26,14 @@ import {
|
||||
RenderingStates,
|
||||
watchScroll,
|
||||
} from "./ui_utils.js";
|
||||
import { MathClamp, stopEvent } from "pdfjs-lib";
|
||||
import { PDFThumbnailView } from "./pdf_thumbnail_view.js";
|
||||
|
||||
const THUMBNAIL_SELECTED_CLASS = "selected";
|
||||
const SCROLL_OPTIONS = {
|
||||
behavior: "instant",
|
||||
container: "nearest",
|
||||
block: "nearest",
|
||||
inline: "nearest",
|
||||
container: "nearest",
|
||||
};
|
||||
|
||||
/**
|
||||
@ -75,6 +76,7 @@ class PDFThumbnailViewer {
|
||||
abortSignal,
|
||||
enableHWA,
|
||||
}) {
|
||||
this.scrollableContainer = container.parentElement;
|
||||
this.container = container;
|
||||
this.eventBus = eventBus;
|
||||
this.linkService = linkService;
|
||||
@ -85,11 +87,12 @@ class PDFThumbnailViewer {
|
||||
this.enableHWA = enableHWA || false;
|
||||
|
||||
this.scroll = watchScroll(
|
||||
this.container,
|
||||
this.scrollableContainer,
|
||||
this.#scrollUpdated.bind(this),
|
||||
abortSignal
|
||||
);
|
||||
this.#resetView();
|
||||
this.#addEventListeners();
|
||||
}
|
||||
|
||||
#scrollUpdated() {
|
||||
@ -102,7 +105,7 @@ class PDFThumbnailViewer {
|
||||
|
||||
#getVisibleThumbs() {
|
||||
return getVisibleElements({
|
||||
scrollEl: this.container,
|
||||
scrollEl: this.scrollableContainer,
|
||||
views: this._thumbnails,
|
||||
});
|
||||
}
|
||||
@ -120,10 +123,9 @@ class PDFThumbnailViewer {
|
||||
|
||||
if (pageNumber !== this._currentPageNumber) {
|
||||
const prevThumbnailView = this._thumbnails[this._currentPageNumber - 1];
|
||||
// Remove the highlight from the previous thumbnail...
|
||||
prevThumbnailView.div.classList.remove(THUMBNAIL_SELECTED_CLASS);
|
||||
// ... and add the highlight to the new thumbnail.
|
||||
thumbnailView.div.classList.add(THUMBNAIL_SELECTED_CLASS);
|
||||
prevThumbnailView.toggleCurrent(/* isCurrent = */ false);
|
||||
thumbnailView.toggleCurrent(/* isCurrent = */ true);
|
||||
this._currentPageNumber = pageNumber;
|
||||
}
|
||||
const { first, last, views } = this.#getVisibleThumbs();
|
||||
|
||||
@ -236,7 +238,7 @@ class PDFThumbnailViewer {
|
||||
|
||||
// Ensure that the current thumbnail is always highlighted on load.
|
||||
const thumbnailView = this._thumbnails[this._currentPageNumber - 1];
|
||||
thumbnailView.div.classList.add(THUMBNAIL_SELECTED_CLASS);
|
||||
thumbnailView.toggleCurrent(/* isCurrent = */ true);
|
||||
this.container.append(fragment);
|
||||
})
|
||||
.catch(reason => {
|
||||
@ -320,6 +322,107 @@ class PDFThumbnailViewer {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#addEventListeners() {
|
||||
this.container.addEventListener("keydown", e => {
|
||||
switch (e.key) {
|
||||
case "ArrowLeft":
|
||||
this.#goToNextItem(e.target, false, true);
|
||||
stopEvent(e);
|
||||
break;
|
||||
case "ArrowRight":
|
||||
this.#goToNextItem(e.target, true, true);
|
||||
stopEvent(e);
|
||||
break;
|
||||
case "ArrowDown":
|
||||
this.#goToNextItem(e.target, true, false);
|
||||
stopEvent(e);
|
||||
break;
|
||||
case "ArrowUp":
|
||||
this.#goToNextItem(e.target, false, false);
|
||||
stopEvent(e);
|
||||
break;
|
||||
case "Home":
|
||||
this._thumbnails[0].image.focus();
|
||||
stopEvent(e);
|
||||
break;
|
||||
case "End":
|
||||
this._thumbnails.at(-1).image.focus();
|
||||
stopEvent(e);
|
||||
break;
|
||||
case "Enter":
|
||||
case " ":
|
||||
this.#goToPage(e);
|
||||
break;
|
||||
}
|
||||
});
|
||||
this.container.addEventListener("click", this.#goToPage.bind(this));
|
||||
}
|
||||
|
||||
#goToPage(e) {
|
||||
const { target } = e;
|
||||
if (target.classList.contains("thumbnailImage")) {
|
||||
const pageNumber = parseInt(
|
||||
target.parentElement.getAttribute("page-number"),
|
||||
10
|
||||
);
|
||||
this.linkService.goToPage(pageNumber);
|
||||
stopEvent(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to the next/previous menu item.
|
||||
* @param {HTMLElement} element
|
||||
* @param {boolean} forward
|
||||
* @param {boolean} horizontal
|
||||
*/
|
||||
#goToNextItem(element, forward, horizontal) {
|
||||
let currentPageNumber = parseInt(
|
||||
element.parentElement.getAttribute("page-number"),
|
||||
10
|
||||
);
|
||||
if (isNaN(currentPageNumber)) {
|
||||
currentPageNumber = this._currentPageNumber;
|
||||
}
|
||||
|
||||
const increment = forward ? 1 : -1;
|
||||
let nextThumbnail;
|
||||
if (horizontal) {
|
||||
const nextPageNumber = MathClamp(
|
||||
currentPageNumber + increment,
|
||||
1,
|
||||
this._thumbnails.length + 1
|
||||
);
|
||||
nextThumbnail = this._thumbnails[nextPageNumber - 1];
|
||||
} else {
|
||||
const currentThumbnail = this._thumbnails[currentPageNumber - 1];
|
||||
const { x: currentX, y: currentY } =
|
||||
currentThumbnail.div.getBoundingClientRect();
|
||||
let firstWithDifferentY;
|
||||
for (
|
||||
let i = currentPageNumber - 1 + increment;
|
||||
i >= 0 && i < this._thumbnails.length;
|
||||
i += increment
|
||||
) {
|
||||
const thumbnail = this._thumbnails[i];
|
||||
const { x, y } = thumbnail.div.getBoundingClientRect();
|
||||
if (!firstWithDifferentY && y !== currentY) {
|
||||
firstWithDifferentY = thumbnail;
|
||||
}
|
||||
if (x === currentX) {
|
||||
nextThumbnail = thumbnail;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!nextThumbnail) {
|
||||
nextThumbnail = firstWithDifferentY;
|
||||
}
|
||||
}
|
||||
if (nextThumbnail) {
|
||||
nextThumbnail.image.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { PDFThumbnailViewer };
|
||||
|
||||
@ -19,9 +19,10 @@
|
||||
@import url(xfa_layer_builder.css);
|
||||
/* Ignored in GECKOVIEW: begin */
|
||||
@import url(annotation_editor_layer_builder.css);
|
||||
@import url(sidebar.css);
|
||||
@import url(menu.css);
|
||||
@import url(tree.css);
|
||||
@import url(views_manager.css);
|
||||
@import url(sidebar.css);
|
||||
/* Ignored in GECKOVIEW: end */
|
||||
|
||||
:root {
|
||||
|
||||
@ -24,10 +24,10 @@ const PDFFindBar = null;
|
||||
const PDFLayerViewer = null;
|
||||
const PDFOutlineViewer = null;
|
||||
const PDFPresentationMode = null;
|
||||
const PDFSidebar = null;
|
||||
const PDFThumbnailViewer = null;
|
||||
const SecondaryToolbar = null;
|
||||
const SignatureManager = null;
|
||||
const ViewsManager = null;
|
||||
|
||||
export {
|
||||
AltTextManager,
|
||||
@ -41,8 +41,8 @@ export {
|
||||
PDFLayerViewer,
|
||||
PDFOutlineViewer,
|
||||
PDFPresentationMode,
|
||||
PDFSidebar,
|
||||
PDFThumbnailViewer,
|
||||
SecondaryToolbar,
|
||||
SignatureManager,
|
||||
ViewsManager,
|
||||
};
|
||||
|
||||
@ -850,6 +850,13 @@ function toggleCheckedBtn(button, toggle, view = null) {
|
||||
view?.classList.toggle("hidden", !toggle);
|
||||
}
|
||||
|
||||
function toggleSelectedBtn(button, toggle, view = null) {
|
||||
button.classList.toggle("selected", toggle);
|
||||
button.setAttribute("aria-selected", toggle);
|
||||
|
||||
view?.classList.toggle("hidden", !toggle);
|
||||
}
|
||||
|
||||
function toggleExpandedBtn(button, toggle, view = null) {
|
||||
button.classList.toggle("toggled", toggle);
|
||||
button.setAttribute("aria-expanded", toggle);
|
||||
@ -916,6 +923,7 @@ export {
|
||||
TextLayerMode,
|
||||
toggleCheckedBtn,
|
||||
toggleExpandedBtn,
|
||||
toggleSelectedBtn,
|
||||
UNKNOWN_SCALE,
|
||||
VERTICAL_PADDING,
|
||||
watchScroll,
|
||||
|
||||
@ -80,13 +80,13 @@ See https://github.com/adobe-type-tools/cmap-resources
|
||||
"web-pdf_layer_viewer": "./stubs-geckoview.js",
|
||||
"web-pdf_outline_viewer": "./stubs-geckoview.js",
|
||||
"web-pdf_presentation_mode": "./stubs-geckoview.js",
|
||||
"web-pdf_sidebar": "./stubs-geckoview.js",
|
||||
"web-pdf_thumbnail_viewer": "./stubs-geckoview.js",
|
||||
"web-preferences": "./genericcom.js",
|
||||
"web-print_service": "./pdf_print_service.js",
|
||||
"web-secondary_toolbar": "./stubs-geckoview.js",
|
||||
"web-signature_manager": "./stubs-geckoview.js",
|
||||
"web-toolbar": "./toolbar-geckoview.js"
|
||||
"web-toolbar": "./toolbar-geckoview.js",
|
||||
"web-views_manager": "./stubs-geckoview.js"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
134
web/viewer.css
@ -69,11 +69,6 @@
|
||||
--field-color: light-dark(rgb(6 6 6), rgb(250 250 250));
|
||||
--field-bg-color: light-dark(rgb(255 255 255), rgb(64 64 68));
|
||||
--field-border-color: light-dark(rgb(187 187 188), rgb(115 115 115));
|
||||
--thumbnail-hover-color: light-dark(rgb(0 0 0 / 0.1), rgb(255 255 255 / 0.1));
|
||||
--thumbnail-selected-color: light-dark(
|
||||
rgb(0 0 0 / 0.2),
|
||||
rgb(255 255 255 / 0.2)
|
||||
);
|
||||
--doorhanger-bg-color: light-dark(rgb(255 255 255), #42414d);
|
||||
--doorhanger-border-color: light-dark(rgb(12 12 13 / 0.2), rgb(39 39 43));
|
||||
--doorhanger-hover-color: light-dark(rgb(12 12 13), rgb(249 249 250));
|
||||
@ -93,7 +88,7 @@
|
||||
--toolbarButton-editorStamp-icon: url(images/toolbarButton-editorStamp.svg);
|
||||
--toolbarButton-editorSignature-icon: url(images/toolbarButton-editorSignature.svg);
|
||||
--toolbarButton-menuArrow-icon: url(images/toolbarButton-menuArrow.svg);
|
||||
--toolbarButton-sidebarToggle-icon: url(images/toolbarButton-sidebarToggle.svg);
|
||||
--toolbarButton-viewsManagerToggle-icon: url(images/toolbarButton-viewsManagerToggle.svg);
|
||||
--toolbarButton-secondaryToolbarToggle-icon: url(images/toolbarButton-secondaryToolbarToggle.svg);
|
||||
--toolbarButton-pageUp-icon: url(images/toolbarButton-pageUp.svg);
|
||||
--toolbarButton-pageDown-icon: url(images/toolbarButton-pageDown.svg);
|
||||
@ -269,29 +264,6 @@ body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#sidebarContainer {
|
||||
position: absolute;
|
||||
inset-block: var(--toolbar-height) 0;
|
||||
inset-inline-start: calc(-1 * var(--sidebar-width));
|
||||
width: var(--sidebar-width);
|
||||
visibility: hidden;
|
||||
z-index: 1;
|
||||
font: message-box;
|
||||
border-top: 1px solid transparent;
|
||||
border-inline-end: var(--doorhanger-border-color-whcm);
|
||||
transition-property: inset-inline-start;
|
||||
transition-duration: var(--sidebar-transition-duration);
|
||||
transition-timing-function: var(--sidebar-transition-timing-function);
|
||||
}
|
||||
|
||||
#outerContainer:is(.sidebarMoving, .sidebarOpen) #sidebarContainer {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
#outerContainer.sidebarOpen #sidebarContainer {
|
||||
inset-inline-start: 0;
|
||||
}
|
||||
|
||||
#mainContainer {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
@ -323,11 +295,6 @@ body {
|
||||
transition-timing-function: var(--sidebar-transition-timing-function);
|
||||
}
|
||||
|
||||
#outerContainer.sidebarOpen #viewerContainer:not(.pdfPresentationMode) {
|
||||
inset-inline-start: var(--sidebar-width);
|
||||
transition-property: inset-inline-start;
|
||||
}
|
||||
|
||||
#sidebarContainer :is(input, button, select) {
|
||||
font: message-box;
|
||||
}
|
||||
@ -374,25 +341,6 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
#sidebarResizer {
|
||||
position: absolute;
|
||||
inset-block: 0;
|
||||
inset-inline-end: -6px;
|
||||
width: 6px;
|
||||
z-index: 200;
|
||||
cursor: ew-resize;
|
||||
}
|
||||
|
||||
#outerContainer.sidebarOpen #loadingBar {
|
||||
inset-inline-start: var(--sidebar-width);
|
||||
}
|
||||
|
||||
#outerContainer.sidebarResizing
|
||||
:is(#sidebarContainer, #viewerContainer, #loadingBar) {
|
||||
/* Improve responsiveness and avoid visual glitches when the sidebar is resized. */
|
||||
transition-duration: 0s;
|
||||
}
|
||||
|
||||
.doorHanger,
|
||||
.doorHangerRight {
|
||||
border-radius: 2px;
|
||||
@ -490,8 +438,8 @@ body {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#sidebarToggleButton::before {
|
||||
mask-image: var(--toolbarButton-sidebarToggle-icon);
|
||||
#viewsManagerToggleButton::before {
|
||||
mask-image: var(--toolbarButton-viewsManagerToggle-icon);
|
||||
transform: scaleX(var(--dir-factor));
|
||||
}
|
||||
|
||||
@ -677,80 +625,6 @@ body {
|
||||
inset-inline-end: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
#thumbnailView,
|
||||
#outlineView,
|
||||
#attachmentsView,
|
||||
#layersView {
|
||||
position: absolute;
|
||||
width: calc(100% - 8px);
|
||||
inset-block: 0;
|
||||
padding: 4px 4px 0;
|
||||
overflow: auto;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#thumbnailView {
|
||||
--thumbnail-width: 98px;
|
||||
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
width: calc(100% - 60px);
|
||||
padding: 10px 30px 0;
|
||||
|
||||
> a {
|
||||
width: auto;
|
||||
height: auto;
|
||||
|
||||
> .thumbnail {
|
||||
scroll-margin-block: 19px;
|
||||
width: var(--thumbnail-width);
|
||||
margin: 0 10px 5px;
|
||||
padding: 1px;
|
||||
border: 7px solid transparent;
|
||||
border-radius: 2px;
|
||||
|
||||
&.selected {
|
||||
border-color: var(--thumbnail-selected-color) !important;
|
||||
|
||||
> .thumbnailImage {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.missingThumbnailImage {
|
||||
border: 1px dashed rgb(132 132 132);
|
||||
padding: 7px;
|
||||
> .thumbnailImage {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
> .thumbnailImage {
|
||||
width: 100%;
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
|
||||
&:is(:active, :focus) {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
&:last-of-type > .thumbnail {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
&:focus > .thumbnail,
|
||||
.thumbnail:hover {
|
||||
border-color: var(--thumbnail-hover-color);
|
||||
|
||||
> .thumbnailImage {
|
||||
opacity: 0.95;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#outlineOptionsContainer {
|
||||
display: none;
|
||||
|
||||
@ -1559,7 +1433,7 @@ dialog :link {
|
||||
#sidebarContainer {
|
||||
background-color: var(--sidebar-narrow-bg-color);
|
||||
}
|
||||
#outerContainer.sidebarOpen #viewerContainer {
|
||||
#outerContainer.viewsManagerOpen #viewerContainer {
|
||||
inset-inline-start: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
257
web/viewer.html
@ -83,13 +83,13 @@ See https://github.com/adobe-type-tools/cmap-resources
|
||||
"web-pdf_layer_viewer": "./pdf_layer_viewer.js",
|
||||
"web-pdf_outline_viewer": "./pdf_outline_viewer.js",
|
||||
"web-pdf_presentation_mode": "./pdf_presentation_mode.js",
|
||||
"web-pdf_sidebar": "./pdf_sidebar.js",
|
||||
"web-pdf_thumbnail_viewer": "./pdf_thumbnail_viewer.js",
|
||||
"web-preferences": "./genericcom.js",
|
||||
"web-print_service": "./pdf_print_service.js",
|
||||
"web-secondary_toolbar": "./secondary_toolbar.js",
|
||||
"web-signature_manager": "./signature_manager.js",
|
||||
"web-toolbar": "./toolbar.js"
|
||||
"web-toolbar": "./toolbar.js",
|
||||
"web-views_manager": "./views_manager.js"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -101,105 +101,186 @@ See https://github.com/adobe-type-tools/cmap-resources
|
||||
<div id="outerContainer">
|
||||
<span id="viewer-alert" class="visuallyHidden" role="alert"></span>
|
||||
|
||||
<div id="sidebarContainer">
|
||||
<div id="toolbarSidebar" class="toolbarHorizontalGroup">
|
||||
<div id="toolbarSidebarLeft">
|
||||
<div id="sidebarViewButtons" class="toolbarHorizontalGroup toggled" role="radiogroup">
|
||||
<button
|
||||
id="viewThumbnail"
|
||||
class="toolbarButton toggled"
|
||||
type="button"
|
||||
tabindex="0"
|
||||
data-l10n-id="pdfjs-thumbs-button"
|
||||
role="radio"
|
||||
aria-checked="true"
|
||||
aria-controls="thumbnailView"
|
||||
>
|
||||
<span data-l10n-id="pdfjs-thumbs-button-label"></span>
|
||||
</button>
|
||||
<button
|
||||
id="viewOutline"
|
||||
class="toolbarButton"
|
||||
type="button"
|
||||
tabindex="0"
|
||||
data-l10n-id="pdfjs-document-outline-button"
|
||||
role="radio"
|
||||
aria-checked="false"
|
||||
aria-controls="outlineView"
|
||||
>
|
||||
<span data-l10n-id="pdfjs-document-outline-button-label"></span>
|
||||
</button>
|
||||
<button
|
||||
id="viewAttachments"
|
||||
class="toolbarButton"
|
||||
type="button"
|
||||
tabindex="0"
|
||||
data-l10n-id="pdfjs-attachments-button"
|
||||
role="radio"
|
||||
aria-checked="false"
|
||||
aria-controls="attachmentsView"
|
||||
>
|
||||
<span data-l10n-id="pdfjs-attachments-button-label"></span>
|
||||
</button>
|
||||
<button
|
||||
id="viewLayers"
|
||||
class="toolbarButton"
|
||||
type="button"
|
||||
tabindex="0"
|
||||
data-l10n-id="pdfjs-layers-button"
|
||||
role="radio"
|
||||
aria-checked="false"
|
||||
aria-controls="layersView"
|
||||
>
|
||||
<span data-l10n-id="pdfjs-layers-button-label"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="toolbarSidebarRight">
|
||||
<div id="outlineOptionsContainer" class="toolbarHorizontalGroup">
|
||||
<div class="verticalToolbarSeparator"></div>
|
||||
|
||||
<button
|
||||
id="currentOutlineItem"
|
||||
class="toolbarButton"
|
||||
type="button"
|
||||
disabled="disabled"
|
||||
tabindex="0"
|
||||
data-l10n-id="pdfjs-current-outline-item-button"
|
||||
>
|
||||
<span data-l10n-id="pdfjs-current-outline-item-button-label"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="sidebarContent">
|
||||
<div id="thumbnailView"></div>
|
||||
<div id="outlineView" class="treeView hidden"></div>
|
||||
<div id="attachmentsView" class="hidden"></div>
|
||||
<div id="layersView" class="treeView hidden"></div>
|
||||
</div>
|
||||
<div id="sidebarResizer"></div>
|
||||
</div>
|
||||
<!-- sidebarContainer -->
|
||||
|
||||
<div id="mainContainer">
|
||||
<div class="toolbar">
|
||||
<div id="toolbarContainer">
|
||||
<div id="toolbarViewer" class="toolbarHorizontalGroup">
|
||||
<div id="toolbarViewerLeft" class="toolbarHorizontalGroup">
|
||||
<button
|
||||
id="sidebarToggleButton"
|
||||
id="viewsManagerToggleButton"
|
||||
class="toolbarButton"
|
||||
type="button"
|
||||
tabindex="0"
|
||||
data-l10n-id="pdfjs-toggle-sidebar-button"
|
||||
data-l10n-id="pdfjs-toggle-views-manager-button"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="true"
|
||||
aria-controls="sidebarContainer"
|
||||
aria-controls="viewsManager"
|
||||
>
|
||||
<span data-l10n-id="pdfjs-toggle-sidebar-button-label"></span>
|
||||
<span data-l10n-id="pdfjs-toggle-views-manager-button-label"></span>
|
||||
</button>
|
||||
<div
|
||||
id="viewsManager"
|
||||
class="menuContainer sidebar"
|
||||
hidden="true"
|
||||
role="dialog"
|
||||
aria-describedby="viewsManagerHeaderLabel"
|
||||
data-l10n-id="pdfjs-views-manager-sidebar"
|
||||
>
|
||||
<div id="viewsManagerHeader" role="heading" aria-level="2">
|
||||
<div id="viewsManagerTitle">
|
||||
<div id="viewsManagerSelector">
|
||||
<button
|
||||
class="toolbarButton viewsManagerButton"
|
||||
type="button"
|
||||
id="viewsManagerSelectorButton"
|
||||
tabindex="0"
|
||||
data-l10n-id="pdfjs-views-manager-view-selector-button"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="listbox"
|
||||
aria-controls="viewsManagerSelectorOptions"
|
||||
>
|
||||
<span data-l10n-id="pdfjs-views-manager-view-selector-button-label"></span>
|
||||
</button>
|
||||
<menu id="viewsManagerSelectorOptions" role="listbox" class="popupMenu hidden withMark">
|
||||
<li>
|
||||
<button id="thumbnailsViewMenu" role="option" type="button" tabindex="-1">
|
||||
<span data-l10n-id="pdfjs-views-manager-pages-option-label"></span>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button id="outlinesViewMenu" role="option" type="button" tabindex="-1">
|
||||
<span data-l10n-id="pdfjs-views-manager-outlines-option-label"></span>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button id="attachmentsViewMenu" role="option" type="button" tabindex="-1">
|
||||
<span data-l10n-id="pdfjs-views-manager-attachments-option-label"></span>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button id="layersViewMenu" role="option" type="button" tabindex="-1">
|
||||
<span data-l10n-id="pdfjs-views-manager-layers-option-label"></span>
|
||||
</button>
|
||||
</li>
|
||||
</menu>
|
||||
</div>
|
||||
<span id="viewsManagerHeaderLabel" class="viewsManagerLabel"></span>
|
||||
<button
|
||||
id="viewsManagerAddFileButton"
|
||||
class="toolbarButton viewsManagerButton"
|
||||
type="button"
|
||||
tabindex="0"
|
||||
data-l10n-id="pdfjs-views-manager-add-file-button"
|
||||
hidden="true"
|
||||
>
|
||||
<span data-l10n-id="pdfjs-views-manager-add-file-button-label"></span>
|
||||
</button>
|
||||
<button
|
||||
id="viewsManagerCurrentOutlineButton"
|
||||
class="toolbarButton viewsManagerButton"
|
||||
type="button"
|
||||
tabindex="0"
|
||||
data-l10n-id="pdfjs-current-outline-item-button"
|
||||
hidden="true"
|
||||
>
|
||||
<span data-l10n-id="pdfjs-current-outline-item-button-label"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div id="viewsManagerStatus">
|
||||
<div id="viewsManagerStatusAction" class="hidden">
|
||||
<span
|
||||
id="viewsManagerStatusActionLabel"
|
||||
class="viewsManagerStatusLabel"
|
||||
data-l10n-id="pdfjs-views-manager-pages-status-none-action-label"
|
||||
></span>
|
||||
<div id="actionSelector">
|
||||
<button
|
||||
id="viewsManagerStatusActionButton"
|
||||
class="viewsManagerButton"
|
||||
type="button"
|
||||
tabindex="0"
|
||||
aria-haspopup="menu"
|
||||
aria-controls="viewsManagerStatusActionOptions"
|
||||
>
|
||||
<span data-l10n-id="pdfjs-views-manager-pages-status-action-button-label"></span>
|
||||
</button>
|
||||
<menu id="viewsManagerStatusActionOptions" class="popupMenu hidden">
|
||||
<li>
|
||||
<button id="viewsManagerStatusActionCopy" class="noIcon" role="menuitem" type="button" tabindex="0">
|
||||
<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">
|
||||
<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">
|
||||
<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">
|
||||
<span data-l10n-id="pdfjs-views-manager-pages-status-save-as-button-label"></span>
|
||||
</button>
|
||||
</li>
|
||||
</menu>
|
||||
</div>
|
||||
</div>
|
||||
<div id="viewsManagerStatusUndo" class="hidden">
|
||||
<span class="viewsManagerStatusLabel" data-l10n-id="pdfjs-views-manager-status-undo-cut-label" data-l10n-args='{"count": 0}'></span>
|
||||
<div>
|
||||
<button id="viewsManagerStatusUndoButton" class="viewsManagerButton" type="button" tabindex="0">
|
||||
<span data-l10n-id="pdfjs-views-manager-status-undo-button-label"></span>
|
||||
</button>
|
||||
<button
|
||||
id="viewsManagerStatusUndoCloseButton"
|
||||
class="toolbarButton viewsManagerButton viewsCloseButton"
|
||||
type="button"
|
||||
tabindex="0"
|
||||
data-l10n-id="pdfjs-views-manager-status-close-button"
|
||||
>
|
||||
<span data-l10n-id="pdfjs-views-manager-status-close-button-label"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="viewsManagerStatusWarning" class="hidden">
|
||||
<span class="viewsManagerStatusLabel"></span>
|
||||
<button
|
||||
id="viewsManagerStatusWarningCloseButton"
|
||||
class="toolbarButton viewsManagerButton viewsCloseButton"
|
||||
type="button"
|
||||
tabindex="0"
|
||||
data-l10n-id="pdfjs-views-manager-status-close-button"
|
||||
>
|
||||
<span data-l10n-id="pdfjs-views-manager-status-close-button-label"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div id="viewsManagerStatusWaiting" class="hidden">
|
||||
<span class="viewsManagerStatusLabel"></span>
|
||||
<button
|
||||
id="viewsManagerStatusWaitingCloseButton"
|
||||
class="toolbarButton viewsManagerButton viewsCloseButton"
|
||||
type="button"
|
||||
tabindex="0"
|
||||
data-l10n-id="pdfjs-views-manager-status-close-button"
|
||||
>
|
||||
<span data-l10n-id="pdfjs-views-manager-status-close-button-label"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="viewsManagerContent" tabindex="-1">
|
||||
<div id="thumbnailsView" class="thumbnailsView hidden" tabindex="-1"></div>
|
||||
<div id="outlinesView" class="treeView hidden"></div>
|
||||
<div id="attachmentsView" class="hidden"></div>
|
||||
<div id="layersView" class="treeView hidden"></div>
|
||||
</div>
|
||||
<div id="viewsManagerResizer" class="sidebarResizer"></div>
|
||||
</div>
|
||||
<!-- sidebarContainer -->
|
||||
|
||||
<div class="toolbarButtonSpacer"></div>
|
||||
<div class="toolbarButtonWithContainer">
|
||||
<button
|
||||
|
||||
@ -104,24 +104,34 @@ function getViewerConfiguration() {
|
||||
),
|
||||
documentPropertiesButton: document.getElementById("documentProperties"),
|
||||
},
|
||||
sidebar: {
|
||||
// Divs (and sidebar button)
|
||||
viewsManager: {
|
||||
outerContainer: document.getElementById("outerContainer"),
|
||||
sidebarContainer: document.getElementById("sidebarContainer"),
|
||||
toggleButton: document.getElementById("sidebarToggleButton"),
|
||||
resizer: document.getElementById("sidebarResizer"),
|
||||
// Buttons
|
||||
thumbnailButton: document.getElementById("viewThumbnail"),
|
||||
outlineButton: document.getElementById("viewOutline"),
|
||||
attachmentsButton: document.getElementById("viewAttachments"),
|
||||
layersButton: document.getElementById("viewLayers"),
|
||||
// Views
|
||||
thumbnailView: document.getElementById("thumbnailView"),
|
||||
outlineView: document.getElementById("outlineView"),
|
||||
toggleButton: document.getElementById("viewsManagerToggleButton"),
|
||||
sidebarContainer: document.getElementById("viewsManager"),
|
||||
resizer: document.getElementById("viewsManagerResizer"),
|
||||
thumbnailButton: document.getElementById("thumbnailsViewMenu"),
|
||||
outlineButton: document.getElementById("outlinesViewMenu"),
|
||||
attachmentsButton: document.getElementById("attachmentsViewMenu"),
|
||||
layersButton: document.getElementById("layersViewMenu"),
|
||||
viewsManagerSelectorButton: document.getElementById(
|
||||
"viewsManagerSelectorButton"
|
||||
),
|
||||
viewsManagerSelectorOptions: document.getElementById(
|
||||
"viewsManagerSelectorOptions"
|
||||
),
|
||||
thumbnailsView: document.getElementById("thumbnailsView"),
|
||||
outlinesView: document.getElementById("outlinesView"),
|
||||
attachmentsView: document.getElementById("attachmentsView"),
|
||||
layersView: document.getElementById("layersView"),
|
||||
// View-specific options
|
||||
currentOutlineItemButton: document.getElementById("currentOutlineItem"),
|
||||
viewsManagerAddFileButton: document.getElementById(
|
||||
"viewsManagerAddFileButton"
|
||||
),
|
||||
viewsManagerCurrentOutlineButton: document.getElementById(
|
||||
"viewsManagerCurrentOutlineButton"
|
||||
),
|
||||
viewsManagerHeaderLabel: document.getElementById(
|
||||
"viewsManagerHeaderLabel"
|
||||
),
|
||||
},
|
||||
findBar: {
|
||||
bar: document.getElementById("findbar"),
|
||||
|
||||
623
web/views_manager.css
Normal file
@ -0,0 +1,623 @@
|
||||
/* Copyright 2025 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#outerContainer {
|
||||
&.viewsManagerMoving {
|
||||
#viewsManager {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
&.viewsManagerOpen {
|
||||
#viewsManager {
|
||||
visibility: visible;
|
||||
inset-inline-start: 1px;
|
||||
}
|
||||
|
||||
#viewerContainer:not(.pdfPresentationMode) {
|
||||
inset-inline-start: var(--viewsManager-width, 0);
|
||||
transition-property: inset-inline-start;
|
||||
}
|
||||
}
|
||||
|
||||
&.viewsManagerResizing :is(#sidebarContainer, #viewerContainer, #loadingBar) {
|
||||
/* Improve responsiveness and avoid visual glitches when the sidebar is resized. */
|
||||
transition-duration: 0s;
|
||||
}
|
||||
}
|
||||
|
||||
#viewsManager {
|
||||
--views-manager-button-icon: url(images/pages_viewButton.svg);
|
||||
--views-manager-button-arrow-icon: url(images/pages_viewArrow.svg);
|
||||
--views-manager-add-file-button-icon: url(images/toolbarButton-zoomIn.svg);
|
||||
--current-outline-button-icon: url(images/toolbarButton-currentOutlineItem.svg);
|
||||
--menuitem-thumbnailsView-icon: url(images/pages_viewButton.svg);
|
||||
--menuitem-outlinesView-icon: url(images/toolbarButton-viewOutline.svg);
|
||||
--menuitem-attachmentsView-icon: url(images/toolbarButton-viewAttachments.svg);
|
||||
--menuitem-layersView-icon: url(images/toolbarButton-viewLayers.svg);
|
||||
--manage-button-icon: url(images/toolbarButton-pageDown.svg);
|
||||
--close-button-icon: url(images/pages_closeButton.svg);
|
||||
--undo-label-icon: url(images/altText_done.svg);
|
||||
--pages-selected-icon: url(images/pages_selected.svg);
|
||||
--spinner-icon: url(images/altText_spinner.svg);
|
||||
|
||||
--sidebar-bg-color: light-dark(rgb(255 255 255 / 0.92), rgb(35 34 43 / 0.92));
|
||||
--sidebar-backdrop-filter: blur(7px);
|
||||
--sidebar-width: 230px;
|
||||
--sidebar-min-width: min-content;
|
||||
--sidebar-max-width: 50vw;
|
||||
|
||||
--text-color: light-dark(#15141a, #fbfbfe);
|
||||
--button-fg: var(--text-color);
|
||||
--button-no-bg: transparent;
|
||||
--button-bg: light-dark(rgb(21 20 26 / 0.07), rgb(251 251 254 / 0.07));
|
||||
--button-border-color: transparent;
|
||||
--button-hover-bg: light-dark(rgb(21 20 26 / 0.14), rgb(251 251 254 / 0.14));
|
||||
--button-hover-fg: var(--text-color);
|
||||
--button-hover-border-color: var(--button-border-color);
|
||||
--button-active-bg: light-dark(rgb(21 20 26 / 0.21), rgb(251 251 254 / 0.21));
|
||||
--button-active-fg: var(--text-color);
|
||||
--button-active-border-color: var(--button-border-color);
|
||||
--button-focus-no-bg: color-mix(in srgb, var(--text-color), transparent 93%);
|
||||
--button-focus-outline-color: light-dark(#0062fa, #00cadb);
|
||||
--button-focus-border-color: light-dark(white, black);
|
||||
--status-border-color: transparent;
|
||||
--status-actions-bg: light-dark(
|
||||
rgb(21 20 26 / 0.03),
|
||||
rgb(251 251 254 / 0.03)
|
||||
);
|
||||
--status-undo-bg: light-dark(rgb(0 98 250 / 0.08), rgb(0 202 219 / 0.08));
|
||||
--status-waiting-bg: var(--status-undo-bg);
|
||||
--indicator-color: light-dark(#0062fa, #00cadb);
|
||||
--status-warning-bg: light-dark(#ffe8ea, #6e001f);
|
||||
--indicator-warning-color: light-dark(#b20037, #ffa0aa);
|
||||
--header-shadow:
|
||||
0 0.25px 0.75px -0.75px light-dark(rgb(0 0 0 / 0.05), rgb(0 0 0 / 0.2)),
|
||||
0 2px 6px -6px light-dark(rgb(0 0 0 / 0.1), rgb(0 0 0 / 0.4));
|
||||
--image-outline: none;
|
||||
--image-border-width: 4px;
|
||||
--image-border-color: light-dark(#cfcfd8, #3a3944);
|
||||
--image-hover-border-color: light-dark(#cfcfd8, #3a3944);
|
||||
--image-current-border-color: var(--button-focus-outline-color);
|
||||
--image-current-focused-outline-color: var(--image-hover-border-color);
|
||||
--image-page-number-bg: light-dark(#f0f0f4, #23222b);
|
||||
--image-page-number-fg: var(--text-color);
|
||||
--image-shadow:
|
||||
0 0.375px 1.5px 0 light-dark(rgb(0 0 0 / 0.05), rgb(0 0 0 / 0.2)),
|
||||
0 0 0 1px var(--image-border-color),
|
||||
0 3px 12px 0 light-dark(rgb(0 0 0 / 0.1), rgb(0 0 0 / 0.4));
|
||||
--image-hover-shadow:
|
||||
0 0.375px 1.5px 0 light-dark(rgb(0 0 0 / 0.05), rgb(0 0 0 / 0.2)),
|
||||
0 0 0 var(--image-border-width) var(--image-hover-border-color),
|
||||
0 3px 12px 0 light-dark(rgb(0 0 0 / 0.1), rgb(0 0 0 / 0.4));
|
||||
--image-current-shadow:
|
||||
0 0.375px 1.5px 0 light-dark(rgb(0 0 0 / 0.05), rgb(0 0 0 / 0.2)),
|
||||
0 0 0 var(--image-border-width) var(--image-current-border-color),
|
||||
0 3px 12px 0 light-dark(rgb(0 0 0 / 0.1), rgb(0 0 0 / 0.4));
|
||||
|
||||
@media screen and (forced-colors: active) {
|
||||
--text-color: CanvasText;
|
||||
--button-fg: ButtonText;
|
||||
--button-bg: ButtonFace;
|
||||
--button-no-bg: ButtonFace;
|
||||
--button-border-color: ButtonText;
|
||||
--button-hover-bg: SelectedItemText;
|
||||
--button-hover-fg: SelectedItem;
|
||||
--button-hover-border-color: SelectedItem;
|
||||
--button-active-bg: SelectedItemText;
|
||||
--button-active-fg: SelectedItem;
|
||||
--button-active-border-color: ButtonText;
|
||||
--button-focus-no-bg: ButtonFace;
|
||||
--button-focus-outline-color: CanvasText;
|
||||
--button-focus-border-color: none;
|
||||
--status-border-color: CanvasText;
|
||||
--status-undo-bg: none;
|
||||
--indicator-color: CanvasText;
|
||||
--status-warning-bg: none;
|
||||
--indicator-warning-color: CanvasText;
|
||||
--header-shadow: none;
|
||||
--image-shadow: 0 0 0 1px CanvasText;
|
||||
--image-outline: 1px solid CanvasText;
|
||||
--image-border-color: CanvasText;
|
||||
--image-hover-border-color: SelectedItem;
|
||||
--image-current-border-color: ButtonBorder;
|
||||
--image-current-focused-outline-color: var(--image-hover-border-color);
|
||||
--image-page-number-bg: ButtonFace;
|
||||
--image-page-number-fg: CanvasText;
|
||||
}
|
||||
|
||||
display: flex;
|
||||
padding-bottom: 16px;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
height: calc(
|
||||
var(--viewer-container-height) - var(--toolbar-height) -
|
||||
var(--doorhanger-height)
|
||||
);
|
||||
position: absolute;
|
||||
|
||||
inset-inline-start: calc(
|
||||
-1 * var(--viewsManager-width, --sidebar-width) - 1px
|
||||
);
|
||||
transition-property: inset-inline-start;
|
||||
transition-duration: var(--sidebar-transition-duration);
|
||||
transition-timing-function: var(--sidebar-transition-timing-function);
|
||||
|
||||
.sidebarResizer {
|
||||
inset-inline-start: 100%;
|
||||
}
|
||||
|
||||
.viewsManagerButton {
|
||||
width: auto;
|
||||
color: var(--button-fg);
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--button-border-color);
|
||||
background: var(--button-bg);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--button-hover-bg) !important;
|
||||
color: var(--button-hover-fg) !important;
|
||||
border-color: var(--button-hover-border-color) !important;
|
||||
|
||||
&::before {
|
||||
background-color: var(--button-hover-fg) !important;
|
||||
}
|
||||
}
|
||||
&:active {
|
||||
background: var(--button-active-bg) !important;
|
||||
color: var(--button-active-fg) !important;
|
||||
border-color: var(--button-active-border-color) !important;
|
||||
|
||||
&::before {
|
||||
background-color: var(--button-active-fg) !important;
|
||||
}
|
||||
}
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--button-focus-outline-color);
|
||||
outline-offset: 2px;
|
||||
border-color: var(--button-focus-border-color);
|
||||
}
|
||||
|
||||
&.viewsCloseButton {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
padding: 4px;
|
||||
border-radius: 8px;
|
||||
|
||||
&::before {
|
||||
mask-image: var(--close-button-icon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#viewsManagerHeader {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
width: 100%;
|
||||
box-shadow: var(--header-shadow);
|
||||
flex: 0 0 auto;
|
||||
|
||||
.viewsManagerLabel {
|
||||
flex: 1 0 0;
|
||||
color: var(--text-color);
|
||||
text-align: center;
|
||||
height: fit-content;
|
||||
user-select: none;
|
||||
|
||||
font: menu;
|
||||
font-size: 15px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
#viewsManagerTitle {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
justify-content: space-between;
|
||||
width: auto;
|
||||
padding: 12px 16px 12px 8px;
|
||||
|
||||
#viewsManagerSelector {
|
||||
width: 48px;
|
||||
height: 32px;
|
||||
display: block;
|
||||
|
||||
> button {
|
||||
background: var(--button-no-bg);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
&:focus-visible {
|
||||
background-color: var(--button-focus-no-bg);
|
||||
}
|
||||
|
||||
&::before {
|
||||
mask-repeat: no-repeat;
|
||||
mask-image: var(--views-manager-button-icon);
|
||||
background-color: var(--button-fg);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--button-hover-fg) !important;
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--button-active-fg) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
margin-left: 8px;
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
mask-image: var(--views-manager-button-arrow-icon);
|
||||
background-color: var(--button-fg);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--button-hover-fg) !important;
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--button-active-fg) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .popupMenu {
|
||||
min-width: 182px;
|
||||
z-index: 1;
|
||||
|
||||
> li > button {
|
||||
&#thumbnailsViewMenu::before {
|
||||
mask-image: var(--menuitem-thumbnailsView-icon);
|
||||
}
|
||||
&#outlinesViewMenu::before {
|
||||
mask-image: var(--menuitem-outlinesView-icon);
|
||||
}
|
||||
&#attachmentsViewMenu::before {
|
||||
mask-image: var(--menuitem-attachmentsView-icon);
|
||||
}
|
||||
&#layersViewMenu::before {
|
||||
mask-image: var(--menuitem-layersView-icon);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#viewsManagerAddFileButton {
|
||||
background: var(--button-no-bg);
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
|
||||
&:focus-visible {
|
||||
background-color: var(--button-focus-no-bg);
|
||||
}
|
||||
|
||||
&::before {
|
||||
mask-repeat: no-repeat;
|
||||
mask-image: var(--views-manager-add-file-button-icon);
|
||||
}
|
||||
}
|
||||
|
||||
#viewsManagerCurrentOutlineButton {
|
||||
background: var(--button-no-bg);
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
|
||||
&:focus-visible {
|
||||
background-color: var(--button-focus-no-bg);
|
||||
}
|
||||
|
||||
&::before {
|
||||
mask-repeat: no-repeat;
|
||||
mask-image: var(--current-outline-button-icon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#viewsManagerStatus {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
justify-content: space-between;
|
||||
width: auto;
|
||||
border: 1px solid var(--status-border-color);
|
||||
|
||||
> div {
|
||||
min-height: 64px;
|
||||
width: 100%;
|
||||
padding-inline: 16px;
|
||||
}
|
||||
|
||||
.viewsManagerStatusLabel {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
font: menu;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
#viewsManagerStatusAction {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
background-color: var(--status-actions-bg);
|
||||
|
||||
> span.selected::before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: var(--icon-size);
|
||||
height: var(--icon-size);
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
background-color: var(--indicator-color);
|
||||
mask-image: var(--pages-selected-icon);
|
||||
}
|
||||
|
||||
#actionSelector {
|
||||
height: 32px;
|
||||
width: auto;
|
||||
display: block;
|
||||
|
||||
#viewsManagerStatusActionButton {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 4px 16px;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: var(--icon-size);
|
||||
height: var(--icon-size);
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
mask-image: var(--manage-button-icon);
|
||||
background-color: var(--button-fg);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--button-hover-fg) !important;
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--button-active-fg) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .contextMenu {
|
||||
min-width: 115px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#viewsManagerStatusUndo {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
background-color: var(--status-undo-bg);
|
||||
|
||||
> span::before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: var(--icon-size);
|
||||
height: var(--icon-size);
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
mask-image: var(--undo-label-icon);
|
||||
background-color: var(--indicator-color);
|
||||
}
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: auto;
|
||||
|
||||
#viewsManagerStatusUndoButton {
|
||||
width: auto;
|
||||
min-height: 24px;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#viewsManagerStatusWarning {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background-color: var(--status-warning-bg);
|
||||
|
||||
> span {
|
||||
align-items: flex-start;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: var(--icon-size);
|
||||
height: var(--icon-size);
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
background-color: var(--indicator-warning-color);
|
||||
mask-image: var(--undo-label-icon);
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#viewsManagerStatusWaiting {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background-color: var(--status-waiting-bg);
|
||||
|
||||
> span::before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: var(--icon-size);
|
||||
height: var(--icon-size);
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
background-color: var(--indicator-color);
|
||||
mask-image: var(--spinner-icon);
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#viewsManagerContent {
|
||||
width: 100%;
|
||||
flex: 1 1 0%;
|
||||
overflow: auto;
|
||||
|
||||
#thumbnailsView {
|
||||
--thumbnail-width: 126px;
|
||||
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
align-items: center;
|
||||
justify-content: space-evenly;
|
||||
padding: 20px 32px;
|
||||
gap: 16px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
> .thumbnail {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
width: auto;
|
||||
height: auto;
|
||||
position: relative;
|
||||
scroll-margin-top: 20px;
|
||||
|
||||
> input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: attr(page-number);
|
||||
border-radius: 8px;
|
||||
background-color: var(--image-page-number-bg);
|
||||
color: var(--image-page-number-fg);
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
right: calc(var(--thumbnail-width) / 2);
|
||||
min-width: 32px;
|
||||
height: 16px;
|
||||
text-align: center;
|
||||
translate: 50%;
|
||||
|
||||
font: menu;
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
> .thumbnailImage {
|
||||
width: var(--thumbnail-width);
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--image-shadow);
|
||||
box-sizing: content-box;
|
||||
outline: var(--image-outline);
|
||||
|
||||
&.missingThumbnailImage {
|
||||
content-visibility: hidden;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
box-shadow: var(--image-hover-shadow);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
&:not([aria-current="page"]) {
|
||||
box-shadow: var(--image-hover-shadow);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&[aria-current="page"] {
|
||||
outline: var(--image-border-width) solid
|
||||
var(--image-current-focused-outline-color);
|
||||
outline-offset: var(--image-border-width);
|
||||
}
|
||||
}
|
||||
|
||||
&[aria-current="page"] {
|
||||
box-shadow: var(--image-current-shadow);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#attachmentsView {
|
||||
--attachment-color: light-dark(rgb(0 0 0 / 0.8), rgb(255 255 255 / 0.8));
|
||||
--attachment-bg-color: light-dark(
|
||||
rgb(0 0 0 / 0.15),
|
||||
rgb(255 255 255 / 0.15)
|
||||
);
|
||||
--attachment-hover-color: light-dark(
|
||||
rgb(0 0 0 / 0.9),
|
||||
rgb(255 255 255 / 0.9)
|
||||
);
|
||||
|
||||
> ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
|
||||
> li > a {
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
/* Subtract the right padding (left, in RTL mode) of the container: */
|
||||
min-width: calc(100% - 4px);
|
||||
height: auto;
|
||||
margin-bottom: 1px;
|
||||
padding: 2px 0 5px;
|
||||
padding-inline-start: 4px;
|
||||
border-radius: 2px;
|
||||
color: var(--attachment-color);
|
||||
font-size: 13px;
|
||||
line-height: 15px;
|
||||
user-select: none;
|
||||
white-space: normal;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--attachment-bg-color);
|
||||
background-clip: padding-box;
|
||||
border-radius: 2px;
|
||||
color: var(--attachment-hover-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -20,13 +20,14 @@ import {
|
||||
docStyle,
|
||||
PresentationModeState,
|
||||
SidebarView,
|
||||
toggleCheckedBtn,
|
||||
toggleExpandedBtn,
|
||||
toggleSelectedBtn,
|
||||
} from "./ui_utils.js";
|
||||
import { Menu } from "./menu.js";
|
||||
import { Sidebar } from "./sidebar.js";
|
||||
|
||||
const SIDEBAR_WIDTH_VAR = "--sidebar-width";
|
||||
const SIDEBAR_MIN_WIDTH = 200; // pixels
|
||||
const SIDEBAR_RESIZING_CLASS = "sidebarResizing";
|
||||
const SIDEBAR_WIDTH_VAR = "--viewsManager-width";
|
||||
const SIDEBAR_RESIZING_CLASS = "viewsManagerResizing";
|
||||
const UI_NOTIFICATION_CLASS = "pdfSidebarNotification";
|
||||
|
||||
/**
|
||||
@ -66,19 +67,43 @@ const UI_NOTIFICATION_CLASS = "pdfSidebarNotification";
|
||||
* find the current outline item.
|
||||
*/
|
||||
|
||||
class PDFSidebar {
|
||||
#isRTL = false;
|
||||
|
||||
#mouseAC = null;
|
||||
|
||||
#outerContainerWidth = null;
|
||||
|
||||
#width = null;
|
||||
class ViewsManager extends Sidebar {
|
||||
static #l10nDescription = null;
|
||||
|
||||
/**
|
||||
* @param {PDFSidebarOptions} options
|
||||
*/
|
||||
constructor({ elements, eventBus, l10n }) {
|
||||
constructor({
|
||||
elements: {
|
||||
outerContainer,
|
||||
sidebarContainer,
|
||||
toggleButton,
|
||||
resizer,
|
||||
thumbnailButton,
|
||||
outlineButton,
|
||||
attachmentsButton,
|
||||
layersButton,
|
||||
thumbnailsView,
|
||||
outlinesView,
|
||||
attachmentsView,
|
||||
layersView,
|
||||
viewsManagerCurrentOutlineButton,
|
||||
viewsManagerSelectorButton,
|
||||
viewsManagerSelectorOptions,
|
||||
viewsManagerHeaderLabel,
|
||||
},
|
||||
eventBus,
|
||||
l10n,
|
||||
}) {
|
||||
super(
|
||||
{
|
||||
sidebar: sidebarContainer,
|
||||
resizer,
|
||||
toggleButton,
|
||||
},
|
||||
l10n.getDirection() === "ltr",
|
||||
/* isResizerOnTheLeft = */ false
|
||||
);
|
||||
this.isOpen = false;
|
||||
this.active = SidebarView.THUMBS;
|
||||
this.isInitialViewSet = false;
|
||||
@ -91,26 +116,41 @@ class PDFSidebar {
|
||||
this.onToggled = null;
|
||||
this.onUpdateThumbnails = null;
|
||||
|
||||
this.outerContainer = elements.outerContainer;
|
||||
this.sidebarContainer = elements.sidebarContainer;
|
||||
this.toggleButton = elements.toggleButton;
|
||||
this.resizer = elements.resizer;
|
||||
this.outerContainer = outerContainer;
|
||||
this.sidebarContainer = sidebarContainer;
|
||||
this.toggleButton = toggleButton;
|
||||
this.resizer = resizer;
|
||||
|
||||
this.thumbnailButton = elements.thumbnailButton;
|
||||
this.outlineButton = elements.outlineButton;
|
||||
this.attachmentsButton = elements.attachmentsButton;
|
||||
this.layersButton = elements.layersButton;
|
||||
this.thumbnailButton = thumbnailButton;
|
||||
this.outlineButton = outlineButton;
|
||||
this.attachmentsButton = attachmentsButton;
|
||||
this.layersButton = layersButton;
|
||||
|
||||
this.thumbnailView = elements.thumbnailView;
|
||||
this.outlineView = elements.outlineView;
|
||||
this.attachmentsView = elements.attachmentsView;
|
||||
this.layersView = elements.layersView;
|
||||
this.thumbnailsView = thumbnailsView;
|
||||
this.outlinesView = outlinesView;
|
||||
this.attachmentsView = attachmentsView;
|
||||
this.layersView = layersView;
|
||||
|
||||
this._currentOutlineItemButton = elements.currentOutlineItemButton;
|
||||
this.viewsManagerCurrentOutlineButton = viewsManagerCurrentOutlineButton;
|
||||
this.viewsManagerHeaderLabel = viewsManagerHeaderLabel;
|
||||
|
||||
this.eventBus = eventBus;
|
||||
|
||||
this.#isRTL = l10n.getDirection() === "rtl";
|
||||
this.menu = new Menu(
|
||||
viewsManagerSelectorOptions,
|
||||
viewsManagerSelectorButton,
|
||||
[thumbnailButton, outlineButton, attachmentsButton, layersButton]
|
||||
);
|
||||
|
||||
ViewsManager.#l10nDescription ||= Object.freeze({
|
||||
pagesTitle: "pdfjs-views-manager-pages-title",
|
||||
outlinesTitle: "pdfjs-views-manager-outlines-title",
|
||||
attachmentsTitle: "pdfjs-views-manager-attachments-title",
|
||||
layersTitle: "pdfjs-views-manager-layers-title",
|
||||
notificationButton: "pdfjs-toggle-views-manager-notification-button",
|
||||
toggleButton: "pdfjs-toggle-views-manager-button",
|
||||
});
|
||||
|
||||
this.#addEventListeners();
|
||||
}
|
||||
|
||||
@ -121,10 +161,11 @@ class PDFSidebar {
|
||||
this.#hideUINotification(/* reset = */ true);
|
||||
this.switchView(SidebarView.THUMBS);
|
||||
|
||||
this.outlineButton.disabled = false;
|
||||
this.attachmentsButton.disabled = false;
|
||||
this.layersButton.disabled = false;
|
||||
this._currentOutlineItemButton.disabled = true;
|
||||
this.outlineButton.disabled =
|
||||
this.attachmentsButton.disabled =
|
||||
this.layersButton.disabled =
|
||||
false;
|
||||
this.viewsManagerCurrentOutlineButton.disabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -168,6 +209,7 @@ class PDFSidebar {
|
||||
switchView(view, forceOpen = false) {
|
||||
const isViewChanged = view !== this.active;
|
||||
let forceRendering = false;
|
||||
let titleL10nId = null;
|
||||
|
||||
switch (view) {
|
||||
case SidebarView.NONE:
|
||||
@ -176,21 +218,25 @@ class PDFSidebar {
|
||||
}
|
||||
return; // Closing will trigger rendering and dispatch the event.
|
||||
case SidebarView.THUMBS:
|
||||
titleL10nId = "pagesTitle";
|
||||
if (this.isOpen && isViewChanged) {
|
||||
forceRendering = true;
|
||||
}
|
||||
break;
|
||||
case SidebarView.OUTLINE:
|
||||
titleL10nId = "outlinesTitle";
|
||||
if (this.outlineButton.disabled) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case SidebarView.ATTACHMENTS:
|
||||
titleL10nId = "attachmentsTitle";
|
||||
if (this.attachmentsButton.disabled) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case SidebarView.LAYERS:
|
||||
titleL10nId = "layersTitle";
|
||||
if (this.layersButton.disabled) {
|
||||
return;
|
||||
}
|
||||
@ -199,27 +245,34 @@ class PDFSidebar {
|
||||
console.error(`PDFSidebar.switchView: "${view}" is not a valid view.`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.viewsManagerCurrentOutlineButton.hidden = view !== SidebarView.OUTLINE;
|
||||
this.viewsManagerHeaderLabel.setAttribute(
|
||||
"data-l10n-id",
|
||||
ViewsManager.#l10nDescription[titleL10nId] || ""
|
||||
);
|
||||
|
||||
// Update the active view *after* it has been validated above,
|
||||
// in order to prevent setting it to an invalid state.
|
||||
this.active = view;
|
||||
|
||||
// Update the CSS classes (and aria attributes), for all buttons and views.
|
||||
toggleCheckedBtn(
|
||||
toggleSelectedBtn(
|
||||
this.thumbnailButton,
|
||||
view === SidebarView.THUMBS,
|
||||
this.thumbnailView
|
||||
this.thumbnailsView
|
||||
);
|
||||
toggleCheckedBtn(
|
||||
toggleSelectedBtn(
|
||||
this.outlineButton,
|
||||
view === SidebarView.OUTLINE,
|
||||
this.outlineView
|
||||
this.outlinesView
|
||||
);
|
||||
toggleCheckedBtn(
|
||||
toggleSelectedBtn(
|
||||
this.attachmentsButton,
|
||||
view === SidebarView.ATTACHMENTS,
|
||||
this.attachmentsView
|
||||
);
|
||||
toggleCheckedBtn(
|
||||
toggleSelectedBtn(
|
||||
this.layersButton,
|
||||
view === SidebarView.LAYERS,
|
||||
this.layersView
|
||||
@ -243,10 +296,20 @@ class PDFSidebar {
|
||||
return;
|
||||
}
|
||||
this.isOpen = true;
|
||||
this.onResizing(this.width);
|
||||
this._sidebar.hidden = false;
|
||||
toggleExpandedBtn(this.toggleButton, true);
|
||||
this.switchView(this.active);
|
||||
|
||||
this.outerContainer.classList.add("sidebarMoving", "sidebarOpen");
|
||||
|
||||
// Changing `hidden` above may cause a reflow which would prevent the
|
||||
// CSS transition from being applied correctly, so we need to delay
|
||||
// adding the relevant CSS classes.
|
||||
queueMicrotask(() => {
|
||||
this.outerContainer.classList.add(
|
||||
"viewsManagerMoving",
|
||||
"viewsManagerOpen"
|
||||
);
|
||||
});
|
||||
if (this.active === SidebarView.THUMBS) {
|
||||
this.onUpdateThumbnails();
|
||||
}
|
||||
@ -261,10 +324,11 @@ class PDFSidebar {
|
||||
return;
|
||||
}
|
||||
this.isOpen = false;
|
||||
this._sidebar.hidden = true;
|
||||
toggleExpandedBtn(this.toggleButton, false);
|
||||
|
||||
this.outerContainer.classList.add("sidebarMoving");
|
||||
this.outerContainer.classList.remove("sidebarOpen");
|
||||
this.outerContainer.classList.add("viewsManagerMoving");
|
||||
this.outerContainer.classList.remove("viewsManagerOpen");
|
||||
|
||||
this.onToggled();
|
||||
this.#dispatchEvent();
|
||||
@ -276,6 +340,7 @@ class PDFSidebar {
|
||||
}
|
||||
|
||||
toggle(evt = null) {
|
||||
super.toggle();
|
||||
if (this.isOpen) {
|
||||
this.close(evt);
|
||||
} else {
|
||||
@ -297,7 +362,7 @@ class PDFSidebar {
|
||||
#showUINotification() {
|
||||
this.toggleButton.setAttribute(
|
||||
"data-l10n-id",
|
||||
"pdfjs-toggle-sidebar-notification-button"
|
||||
ViewsManager.#l10nDescription.notificationButton
|
||||
);
|
||||
|
||||
if (!this.isOpen) {
|
||||
@ -317,7 +382,7 @@ class PDFSidebar {
|
||||
if (reset) {
|
||||
this.toggleButton.setAttribute(
|
||||
"data-l10n-id",
|
||||
"pdfjs-toggle-sidebar-button"
|
||||
ViewsManager.#l10nDescription.toggleButton
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -327,16 +392,12 @@ class PDFSidebar {
|
||||
|
||||
this.sidebarContainer.addEventListener("transitionend", evt => {
|
||||
if (evt.target === this.sidebarContainer) {
|
||||
outerContainer.classList.remove("sidebarMoving");
|
||||
outerContainer.classList.remove("viewsManagerMoving");
|
||||
// Ensure that rendering is triggered after opening/closing the sidebar.
|
||||
eventBus.dispatch("resize", { source: this });
|
||||
}
|
||||
});
|
||||
|
||||
this.toggleButton.addEventListener("click", evt => {
|
||||
this.toggle(evt);
|
||||
});
|
||||
|
||||
// Buttons for switching views.
|
||||
this.thumbnailButton.addEventListener("click", () => {
|
||||
this.switchView(SidebarView.THUMBS);
|
||||
@ -361,7 +422,7 @@ class PDFSidebar {
|
||||
});
|
||||
|
||||
// Buttons for view-specific options.
|
||||
this._currentOutlineItemButton.addEventListener("click", () => {
|
||||
this.viewsManagerCurrentOutlineButton.addEventListener("click", () => {
|
||||
eventBus.dispatch("currentoutlineitem", { source: this });
|
||||
});
|
||||
|
||||
@ -385,7 +446,7 @@ class PDFSidebar {
|
||||
if (!this.isInitialViewSet) {
|
||||
return;
|
||||
}
|
||||
this._currentOutlineItemButton.disabled = !enabled;
|
||||
this.viewsManagerCurrentOutlineButton.disabled = !enabled;
|
||||
});
|
||||
});
|
||||
|
||||
@ -410,105 +471,20 @@ class PDFSidebar {
|
||||
this.onUpdateThumbnails();
|
||||
}
|
||||
});
|
||||
|
||||
// Handle resizing of the sidebar.
|
||||
this.resizer.addEventListener("mousedown", evt => {
|
||||
if (evt.button !== 0) {
|
||||
return;
|
||||
}
|
||||
// Disable the `transition-duration` rules when sidebar resizing begins,
|
||||
// in order to improve responsiveness and to avoid visual glitches.
|
||||
outerContainer.classList.add(SIDEBAR_RESIZING_CLASS);
|
||||
|
||||
this.#mouseAC = new AbortController();
|
||||
const opts = { signal: this.#mouseAC.signal };
|
||||
|
||||
window.addEventListener("mousemove", this.#mouseMove.bind(this), opts);
|
||||
window.addEventListener("mouseup", this.#mouseUp.bind(this), opts);
|
||||
window.addEventListener("blur", this.#mouseUp.bind(this), opts);
|
||||
});
|
||||
|
||||
eventBus._on("resize", evt => {
|
||||
// When the *entire* viewer is resized, such that it becomes narrower,
|
||||
// ensure that the sidebar doesn't end up being too wide.
|
||||
if (evt.source !== window) {
|
||||
return;
|
||||
}
|
||||
// Always reset the cached width when the viewer is resized.
|
||||
this.#outerContainerWidth = null;
|
||||
|
||||
if (!this.#width) {
|
||||
// The sidebar hasn't been resized, hence no need to adjust its width.
|
||||
return;
|
||||
}
|
||||
// NOTE: If the sidebar is closed, we don't need to worry about
|
||||
// visual glitches nor ensure that rendering is triggered.
|
||||
if (!this.isOpen) {
|
||||
this.#updateWidth(this.#width);
|
||||
return;
|
||||
}
|
||||
outerContainer.classList.add(SIDEBAR_RESIZING_CLASS);
|
||||
const updated = this.#updateWidth(this.#width);
|
||||
|
||||
Promise.resolve().then(() => {
|
||||
outerContainer.classList.remove(SIDEBAR_RESIZING_CLASS);
|
||||
// Trigger rendering if the sidebar width changed, to avoid
|
||||
// depending on the order in which 'resize' events are handled.
|
||||
if (updated) {
|
||||
eventBus.dispatch("resize", { source: this });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
get outerContainerWidth() {
|
||||
return (this.#outerContainerWidth ||= this.outerContainer.clientWidth);
|
||||
onStartResizing() {
|
||||
this.outerContainer.classList.add(SIDEBAR_RESIZING_CLASS);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns {boolean} Indicating if the sidebar width was updated.
|
||||
*/
|
||||
#updateWidth(width = 0) {
|
||||
// Prevent the sidebar from becoming too narrow, or from occupying more
|
||||
// than half of the available viewer width.
|
||||
const maxWidth = Math.floor(this.outerContainerWidth / 2);
|
||||
if (width > maxWidth) {
|
||||
width = maxWidth;
|
||||
}
|
||||
if (width < SIDEBAR_MIN_WIDTH) {
|
||||
width = SIDEBAR_MIN_WIDTH;
|
||||
}
|
||||
// Only update the UI when the sidebar width did in fact change.
|
||||
if (width === this.#width) {
|
||||
return false;
|
||||
}
|
||||
this.#width = width;
|
||||
|
||||
docStyle.setProperty(SIDEBAR_WIDTH_VAR, `${width}px`);
|
||||
return true;
|
||||
}
|
||||
|
||||
#mouseMove(evt) {
|
||||
let width = evt.clientX;
|
||||
// For sidebar resizing to work correctly in RTL mode, invert the width.
|
||||
if (this.#isRTL) {
|
||||
width = this.outerContainerWidth - width;
|
||||
}
|
||||
this.#updateWidth(width);
|
||||
}
|
||||
|
||||
#mouseUp(evt) {
|
||||
// Re-enable the `transition-duration` rules when sidebar resizing ends...
|
||||
this.outerContainer.classList.remove(SIDEBAR_RESIZING_CLASS);
|
||||
// ... and ensure that rendering will always be triggered.
|
||||
onStopResizing() {
|
||||
this.eventBus.dispatch("resize", { source: this });
|
||||
this.outerContainer.classList.remove(SIDEBAR_RESIZING_CLASS);
|
||||
}
|
||||
|
||||
this.#mouseAC?.abort();
|
||||
this.#mouseAC = null;
|
||||
onResizing(newWidth) {
|
||||
docStyle.setProperty(SIDEBAR_WIDTH_VAR, `${newWidth}px`);
|
||||
}
|
||||
}
|
||||
|
||||
export { PDFSidebar };
|
||||
export { ViewsManager };
|
||||