diff --git a/extensions/firefox/tools/l10n.js b/extensions/firefox/tools/l10n.js index 2e0684e06..04346ee1d 100644 --- a/extensions/firefox/tools/l10n.js +++ b/extensions/firefox/tools/l10n.js @@ -5,6 +5,7 @@ (function(window) { var gLanguage = ''; var gExternalLocalizerServices = null; + var gReadyState = 'loading'; // fetch an l10n objects function getL10nData(key) { @@ -99,6 +100,8 @@ translateFragment(); + gReadyState = 'complete'; + // fire a 'localized' DOM event var evtObject = document.createEvent('Event'); evtObject.initEvent('localized', false, false); @@ -135,6 +138,8 @@ return (rtlList.indexOf(shortCode) >= 0) ? 'rtl' : 'ltr'; }, + getReadyState: function() { return gReadyState; }, + setExternalLocalizerServices: function (externalLocalizerServices) { gExternalLocalizerServices = externalLocalizerServices; diff --git a/external/webL10n/l10n.js b/external/webL10n/l10n.js index 3d5ecffaf..2190c5385 100644 --- a/external/webL10n/l10n.js +++ b/external/webL10n/l10n.js @@ -101,9 +101,7 @@ document.webL10n = (function(window, document, undefined) { function xhrLoadText(url, onSuccess, onFailure) { onSuccess = onSuccess || function _onSuccess(data) {}; - onFailure = onFailure || function _onFailure() { - console.warn(url + ' not found.'); - }; + onFailure = onFailure || function _onFailure() {}; var xhr = new XMLHttpRequest(); xhr.open('GET', url, gAsyncResourceLoading); @@ -244,7 +242,10 @@ document.webL10n = (function(window, document, undefined) { function loadImport(url, callback) { xhrLoadText(url, function(content) { parseRawLines(content, false, callback); // don't allow recursive imports - }, null); + }, function () { + console.warn(url + ' not found.'); + callback(); + }); } // fill the dictionary diff --git a/web/app.js b/web/app.js index 69f268fe1..b53d779c5 100644 --- a/web/app.js +++ b/web/app.js @@ -22,20 +22,22 @@ 'pdfjs-web/download_manager', 'pdfjs-web/pdf_history', 'pdfjs-web/preferences', 'pdfjs-web/pdf_sidebar', 'pdfjs-web/view_history', 'pdfjs-web/pdf_thumbnail_viewer', - 'pdfjs-web/secondary_toolbar', 'pdfjs-web/password_prompt', - 'pdfjs-web/pdf_presentation_mode', 'pdfjs-web/pdf_document_properties', - 'pdfjs-web/hand_tool', 'pdfjs-web/pdf_viewer', - 'pdfjs-web/pdf_rendering_queue', 'pdfjs-web/pdf_link_service', - 'pdfjs-web/pdf_outline_viewer', 'pdfjs-web/overlay_manager', - 'pdfjs-web/pdf_attachment_viewer', 'pdfjs-web/pdf_find_controller', - 'pdfjs-web/pdf_find_bar', 'pdfjs-web/dom_events', 'pdfjs-web/pdfjs'], + 'pdfjs-web/toolbar', 'pdfjs-web/secondary_toolbar', + 'pdfjs-web/password_prompt', 'pdfjs-web/pdf_presentation_mode', + 'pdfjs-web/pdf_document_properties', 'pdfjs-web/hand_tool', + 'pdfjs-web/pdf_viewer', 'pdfjs-web/pdf_rendering_queue', + 'pdfjs-web/pdf_link_service', 'pdfjs-web/pdf_outline_viewer', + 'pdfjs-web/overlay_manager', 'pdfjs-web/pdf_attachment_viewer', + 'pdfjs-web/pdf_find_controller', 'pdfjs-web/pdf_find_bar', + 'pdfjs-web/dom_events', 'pdfjs-web/pdfjs'], factory); } else if (typeof exports !== 'undefined') { factory(exports, require('./ui_utils.js'), require('./download_manager.js'), require('./pdf_history.js'), require('./preferences.js'), require('./pdf_sidebar.js'), require('./view_history.js'), - require('./pdf_thumbnail_viewer.js'), require('./secondary_toolbar.js'), - require('./password_prompt.js'), require('./pdf_presentation_mode.js'), + require('./pdf_thumbnail_viewer.js'), require('./toolbar.js'), + require('./secondary_toolbar.js'), require('./password_prompt.js'), + require('./pdf_presentation_mode.js'), require('./pdf_document_properties.js'), require('./hand_tool.js'), require('./pdf_viewer.js'), require('./pdf_rendering_queue.js'), require('./pdf_link_service.js'), require('./pdf_outline_viewer.js'), @@ -47,25 +49,28 @@ root.pdfjsWebDownloadManager, root.pdfjsWebPDFHistory, root.pdfjsWebPreferences, root.pdfjsWebPDFSidebar, root.pdfjsWebViewHistory, root.pdfjsWebPDFThumbnailViewer, - root.pdfjsWebSecondaryToolbar, root.pdfjsWebPasswordPrompt, - root.pdfjsWebPDFPresentationMode, root.pdfjsWebPDFDocumentProperties, - root.pdfjsWebHandTool, root.pdfjsWebPDFViewer, - root.pdfjsWebPDFRenderingQueue, root.pdfjsWebPDFLinkService, - root.pdfjsWebPDFOutlineViewer, root.pdfjsWebOverlayManager, - root.pdfjsWebPDFAttachmentViewer, root.pdfjsWebPDFFindController, - root.pdfjsWebPDFFindBar, root.pdfjsWebDOMEvents, root.pdfjsWebPDFJS); + root.pdfjsWebToolbar, root.pdfjsWebSecondaryToolbar, + root.pdfjsWebPasswordPrompt, root.pdfjsWebPDFPresentationMode, + root.pdfjsWebPDFDocumentProperties, root.pdfjsWebHandTool, + root.pdfjsWebPDFViewer, root.pdfjsWebPDFRenderingQueue, + root.pdfjsWebPDFLinkService, root.pdfjsWebPDFOutlineViewer, + root.pdfjsWebOverlayManager, root.pdfjsWebPDFAttachmentViewer, + root.pdfjsWebPDFFindController, root.pdfjsWebPDFFindBar, + root.pdfjsWebDOMEvents, root.pdfjsWebPDFJS); } }(this, function (exports, uiUtilsLib, downloadManagerLib, pdfHistoryLib, preferencesLib, pdfSidebarLib, viewHistoryLib, - pdfThumbnailViewerLib, secondaryToolbarLib, passwordPromptLib, - pdfPresentationModeLib, pdfDocumentPropertiesLib, handToolLib, - pdfViewerLib, pdfRenderingQueueLib, pdfLinkServiceLib, - pdfOutlineViewerLib, overlayManagerLib, - pdfAttachmentViewerLib, pdfFindControllerLib, pdfFindBarLib, - domEventsLib, pdfjsLib) { + pdfThumbnailViewerLib, toolbarLib, secondaryToolbarLib, + passwordPromptLib, pdfPresentationModeLib, + pdfDocumentPropertiesLib, handToolLib, pdfViewerLib, + pdfRenderingQueueLib, pdfLinkServiceLib, pdfOutlineViewerLib, + overlayManagerLib, pdfAttachmentViewerLib, + pdfFindControllerLib, pdfFindBarLib, domEventsLib, pdfjsLib) { var UNKNOWN_SCALE = uiUtilsLib.UNKNOWN_SCALE; var DEFAULT_SCALE_VALUE = uiUtilsLib.DEFAULT_SCALE_VALUE; +var MIN_SCALE = uiUtilsLib.MIN_SCALE; +var MAX_SCALE = uiUtilsLib.MAX_SCALE; var ProgressBar = uiUtilsLib.ProgressBar; var getPDFFileNameFromURL = uiUtilsLib.getPDFFileNameFromURL; var noContextMenuHandler = uiUtilsLib.noContextMenuHandler; @@ -77,6 +82,7 @@ var SidebarView = pdfSidebarLib.SidebarView; var PDFSidebar = pdfSidebarLib.PDFSidebar; var ViewHistory = viewHistoryLib.ViewHistory; var PDFThumbnailViewer = pdfThumbnailViewerLib.PDFThumbnailViewer; +var Toolbar = toolbarLib.Toolbar; var SecondaryToolbar = secondaryToolbarLib.SecondaryToolbar; var PasswordPrompt = passwordPromptLib.PasswordPrompt; var PDFPresentationMode = pdfPresentationModeLib.PDFPresentationMode; @@ -94,13 +100,10 @@ var PDFFindController = pdfFindControllerLib.PDFFindController; var PDFFindBar = pdfFindBarLib.PDFFindBar; var getGlobalEventBus = domEventsLib.getGlobalEventBus; var normalizeWheelEventDelta = uiUtilsLib.normalizeWheelEventDelta; +var animationStarted = uiUtilsLib.animationStarted; +var localized = uiUtilsLib.localized; var DEFAULT_SCALE_DELTA = 1.1; -var MIN_SCALE = 0.25; -var MAX_SCALE = 10.0; -var SCALE_SELECT_CONTAINER_PADDING = 8; -var SCALE_SELECT_PADDING = 22; -var PAGE_NUMBER_LOADING_INDICATOR = 'visiblePageIsLoading'; var DISABLE_AUTO_FETCH_LOADING_BAR_TIMEOUT = 5000; function configure(PDFJS) { @@ -169,11 +172,14 @@ var PDFViewerApplication = { store: null, /** @type {DownloadManager} */ downloadManager: null, + /** @type {Toolbar} */ + toolbar: null, + /** @type {SecondaryToolbar} */ + secondaryToolbar: null, /** @type {EventBus} */ eventBus: null, pageRotation: 0, isInitialViewSet: false, - animationStartedPromise: null, preferenceSidebarViewOnLoad: SidebarView.NONE, preferencePdfBugEnabled: false, preferenceShowPreviousViewOnLoad: true, @@ -183,7 +189,6 @@ var PDFViewerApplication = { url: '', baseUrl: '', externalServices: DefaultExernalServices, - hasPageLabels: false, // called once when the document is loaded initialize: function pdfViewInitialize(appConfig) { @@ -274,6 +279,8 @@ var PDFViewerApplication = { this.pdfDocumentProperties = new PDFDocumentProperties(appConfig.documentProperties); + this.toolbar = new Toolbar(appConfig.toolbar, container, eventBus); + this.secondaryToolbar = new SecondaryToolbar(appConfig.secondaryToolbar, container, eventBus); @@ -572,7 +579,6 @@ var PDFViewerApplication = { } this.store = null; this.isInitialViewSet = false; - this.hasPageLabels = false; this.pdfSidebar.reset(); this.pdfOutlineViewer.reset(); @@ -580,6 +586,8 @@ var PDFViewerApplication = { this.findController.reset(); this.findBar.reset(); + this.toolbar.reset(); + this.secondaryToolbar.reset(); if (typeof PDFBug !== 'undefined') { PDFBug.cleanup(); @@ -859,9 +867,8 @@ var PDFViewerApplication = { self.loadingBar.hide(); }); - this._updateUIToolbar({ - resetNumPages: true, - }); + this.toolbar.setPagesCount(pdfDocument.numPages, false); + this.secondaryToolbar.setPagesCount(pdfDocument.numPages); var id = this.documentFingerprint = pdfDocument.fingerprint; var store = this.store = new ViewHistory(id); @@ -987,10 +994,11 @@ var PDFViewerApplication = { pdfViewer.setPageLabels(labels); pdfThumbnailViewer.setPageLabels(labels); - self.hasPageLabels = true; - self._updateUIToolbar({ - resetNumPages: true, - }); + // Changing toolbar page display to use labels and we need to set + // the label of the current page. + self.toolbar.setPagesCount(pdfDocument.numPages, true); + self.toolbar.setPageNumber(pdfViewer.currentPageNumber, + pdfViewer.currentPageLabel); }); pagesPromise.then(function() { @@ -1015,8 +1023,7 @@ var PDFViewerApplication = { } }); - Promise.all([onePageRendered, this.animationStartedPromise]).then( - function() { + Promise.all([onePageRendered, animationStarted]).then(function() { pdfDocument.getOutline().then(function(outline) { self.pdfOutlineViewer.render({ outline: outline }); }); @@ -1114,6 +1121,12 @@ var PDFViewerApplication = { this.page = 1; } + // Ensure that the correct page number is displayed in the UI, + // even if the active page didn't change during document load. + this.toolbar.setPageNumber(this.pdfViewer.currentPageNumber, + this.pdfViewer.currentPageLabel); + this.secondaryToolbar.setPageNumber(this.pdfViewer.currentPageNumber); + if (!this.pdfViewer.currentScaleValue) { // Scale was not initialized: invalid bookmark or scale was not specified. // Setting the default one. @@ -1217,87 +1230,10 @@ var PDFViewerApplication = { this.pdfPresentationMode.request(); }, - /** - * @typedef UpdateUIToolbarParameters - * @property {number} pageNumber - * @property {string} pageLabel - * @property {string} scaleValue - * @property {number} scale - * @property {boolean} resetNumPages - */ - - /** - * @param {Object} UpdateUIToolbarParameters - * @private - */ - _updateUIToolbar: function (params) { - function selectScaleOption(value, scale) { - var options = toolbarConfig.scaleSelect.options; - var predefinedValueFound = false; - for (var i = 0, ii = options.length; i < ii; i++) { - var option = options[i]; - if (option.value !== value) { - option.selected = false; - continue; - } - option.selected = true; - predefinedValueFound = true; - } - if (!predefinedValueFound) { - var customScale = Math.round(scale * 10000) / 100; - toolbarConfig.customScaleOption.textContent = - mozL10n.get('page_scale_percent', {scale: customScale}, '{{scale}}%'); - toolbarConfig.customScaleOption.selected = true; - } - } - - var pageNumber = params.pageNumber || this.pdfViewer.currentPageNumber; - var scaleValue = (params.scaleValue || params.scale || - this.pdfViewer.currentScaleValue || DEFAULT_SCALE_VALUE).toString(); - var scale = params.scale || this.pdfViewer.currentScale; - var resetNumPages = params.resetNumPages || false; - - var toolbarConfig = this.appConfig.toolbar; - var pagesCount = this.pagesCount; - - if (resetNumPages) { - if (this.hasPageLabels) { - toolbarConfig.pageNumber.type = 'text'; - } else { - toolbarConfig.pageNumber.type = 'number'; - toolbarConfig.numPages.textContent = mozL10n.get('of_pages', - { pagesCount: pagesCount }, 'of {{pagesCount}}'); - } - toolbarConfig.pageNumber.max = pagesCount; - } - - if (this.hasPageLabels) { - toolbarConfig.pageNumber.value = params.pageLabel || - this.pdfViewer.currentPageLabel; - toolbarConfig.numPages.textContent = mozL10n.get('page_of_pages', - { pageNumber: pageNumber, pagesCount: pagesCount }, - '({{pageNumber}} of {{pagesCount}})'); - } else { - toolbarConfig.pageNumber.value = pageNumber; - } - - toolbarConfig.previous.disabled = (pageNumber <= 1); - toolbarConfig.next.disabled = (pageNumber >= pagesCount); - - toolbarConfig.firstPage.disabled = (pageNumber <= 1); - toolbarConfig.lastPage.disabled = (pageNumber >= pagesCount); - - toolbarConfig.zoomOut.disabled = (scale <= MIN_SCALE); - toolbarConfig.zoomIn.disabled = (scale >= MAX_SCALE); - - selectScaleOption(scaleValue, scale); - }, - bindEvents: function pdfViewBindEvents() { var eventBus = this.eventBus; eventBus.on('resize', webViewerResize); - eventBus.on('localized', webViewerLocalized); eventBus.on('hashchange', webViewerHashchange); eventBus.on('beforeprint', this.beforePrint.bind(this)); eventBus.on('afterprint', this.afterPrint.bind(this)); @@ -1316,6 +1252,12 @@ var PDFViewerApplication = { eventBus.on('download', webViewerDownload); eventBus.on('firstpage', webViewerFirstPage); eventBus.on('lastpage', webViewerLastPage); + eventBus.on('nextpage', webViewerNextPage); + eventBus.on('previouspage', webViewerPreviousPage); + eventBus.on('zoomin', webViewerZoomIn); + eventBus.on('zoomout', webViewerZoomOut); + eventBus.on('pagenumberchanged', webViewerPageNumberChanged); + eventBus.on('scalechanged', webViewerScaleChanged); eventBus.on('rotatecw', webViewerRotateCw); eventBus.on('rotateccw', webViewerRotateCcw); eventBus.on('documentproperties', webViewerDocumentProperties); @@ -1509,9 +1451,6 @@ function webViewerInitialized() { appConfig.toolbar.viewFind.classList.add('hidden'); } - // Suppress context menus for some controls - appConfig.toolbar.scaleSelect.oncontextmenu = noContextMenuHandler; - appConfig.sidebar.mainContainer.addEventListener('transitionend', function(e) { if (e.target === /* mainContainer */ this) { @@ -1523,63 +1462,6 @@ function webViewerInitialized() { PDFViewerApplication.pdfSidebar.toggle(); }); - appConfig.toolbar.previous.addEventListener('click', function() { - PDFViewerApplication.page--; - }); - - appConfig.toolbar.next.addEventListener('click', function() { - PDFViewerApplication.page++; - }); - - appConfig.toolbar.zoomIn.addEventListener('click', function() { - PDFViewerApplication.zoomIn(); - }); - - appConfig.toolbar.zoomOut.addEventListener('click', function() { - PDFViewerApplication.zoomOut(); - }); - - appConfig.toolbar.pageNumber.addEventListener('click', function() { - this.select(); - }); - - appConfig.toolbar.pageNumber.addEventListener('change', function() { - var pdfViewer = PDFViewerApplication.pdfViewer; - pdfViewer.currentPageLabel = this.value; - - // Ensure that the page number input displays the correct value, even if the - // value entered by the user was invalid (e.g. a floating point number). - if (this.value !== pdfViewer.currentPageNumber.toString() && - this.value !== pdfViewer.currentPageLabel) { - PDFViewerApplication._updateUIToolbar({}); - } - }); - - appConfig.toolbar.scaleSelect.addEventListener('change', function() { - if (this.value === 'custom') { - return; - } - PDFViewerApplication.pdfViewer.currentScaleValue = this.value; - }); - - appConfig.toolbar.presentationModeButton.addEventListener('click', - function (e) { - PDFViewerApplication.eventBus.dispatch('presentationmode'); - - }); - - appConfig.toolbar.openFile.addEventListener('click', function (e) { - PDFViewerApplication.eventBus.dispatch('openfile'); - }); - - appConfig.toolbar.print.addEventListener('click', function (e) { - PDFViewerApplication.eventBus.dispatch('print'); - }); - - appConfig.toolbar.download.addEventListener('click', function (e) { - PDFViewerApplication.eventBus.dispatch('download'); - }); - Promise.all(waitForBeforeOpening).then(function () { webViewerOpenFileViaURL(file); }).catch(function (reason) { @@ -1636,8 +1518,7 @@ function webViewerPageRendered(e) { // If the page is still visible when it has finished rendering, // ensure that the page number input loading indicator is hidden. if (pageNumber === PDFViewerApplication.page) { - var pageNumberInput = PDFViewerApplication.appConfig.toolbar.pageNumber; - pageNumberInput.classList.remove(PAGE_NUMBER_LOADING_INDICATOR); + PDFViewerApplication.toolbar.updateLoadingIndicatorState(false); } // Prevent errors in the edge-case where the PDF document is removed *before* @@ -1789,15 +1670,10 @@ function webViewerUpdateViewarea(e) { location.pageNumber); // Show/hide the loading indicator in the page number input element. - var pageNumberInput = PDFViewerApplication.appConfig.toolbar.pageNumber; var currentPage = PDFViewerApplication.pdfViewer.getPageView(PDFViewerApplication.page - 1); - - if (currentPage.renderingState === RenderingStates.FINISHED) { - pageNumberInput.classList.remove(PAGE_NUMBER_LOADING_INDICATOR); - } else { - pageNumberInput.classList.add(PAGE_NUMBER_LOADING_INDICATOR); - } + var loading = currentPage.renderingState !== RenderingStates.FINISHED; + PDFViewerApplication.toolbar.updateLoadingIndicatorState(loading); } window.addEventListener('resize', function webViewerResize(evt) { @@ -1884,31 +1760,9 @@ if (typeof PDFJSDev === 'undefined' || PDFJSDev.test('GENERIC')) { }; } -window.addEventListener('localized', function localized(evt) { - PDFViewerApplication.eventBus.dispatch('localized'); -}); - function webViewerLocalized() { document.getElementsByTagName('html')[0].dir = mozL10n.getDirection(); - - PDFViewerApplication.animationStartedPromise.then(function() { - // Adjust the width of the zoom box to fit the content. - // Note: If the window is narrow enough that the zoom box is not visible, - // we temporarily show it to be able to adjust its width. - var container = PDFViewerApplication.appConfig.toolbar.scaleSelectContainer; - if (container.clientWidth === 0) { - container.setAttribute('style', 'display: inherit;'); - } - if (container.clientWidth > 0) { - var select = PDFViewerApplication.appConfig.toolbar.scaleSelect; - select.setAttribute('style', 'min-width: inherit;'); - var width = select.clientWidth + SCALE_SELECT_CONTAINER_PADDING; - select.setAttribute('style', 'min-width: ' + - (width + SCALE_SELECT_PADDING) + 'px;'); - container.setAttribute('style', 'min-width: ' + width + 'px; ' + - 'max-width: ' + width + 'px;'); - } - }); + PDFViewerApplication.eventBus.dispatch('localized'); } function webViewerPresentationMode() { @@ -1934,6 +1788,33 @@ function webViewerLastPage() { PDFViewerApplication.page = PDFViewerApplication.pagesCount; } } +function webViewerNextPage() { + PDFViewerApplication.page++; +} +function webViewerPreviousPage() { + PDFViewerApplication.page--; +} +function webViewerZoomIn() { + PDFViewerApplication.zoomIn(); +} +function webViewerZoomOut() { + PDFViewerApplication.zoomOut(); +} +function webViewerPageNumberChanged(e) { + var pdfViewer = PDFViewerApplication.pdfViewer; + pdfViewer.currentPageLabel = e.value; + + // Ensure that the page number input displays the correct value, even if the + // value entered by the user was invalid (e.g. a floating point number). + if (e.value !== pdfViewer.currentPageNumber.toString() && + e.value !== pdfViewer.currentPageLabel) { + PDFViewerApplication.toolbar.setPageNumber( + pdfViewer.currentPageNumber, pdfViewer.currentPageLabel); + } +} +function webViewerScaleChanged(e) { + PDFViewerApplication.pdfViewer.currentScaleValue = e.value; +} function webViewerRotateCw() { PDFViewerApplication.rotatePages(90); } @@ -1965,10 +1846,7 @@ function webViewerFindFromUrlHash(e) { } function webViewerScaleChanging(e) { - PDFViewerApplication._updateUIToolbar({ - scaleValue: e.presetValue, - scale: e.scale, - }); + PDFViewerApplication.toolbar.setPageScale(e.presetValue, e.scale); if (!PDFViewerApplication.initialized) { return; @@ -1979,10 +1857,8 @@ function webViewerScaleChanging(e) { function webViewerPageChanging(e) { var page = e.pageNumber; - PDFViewerApplication._updateUIToolbar({ - pageNumber: page, - pageLabel: e.pageLabel, - }); + PDFViewerApplication.toolbar.setPageNumber(page, e.pageLabel || null); + PDFViewerApplication.secondaryToolbar.setPageNumber(page); if (PDFViewerApplication.pdfSidebar.isThumbnailViewVisible) { PDFViewerApplication.pdfThumbnailViewer.scrollThumbnailIntoView(page); @@ -2332,14 +2208,7 @@ window.addEventListener('afterprint', function afterPrint(evt) { PDFViewerApplication.eventBus.dispatch('afterprint'); }); -(function animationStartedClosure() { - // The offsetParent is not set until the pdf.js iframe or object is visible. - // Waiting for first animation. - PDFViewerApplication.animationStartedPromise = new Promise( - function (resolve) { - window.requestAnimationFrame(resolve); - }); -})(); +localized.then(webViewerLocalized); /* Abstract factory for the print service. */ var PDFPrintServiceFactory = { diff --git a/web/secondary_toolbar.js b/web/secondary_toolbar.js index d4671bd5d..eb43de586 100644 --- a/web/secondary_toolbar.js +++ b/web/secondary_toolbar.js @@ -91,6 +91,12 @@ var SecondaryToolbar = (function SecondaryToolbarClosure() { { element: options.documentPropertiesButton, eventName: 'documentproperties', close: true } ]; + this.items = { + firstPage: options.firstPageButton, + lastPage: options.lastPageButton, + pageRotateCw: options.pageRotateCwButton, + pageRotateCcw: options.pageRotateCcwButton, + }; this.mainContainer = mainContainer; this.eventBus = eventBus; @@ -99,6 +105,8 @@ var SecondaryToolbar = (function SecondaryToolbarClosure() { this.containerHeight = null; this.previousContainerHeight = null; + this.reset(); + // Bind the event listeners for click and hand tool actions. this._bindClickListeners(); this._bindHandToolListener(options.toggleHandToolButton); @@ -115,6 +123,31 @@ var SecondaryToolbar = (function SecondaryToolbarClosure() { return this.opened; }, + setPageNumber: function SecondaryToolbar_setPageNumber(pageNumber) { + this.pageNumber = pageNumber; + this._updateUIState(); + }, + + setPagesCount: function SecondaryToolbar_setPagesCount(pagesCount) { + this.pagesCount = pagesCount; + this._updateUIState(); + }, + + reset: function SecondaryToolbar_reset() { + this.pageNumber = 0; + this.pagesCount = 0; + this._updateUIState(); + }, + + _updateUIState: function SecondaryToolbar_updateUIState() { + var items = this.items; + + items.firstPage.disabled = (this.pageNumber <= 1); + items.lastPage.disabled = (this.pageNumber >= this.pagesCount); + items.pageRotateCw.disabled = this.pagesCount === 0; + items.pageRotateCcw.disabled = this.pagesCount === 0; + }, + _bindClickListeners: function SecondaryToolbar_bindClickListeners() { // Button to toggle the visibility of the secondary toolbar. this.toggleButton.addEventListener('click', this.toggle.bind(this)); diff --git a/web/toolbar.js b/web/toolbar.js new file mode 100644 index 000000000..776858c16 --- /dev/null +++ b/web/toolbar.js @@ -0,0 +1,290 @@ +/* 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. + */ + +'use strict'; + +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + define('pdfjs-web/toolbar', ['exports', 'pdfjs-web/ui_utils'], + factory); + } else if (typeof exports !== 'undefined') { + factory(exports, require('./ui_utils.js')); + } else { + factory((root.pdfjsWebToolbar = {}), root.pdfjsWebUIUtils); + } +}(this, function (exports, uiUtils) { + +var mozL10n = uiUtils.mozL10n; +var noContextMenuHandler = uiUtils.noContextMenuHandler; +var animationStarted = uiUtils.animationStarted; +var localized = uiUtils.localized; + +var DEFAULT_SCALE_VALUE = uiUtils.DEFAULT_SCALE_VALUE; +var DEFAULT_SCALE = uiUtils.DEFAULT_SCALE; +var MIN_SCALE = uiUtils.MIN_SCALE; +var MAX_SCALE = uiUtils.MAX_SCALE; + +var PAGE_NUMBER_LOADING_INDICATOR = 'visiblePageIsLoading'; +var SCALE_SELECT_CONTAINER_PADDING = 8; +var SCALE_SELECT_PADDING = 22; + +/** + * @typedef {Object} ToolbarOptions + * @property {HTMLDivElement} container - Container for the secondary toolbar. + * @property {HTMLSpanElement} numPages - Label that contains number of pages. + * @property {HTMLInputElement} pageNumber - Control for display and user input + * of the current page number. + * @property {HTMLSpanElement} scaleSelectContainer - Container where scale + * controls are placed. The width is adjusted on UI initialization. + * @property {HTMLSelectElement} scaleSelect - Scale selection control. + * @property {HTMLOptionElement} customScaleOption - The item used to display + * a non-predefined scale. + * @property {HTMLButtonElement} previous - Button to go to the previous page. + * @property {HTMLButtonElement} next - Button to go to the next page. + * @property {HTMLButtonElement} zoomIn - Button to zoom in the pages. + * @property {HTMLButtonElement} zoomOut - Button to zoom out the pages. + * @property {HTMLButtonElement} viewFind - Button to open find bar. + * @property {HTMLButtonElement} openFile - Button to open a new document. + * @property {HTMLButtonElement} presentationModeButton - Button to switch to + * presentation mode. + * @property {HTMLButtonElement} download - Button to download the document. + * @property {HTMLAElement} viewBookmark - Element to link current url of + * the page view. + */ + +/** + * @class + */ +var Toolbar = (function ToolbarClosure() { + /** + * @constructs Toolbar + * @param {ToolbarOptions} options + * @param {HTMLDivElement} mainContainer + * @param {EventBus} eventBus + */ + function Toolbar(options, mainContainer, eventBus) { + this.toolbar = options.container; + this.mainContainer = mainContainer; + this.eventBus = eventBus; + this.items = options; + + this._wasLocalized = false; + this.reset(); + + // Bind the event listeners for click and hand tool actions. + this._bindListeners(); + } + + Toolbar.prototype = { + setPageNumber: function (pageNumber, pageLabel) { + this.pageNumber = pageNumber; + this.pageLabel = pageLabel; + this._updateUIState(false); + }, + + setPagesCount: function (pagesCount, hasPageLabels) { + this.pagesCount = pagesCount; + this.hasPageLabels = hasPageLabels; + this._updateUIState(true); + }, + + setPageScale: function (pageScaleValue, pageScale) { + this.pageScaleValue = pageScaleValue; + this.pageScale = pageScale; + this._updateUIState(false); + }, + + reset: function () { + this.pageNumber = 0; + this.pageLabel = null; + this.hasPageLabels = false; + this.pagesCount = 0; + this.pageScaleValue = DEFAULT_SCALE_VALUE; + this.pageScale = DEFAULT_SCALE; + this._updateUIState(true); + }, + + _bindListeners: function Toolbar_bindClickListeners() { + var eventBus = this.eventBus; + var self = this; + var items = this.items; + + items.previous.addEventListener('click', function() { + eventBus.dispatch('previouspage'); + }); + + items.next.addEventListener('click', function() { + eventBus.dispatch('nextpage'); + }); + + items.zoomIn.addEventListener('click', function() { + eventBus.dispatch('zoomin'); + }); + + items.zoomOut.addEventListener('click', function() { + eventBus.dispatch('zoomout'); + }); + + items.pageNumber.addEventListener('click', function() { + this.select(); + }); + + items.pageNumber.addEventListener('change', function() { + eventBus.dispatch('pagenumberchanged', { + source: self, + value: this.value + }); + }); + + items.scaleSelect.addEventListener('change', function() { + if (this.value === 'custom') { + return; + } + eventBus.dispatch('scalechanged', { + source: self, + value: this.value + }); + }); + + items.presentationModeButton.addEventListener('click', + function (e) { + eventBus.dispatch('presentationmode'); + }); + + items.openFile.addEventListener('click', function (e) { + eventBus.dispatch('openfile'); + }); + + items.print.addEventListener('click', function (e) { + eventBus.dispatch('print'); + }); + + items.download.addEventListener('click', function (e) { + eventBus.dispatch('download'); + }); + + // Suppress context menus for some controls + items.scaleSelect.oncontextmenu = noContextMenuHandler; + + localized.then(this._localized.bind(this)); + }, + + _localized: function Toolbar_localized() { + this._wasLocalized = true; + this._adjustScaleWidth(); + this._updateUIState(true); + }, + + _updateUIState: function Toolbar_updateUIState(resetNumPages) { + function selectScaleOption(value, scale) { + var options = items.scaleSelect.options; + var predefinedValueFound = false; + for (var i = 0, ii = options.length; i < ii; i++) { + var option = options[i]; + if (option.value !== value) { + option.selected = false; + continue; + } + option.selected = true; + predefinedValueFound = true; + } + if (!predefinedValueFound) { + var customScale = Math.round(scale * 10000) / 100; + items.customScaleOption.textContent = + mozL10n.get('page_scale_percent', {scale: customScale}, + '{{scale}}%'); + items.customScaleOption.selected = true; + } + } + + if (!this._wasLocalized) { + // Don't update UI state until we will localize the toolbar. + return; + } + + var pageNumber = this.pageNumber; + var scaleValue = (this.pageScaleValue || this.pageScale).toString(); + var scale = this.pageScale; + + var items = this.items; + var pagesCount = this.pagesCount; + + if (resetNumPages) { + if (this.hasPageLabels) { + items.pageNumber.type = 'text'; + } else { + items.pageNumber.type = 'number'; + items.numPages.textContent = mozL10n.get('of_pages', + { pagesCount: pagesCount }, 'of {{pagesCount}}'); + } + items.pageNumber.max = pagesCount; + } + + if (this.hasPageLabels) { + items.pageNumber.value = this.pageLabel; + items.numPages.textContent = mozL10n.get('page_of_pages', + { pageNumber: pageNumber, pagesCount: pagesCount }, + '({{pageNumber}} of {{pagesCount}})'); + } else { + items.pageNumber.value = pageNumber; + } + + items.previous.disabled = (pageNumber <= 1); + items.next.disabled = (pageNumber >= pagesCount); + + items.zoomOut.disabled = (scale <= MIN_SCALE); + items.zoomIn.disabled = (scale >= MAX_SCALE); + + selectScaleOption(scaleValue, scale); + }, + + updateLoadingIndicatorState: + function Toolbar_updateLoadingIndicatorState(loading) { + var pageNumberInput = this.items.pageNumber; + + if (loading) { + pageNumberInput.classList.add(PAGE_NUMBER_LOADING_INDICATOR); + } else { + pageNumberInput.classList.remove(PAGE_NUMBER_LOADING_INDICATOR); + } + }, + + _adjustScaleWidth: function Toolbar_adjustScaleWidth() { + var container = this.items.scaleSelectContainer; + var select = this.items.scaleSelect; + animationStarted.then(function() { + // Adjust the width of the zoom box to fit the content. + // Note: If the window is narrow enough that the zoom box is not + // visible, we temporarily show it to be able to adjust its width. + if (container.clientWidth === 0) { + container.setAttribute('style', 'display: inherit;'); + } + if (container.clientWidth > 0) { + select.setAttribute('style', 'min-width: inherit;'); + var width = select.clientWidth + SCALE_SELECT_CONTAINER_PADDING; + select.setAttribute('style', 'min-width: ' + + (width + SCALE_SELECT_PADDING) + 'px;'); + container.setAttribute('style', 'min-width: ' + width + 'px; ' + + 'max-width: ' + width + 'px;'); + } + }); + }, + }; + + return Toolbar; +})(); + +exports.Toolbar = Toolbar; +})); diff --git a/web/ui_utils.js b/web/ui_utils.js index 4cf5c3076..9679c4235 100644 --- a/web/ui_utils.js +++ b/web/ui_utils.js @@ -28,6 +28,8 @@ var CSS_UNITS = 96.0 / 72.0; var DEFAULT_SCALE_VALUE = 'auto'; var DEFAULT_SCALE = 1.0; +var MIN_SCALE = 0.25; +var MAX_SCALE = 10.0; var UNKNOWN_SCALE = 0; var MAX_AUTO_SCALE = 1.25; var SCROLLBAR_PADDING = 40; @@ -122,7 +124,7 @@ function getOutputScale(ctx) { function scrollIntoView(element, spot, skipOverflowHiddenElements) { // Assuming offsetParent is available (it's not available when viewer is in // hidden iframe or object). We have to scroll: if the offsetParent is not set - // producing the error. See also animationStartedClosure. + // producing the error. See also animationStarted. var parent = element.offsetParent; if (!parent) { console.error('offsetParent is not set -- cannot scroll'); @@ -408,6 +410,30 @@ function normalizeWheelEventDelta(evt) { return delta; } +/** + * Promise that is resolved when DOM window becomes visible. + */ +var animationStarted = new Promise(function (resolve) { + window.requestAnimationFrame(resolve); +}); + +/** + * Promise that is resolved when UI localization is finished. + */ +var localized = new Promise(function (resolve, reject) { + if (!mozL10n) { + reject(new Error('mozL10n service is not available.')); + return; + } + if (mozL10n.getReadyState() !== 'loading') { + resolve(); + return; + } + window.addEventListener('localized', function localized(evt) { + resolve(); + }); +}); + /** * Simple event bus for an application. Listeners are attached using the * `on` and `off` methods. To raise an event, the `dispatch` method shall be @@ -536,6 +562,8 @@ var ProgressBar = (function ProgressBarClosure() { exports.CSS_UNITS = CSS_UNITS; exports.DEFAULT_SCALE_VALUE = DEFAULT_SCALE_VALUE; exports.DEFAULT_SCALE = DEFAULT_SCALE; +exports.MIN_SCALE = MIN_SCALE; +exports.MAX_SCALE = MAX_SCALE; exports.UNKNOWN_SCALE = UNKNOWN_SCALE; exports.MAX_AUTO_SCALE = MAX_AUTO_SCALE; exports.SCROLLBAR_PADDING = SCROLLBAR_PADDING; @@ -554,4 +582,6 @@ exports.scrollIntoView = scrollIntoView; exports.watchScroll = watchScroll; exports.binarySearchFirstItem = binarySearchFirstItem; exports.normalizeWheelEventDelta = normalizeWheelEventDelta; +exports.animationStarted = animationStarted; +exports.localized = localized; })); diff --git a/web/viewer.js b/web/viewer.js index ab620a295..c4fafe512 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -65,8 +65,6 @@ function getViewerConfiguration() { customScaleOption: document.getElementById('customScaleOption'), previous: document.getElementById('previous'), next: document.getElementById('next'), - firstPage: document.getElementById('firstPage'), - lastPage: document.getElementById('lastPage'), zoomIn: document.getElementById('zoomIn'), zoomOut: document.getElementById('zoomOut'), viewFind: document.getElementById('viewFind'),