diff --git a/web/pdf_sidebar.js b/web/pdf_sidebar.js new file mode 100644 index 000000000..0490ef249 --- /dev/null +++ b/web/pdf_sidebar.js @@ -0,0 +1,324 @@ +/* Copyright 2016 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. + */ + /* globals RenderingStates */ + +'use strict'; + +var SidebarView = { + NONE: 0, + THUMBS: 1, + OUTLINE: 2, + ATTACHMENTS: 3 +}; + +/** + * @typedef {Object} PDFSidebarOptions + * @property {PDFViewer} - The document viewer. + * @property {PDFThumbnailViewer} - The thumbnail viewer. + * @property {PDFOutlineViewer} - The outline viewer. + * @property {HTMLDivElement} mainContainer - The main container + * (in which the viewer element is placed). + * @property {HTMLDivElement} outerContainer - The outer container + * (encasing both the viewer and sidebar elements). + * @property {HTMLButtonElement} toggleButton - The button used for + * opening/closing the sidebar. + * @property {HTMLButtonElement} thumbnailButton - The button used to show + * the thumbnail view. + * @property {HTMLButtonElement} outlineButton - The button used to show + * the outline view. + * @property {HTMLButtonElement} attachmentsButton - The button used to show + * the attachments view. + * @property {HTMLDivElement} thumbnailView - The container in which + * the thumbnails are placed. + * @property {HTMLDivElement} outlineView - The container in which + * the outline is placed. + * @property {HTMLDivElement} attachmentsView - The container in which + * the attachments are placed. + */ + +/** + * @class + */ +var PDFSidebar = (function PDFSidebarClosure() { + /** + * @constructs PDFSidebar + * @param {PDFSidebarOptions} options + */ + function PDFSidebar(options) { + this.isOpen = false; + this.active = SidebarView.THUMBS; + this.isInitialViewSet = false; + + /** + * Callback used when the sidebar has been opened/closed, to ensure that + * the viewers (PDFViewer/PDFThumbnailViewer) are updated correctly. + */ + this.onToggled = null; + + this.pdfViewer = options.pdfViewer; + this.pdfThumbnailViewer = options.pdfThumbnailViewer; + this.pdfOutlineViewer = options.pdfOutlineViewer; + + this.mainContainer = options.mainContainer; + this.outerContainer = options.outerContainer; + this.toggleButton = options.toggleButton; + + this.thumbnailButton = options.thumbnailButton; + this.outlineButton = options.outlineButton; + this.attachmentsButton = options.attachmentsButton; + + this.thumbnailView = options.thumbnailView; + this.outlineView = options.outlineView; + this.attachmentsView = options.attachmentsView; + + this._addEventListeners(); + } + + PDFSidebar.prototype = { + reset: function PDFSidebar_reset() { + this.isInitialViewSet = false; + + this.close(); + this.switchView(SidebarView.THUMBS); + + this.outlineButton.disabled = false; + this.attachmentsButton.disabled = false; + }, + + /** + * @returns {number} One of the values in {SidebarView}. + */ + get visibleView() { + return (this.isOpen ? this.active : SidebarView.NONE); + }, + + get isThumbnailViewVisible() { + return (this.isOpen && this.active === SidebarView.THUMBS); + }, + + get isOutlineViewVisible() { + return (this.isOpen && this.active === SidebarView.OUTLINE); + }, + + get isAttachmentsViewVisible() { + return (this.isOpen && this.active === SidebarView.ATTACHMENTS); + }, + + /** + * @param {number} view - The sidebar view that should become visible, + * must be one of the values in {SidebarView}. + */ + setInitialView: function PDFSidebar_setInitialView(view) { + if (this.isInitialViewSet) { + return; + } + this.isInitialViewSet = true; + + if (this.isOpen && view === SidebarView.NONE) { + // If the user has already manually opened the sidebar, + // immediately closing it would be bad UX. + return; + } + this.switchView(view, true); + }, + + /** + * @param {number} view - The sidebar view that should be switched to, + * must be one of the values in {SidebarView}. + * @param {boolean} forceOpen - Ensure that the sidebar is opened. + * The default value is false. + */ + switchView: function PDFSidebar_switchView(view, forceOpen) { + if (view === SidebarView.NONE) { + this.close(); + return; + } + if (forceOpen) { + this.open(); + } + var shouldForceRendering = false; + + switch (view) { + case SidebarView.THUMBS: + this.thumbnailButton.classList.add('toggled'); + this.outlineButton.classList.remove('toggled'); + this.attachmentsButton.classList.remove('toggled'); + + this.thumbnailView.classList.remove('hidden'); + this.outlineView.classList.add('hidden'); + this.attachmentsView.classList.add('hidden'); + + if (this.isOpen && view !== this.active) { + this._updateThumbnailViewer(); + shouldForceRendering = true; + } + break; + case SidebarView.OUTLINE: + if (this.outlineButton.disabled) { + return; + } + this.thumbnailButton.classList.remove('toggled'); + this.outlineButton.classList.add('toggled'); + this.attachmentsButton.classList.remove('toggled'); + + this.thumbnailView.classList.add('hidden'); + this.outlineView.classList.remove('hidden'); + this.attachmentsView.classList.add('hidden'); + break; + case SidebarView.ATTACHMENTS: + if (this.attachmentsButton.disabled) { + return; + } + this.thumbnailButton.classList.remove('toggled'); + this.outlineButton.classList.remove('toggled'); + this.attachmentsButton.classList.add('toggled'); + + this.thumbnailView.classList.add('hidden'); + this.outlineView.classList.add('hidden'); + this.attachmentsView.classList.remove('hidden'); + break; + default: + console.error('PDFSidebar_switchView: "' + view + + '" is an unsupported value.'); + return; + } + // Update the active view *after* it has been validated above, + // in order to prevent setting it to an invalid state. + this.active = view | 0; + + if (shouldForceRendering) { + this._forceRendering(); + } + }, + + open: function PDFSidebar_open() { + if (this.isOpen) { + return; + } + this.isOpen = true; + this.toggleButton.classList.add('toggled'); + + this.outerContainer.classList.add('sidebarMoving'); + this.outerContainer.classList.add('sidebarOpen'); + + if (this.active === SidebarView.THUMBS) { + this._updateThumbnailViewer(); + } + this._forceRendering(); + }, + + close: function PDFSidebar_close() { + if (!this.isOpen) { + return; + } + this.isOpen = false; + this.toggleButton.classList.remove('toggled'); + + this.outerContainer.classList.add('sidebarMoving'); + this.outerContainer.classList.remove('sidebarOpen'); + + this._forceRendering(); + }, + + toggle: function PDFSidebar_toggle() { + if (this.isOpen) { + this.close(); + } else { + this.open(); + } + }, + + /** + * @private + */ + _forceRendering: function PDFSidebar_forceRendering() { + if (this.onToggled) { + this.onToggled(); + } else { // Fallback + this.pdfViewer.forceRendering(); + this.pdfThumbnailViewer.forceRendering(); + } + }, + + /** + * @private + */ + _updateThumbnailViewer: function PDFSidebar_updateThumbnailViewer() { + var pdfViewer = this.pdfViewer; + var thumbnailViewer = this.pdfThumbnailViewer; + + // Use the rendered pages to set the corresponding thumbnail images. + var pagesCount = pdfViewer.pagesCount; + for (var pageIndex = 0; pageIndex < pagesCount; pageIndex++) { + var pageView = pdfViewer.getPageView(pageIndex); + if (pageView && pageView.renderingState === RenderingStates.FINISHED) { + var thumbnailView = thumbnailViewer.getThumbnail(pageIndex); + thumbnailView.setImage(pageView); + } + } + thumbnailViewer.scrollThumbnailIntoView(pdfViewer.currentPageNumber); + }, + + /** + * @private + */ + _addEventListeners: function PDFSidebar_addEventListeners() { + var self = this; + + self.mainContainer.addEventListener('transitionend', function(evt) { + if (evt.target === /* mainContainer */ this) { + self.outerContainer.classList.remove('sidebarMoving'); + } + }); + + // Buttons for switching views. + self.thumbnailButton.addEventListener('click', function() { + self.switchView(SidebarView.THUMBS); + }); + + self.outlineButton.addEventListener('click', function() { + self.switchView(SidebarView.OUTLINE); + }); + self.outlineButton.addEventListener('dblclick', function() { + self.pdfOutlineViewer.toggleOutlineTree(); + }); + + self.attachmentsButton.addEventListener('click', function() { + self.switchView(SidebarView.ATTACHMENTS); + }); + + // Disable/enable views. + self.outlineView.addEventListener('outlineloaded', function(evt) { + var outlineCount = evt.detail.outlineCount; + + self.outlineButton.disabled = !outlineCount; + if (!outlineCount && self.active === SidebarView.OUTLINE) { + self.switchView(SidebarView.THUMBS); + } + }); + + self.attachmentsView.addEventListener('attachmentsloaded', function(evt) { + var attachmentsCount = evt.detail.attachmentsCount; + + self.attachmentsButton.disabled = !attachmentsCount; + if (!attachmentsCount && self.active === SidebarView.ATTACHMENTS) { + self.switchView(SidebarView.THUMBS); + } + }); + }, + }; + + return PDFSidebar; +})(); diff --git a/web/preferences.js b/web/preferences.js index e62b10d21..f0cd46f89 100644 --- a/web/preferences.js +++ b/web/preferences.js @@ -18,13 +18,6 @@ //#include default_preferences.js -var SidebarView = { - NONE: 0, - THUMBS: 1, - OUTLINE: 2, - ATTACHMENTS: 3 -}; - /** * Preferences - Utility for storing persistent settings. * Used for settings that should be applied to all opened documents, diff --git a/web/viewer.html b/web/viewer.html index 9a366e13e..98ee82954 100644 --- a/web/viewer.html +++ b/web/viewer.html @@ -74,6 +74,7 @@ See https://github.com/adobe-type-tools/cmap-resources + diff --git a/web/viewer.js b/web/viewer.js index 506725214..1ab9ca30f 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -15,7 +15,7 @@ /* globals PDFJS, PDFBug, FirefoxCom, Stats, ProgressBar, DownloadManager, getPDFFileNameFromURL, PDFHistory, Preferences, SidebarView, ViewHistory, Stats, PDFThumbnailViewer, URL, noContextMenuHandler, - SecondaryToolbar, PasswordPrompt, PDFPresentationMode, + SecondaryToolbar, PasswordPrompt, PDFPresentationMode, PDFSidebar, PDFDocumentProperties, HandTool, Promise, PDFLinkService, PDFOutlineViewer, PDFAttachmentViewer, OverlayManager, PDFFindController, PDFFindBar, PDFViewer, PDFRenderingQueue, @@ -83,6 +83,7 @@ var mozL10n = document.mozL10n || document.webL10n; //#include pdf_document_properties.js //#include pdf_viewer.js //#include pdf_thumbnail_viewer.js +//#include pdf_sidebar.js //#include pdf_outline_viewer.js //#include pdf_attachment_viewer.js @@ -93,7 +94,6 @@ var PDFViewerApplication = { fellback: false, pdfDocument: null, pdfLoadingTask: null, - sidebarOpen: false, printing: false, /** @type {PDFViewer} */ pdfViewer: null, @@ -109,6 +109,8 @@ var PDFViewerApplication = { pdfLinkService: null, /** @type {PDFHistory} */ pdfHistory: null, + /** @type {PDFSidebar} */ + pdfSidebar: null, /** @type {PDFOutlineViewer} */ pdfOutlineViewer: null, /** @type {PDFAttachmentViewer} */ @@ -259,6 +261,25 @@ var PDFViewerApplication = { downloadManager: new DownloadManager(), }); + this.pdfSidebar = new PDFSidebar({ + pdfViewer: this.pdfViewer, + pdfThumbnailViewer: this.pdfThumbnailViewer, + pdfOutlineViewer: this.pdfOutlineViewer, + // Divs (and sidebar button) + mainContainer: document.getElementById('mainContainer'), + outerContainer: document.getElementById('outerContainer'), + toggleButton: document.getElementById('sidebarToggle'), + // Buttons + thumbnailButton: document.getElementById('viewThumbnail'), + outlineButton: document.getElementById('viewOutline'), + attachmentsButton: document.getElementById('viewAttachments'), + // Views + thumbnailView: document.getElementById('thumbnailView'), + outlineView: document.getElementById('outlineView'), + attachmentsView: document.getElementById('attachmentsView'), + }); + this.pdfSidebar.onToggled = this.forceRendering.bind(this); + var self = this; var initializedPromise = Promise.all([ Preferences.get('enableWebGL').then(function resolved(value) { @@ -540,6 +561,7 @@ var PDFViewerApplication = { this.pdfLinkService.setDocument(null, null); } + this.pdfSidebar.reset(); this.pdfOutlineViewer.reset(); this.pdfAttachmentViewer.reset(); @@ -968,40 +990,12 @@ var PDFViewerApplication = { Promise.all(promises).then(function() { pdfDocument.getOutline().then(function(outline) { self.pdfOutlineViewer.render({ outline: outline }); - - var container = document.getElementById('outlineView'); - document.getElementById('viewOutline').disabled = !outline; - - if (!outline && !container.classList.contains('hidden')) { - self.switchSidebarView('thumbs'); - } - if (outline && - self.preferenceSidebarViewOnLoad === SidebarView.OUTLINE) { - self.switchSidebarView('outline', true); - } }); pdfDocument.getAttachments().then(function(attachments) { self.pdfAttachmentViewer.render({ attachments: attachments }); - - var container = document.getElementById('attachmentsView'); - document.getElementById('viewAttachments').disabled = !attachments; - - if (!attachments && !container.classList.contains('hidden')) { - self.switchSidebarView('thumbs'); - } - if (attachments && - self.preferenceSidebarViewOnLoad === SidebarView.ATTACHMENTS) { - self.switchSidebarView('attachments', true); - } }); }); - if (self.preferenceSidebarViewOnLoad === SidebarView.THUMBS) { - Promise.all([firstPagePromise, onePageRendered]).then(function () { - self.switchSidebarView('thumbs', true); - }); - } - pdfDocument.getMetadata().then(function(data) { var info = data.info, metadata = data.metadata; self.documentInfo = info; @@ -1080,6 +1074,8 @@ var PDFViewerApplication = { document.getElementById('pageNumber').value = this.pdfViewer.currentPageNumber; + this.pdfSidebar.setInitialView(this.preferenceSidebarViewOnLoad); + if (this.initialDestination) { this.pdfLinkService.navigateTo(this.initialDestination); this.initialDestination = null; @@ -1112,83 +1108,11 @@ var PDFViewerApplication = { forceRendering: function pdfViewForceRendering() { this.pdfRenderingQueue.printing = this.printing; - this.pdfRenderingQueue.isThumbnailViewEnabled = this.sidebarOpen; + this.pdfRenderingQueue.isThumbnailViewEnabled = + this.pdfSidebar.isThumbnailViewVisible; this.pdfRenderingQueue.renderHighestPriority(); }, - refreshThumbnailViewer: function pdfViewRefreshThumbnailViewer() { - var pdfViewer = this.pdfViewer; - var thumbnailViewer = this.pdfThumbnailViewer; - - // set thumbnail images of rendered pages - var pagesCount = pdfViewer.pagesCount; - for (var pageIndex = 0; pageIndex < pagesCount; pageIndex++) { - var pageView = pdfViewer.getPageView(pageIndex); - if (pageView && pageView.renderingState === RenderingStates.FINISHED) { - var thumbnailView = thumbnailViewer.getThumbnail(pageIndex); - thumbnailView.setImage(pageView); - } - } - - thumbnailViewer.scrollThumbnailIntoView(this.page); - }, - - switchSidebarView: function pdfViewSwitchSidebarView(view, openSidebar) { - if (openSidebar && !this.sidebarOpen) { - document.getElementById('sidebarToggle').click(); - } - var thumbsView = document.getElementById('thumbnailView'); - var outlineView = document.getElementById('outlineView'); - var attachmentsView = document.getElementById('attachmentsView'); - - var thumbsButton = document.getElementById('viewThumbnail'); - var outlineButton = document.getElementById('viewOutline'); - var attachmentsButton = document.getElementById('viewAttachments'); - - switch (view) { - case 'thumbs': - var wasAnotherViewVisible = thumbsView.classList.contains('hidden'); - - thumbsButton.classList.add('toggled'); - outlineButton.classList.remove('toggled'); - attachmentsButton.classList.remove('toggled'); - thumbsView.classList.remove('hidden'); - outlineView.classList.add('hidden'); - attachmentsView.classList.add('hidden'); - - this.forceRendering(); - - if (wasAnotherViewVisible) { - this.pdfThumbnailViewer.ensureThumbnailVisible(this.page); - } - break; - - case 'outline': - if (outlineButton.disabled) { - return; - } - thumbsButton.classList.remove('toggled'); - outlineButton.classList.add('toggled'); - attachmentsButton.classList.remove('toggled'); - thumbsView.classList.add('hidden'); - outlineView.classList.remove('hidden'); - attachmentsView.classList.add('hidden'); - break; - - case 'attachments': - if (attachmentsButton.disabled) { - return; - } - thumbsButton.classList.remove('toggled'); - outlineButton.classList.remove('toggled'); - attachmentsButton.classList.add('toggled'); - thumbsView.classList.add('hidden'); - outlineView.classList.add('hidden'); - attachmentsView.classList.remove('hidden'); - break; - } - }, - beforePrint: function pdfViewSetupBeforePrint() { if (!this.supportsPrinting) { var printMessage = mozL10n.get('printing_not_supported', null, @@ -1527,48 +1451,18 @@ function webViewerInitialized() { // Suppress context menus for some controls document.getElementById('scaleSelect').oncontextmenu = noContextMenuHandler; - var mainContainer = document.getElementById('mainContainer'); - var outerContainer = document.getElementById('outerContainer'); - mainContainer.addEventListener('transitionend', function(e) { - if (e.target === mainContainer) { - var event = document.createEvent('UIEvents'); - event.initUIEvent('resize', false, false, window, 0); - window.dispatchEvent(event); - outerContainer.classList.remove('sidebarMoving'); - } - }, true); + document.getElementById('mainContainer').addEventListener('transitionend', + function(e) { + if (e.target === /* mainContainer */ this) { + var event = document.createEvent('UIEvents'); + event.initUIEvent('resize', false, false, window, 0); + window.dispatchEvent(event); + } + }, true); document.getElementById('sidebarToggle').addEventListener('click', function() { - this.classList.toggle('toggled'); - outerContainer.classList.add('sidebarMoving'); - outerContainer.classList.toggle('sidebarOpen'); - PDFViewerApplication.sidebarOpen = - outerContainer.classList.contains('sidebarOpen'); - if (PDFViewerApplication.sidebarOpen) { - PDFViewerApplication.refreshThumbnailViewer(); - } - PDFViewerApplication.forceRendering(); - }); - - document.getElementById('viewThumbnail').addEventListener('click', - function() { - PDFViewerApplication.switchSidebarView('thumbs'); - }); - - document.getElementById('viewOutline').addEventListener('click', - function() { - PDFViewerApplication.switchSidebarView('outline'); - }); - - document.getElementById('viewOutline').addEventListener('dblclick', - function() { - PDFViewerApplication.pdfOutlineViewer.toggleOutlineTree(); - }); - - document.getElementById('viewAttachments').addEventListener('click', - function() { - PDFViewerApplication.switchSidebarView('attachments'); + PDFViewerApplication.pdfSidebar.toggle(); }); document.getElementById('previous').addEventListener('click', @@ -1668,7 +1562,8 @@ document.addEventListener('pagerendered', function (e) { var pageIndex = pageNumber - 1; var pageView = PDFViewerApplication.pdfViewer.getPageView(pageIndex); - if (PDFViewerApplication.sidebarOpen) { + // Use the rendered page to set the corresponding thumbnail image. + if (PDFViewerApplication.pdfSidebar.isThumbnailViewVisible) { var thumbnailView = PDFViewerApplication.pdfThumbnailViewer. getThumbnail(pageIndex); thumbnailView.setImage(pageView); @@ -1736,23 +1631,26 @@ document.addEventListener('pagemode', function (evt) { return; } // Handle the 'pagemode' hash parameter, see also `PDFLinkService_setHash`. - var mode = evt.detail.mode; + var mode = evt.detail.mode, view; switch (mode) { - case 'bookmarks': - // Note: Our code calls this property 'outline', even though the - // Open Parameter specification calls it 'bookmarks'. - mode = 'outline'; - /* falls through */ case 'thumbs': + view = SidebarView.THUMBS; + break; + case 'bookmarks': + case 'outline': + view = SidebarView.OUTLINE; + break; case 'attachments': - PDFViewerApplication.switchSidebarView(mode, true); + view = SidebarView.ATTACHMENTS; break; case 'none': - if (PDFViewerApplication.sidebarOpen) { - document.getElementById('sidebarToggle').click(); - } + view = SidebarView.NONE; break; + default: + console.error('Invalid "pagemode" hash parameter: ' + mode); + return; } + PDFViewerApplication.pdfSidebar.switchView(view, /* forceOpen = */ true); }, true); document.addEventListener('namedaction', function (e) { @@ -1954,7 +1852,8 @@ window.addEventListener('pagechange', function pagechange(evt) { var page = evt.pageNumber; if (evt.previousPageNumber !== page) { document.getElementById('pageNumber').value = page; - if (PDFViewerApplication.sidebarOpen) { + + if (PDFViewerApplication.pdfSidebar.isThumbnailViewVisible) { PDFViewerApplication.pdfThumbnailViewer.scrollThumbnailIntoView(page); } }