| // Copyright 2020 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.m.js'; |
| import 'chrome://resources/cr_elements/icons.m.js'; |
| import 'chrome://resources/cr_elements/shared_vars_css.m.js'; |
| import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js'; |
| import 'chrome://resources/polymer/v3_0/paper-progress/paper-progress.js'; |
| import './icons.js'; |
| // <if expr="chromeos"> |
| import './viewer-annotations-bar.js'; |
| // </if> |
| import './viewer-download-controls.js'; |
| import './viewer-page-selector.js'; |
| import './shared-css.js'; |
| import './shared-vars.js'; |
| |
| import {AnchorAlignment} from 'chrome://resources/cr_elements/cr_action_menu/cr_action_menu.m.js'; |
| import {assert} from 'chrome://resources/js/assert.m.js'; |
| import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; |
| |
| import {FittingType} from '../constants.js'; |
| import {record, UserAction} from '../metrics.js'; |
| // <if expr="chromeos"> |
| import {ViewerAnnotationsModeDialogElement} from './viewer-annotations-mode-dialog.js'; |
| // </if> |
| |
| export class ViewerToolbarElement extends PolymerElement { |
| static get is() { |
| return 'viewer-toolbar'; |
| } |
| |
| static get template() { |
| return html`{__html_template__}`; |
| } |
| |
| static get properties() { |
| return { |
| // <if expr="chromeos"> |
| annotationAvailable: Boolean, |
| annotationMode: { |
| type: Boolean, |
| value: false, |
| reflectToAttribute: true, |
| }, |
| // </if> |
| docTitle: String, |
| docLength: Number, |
| documentPropertiesEnabled: Boolean, |
| hasEdits: Boolean, |
| hasEnteredAnnotationMode: Boolean, |
| isFormFieldFocused: Boolean, |
| |
| loadProgress: { |
| type: Number, |
| observer: 'loadProgressChanged_', |
| }, |
| |
| loading_: { |
| type: Boolean, |
| reflectToAttribute: true, |
| }, |
| |
| pageNo: Number, |
| pdfAnnotationsEnabled: Boolean, |
| presentationModeEnabled: Boolean, |
| printingEnabled: Boolean, |
| rotated: Boolean, |
| viewportZoom: Number, |
| /** @type {!{min: number, max: number}} */ |
| zoomBounds: Object, |
| |
| sidenavCollapsed: Boolean, |
| twoUpViewEnabled: Boolean, |
| |
| moreMenuOpen_: { |
| type: Boolean, |
| reflectToAttribute: true, |
| }, |
| |
| fittingType_: Number, |
| |
| /** @private {string} */ |
| fitToButtonIcon_: { |
| type: String, |
| computed: 'computeFitToButtonIcon_(fittingType_)', |
| }, |
| |
| /** @private */ |
| viewportZoomPercent_: { |
| type: Number, |
| computed: 'computeViewportZoomPercent_(viewportZoom)', |
| observer: 'viewportZoomPercentChanged_', |
| }, |
| |
| // <if expr="chromeos"> |
| /** @private */ |
| showAnnotationsModeDialog_: { |
| type: Boolean, |
| value: false, |
| }, |
| |
| /** @private */ |
| showAnnotationsBar_: { |
| type: Boolean, |
| computed: 'computeShowAnnotationsBar_(' + |
| 'loading_, annotationMode, pdfAnnotationsEnabled)', |
| }, |
| // </if> |
| }; |
| } |
| |
| constructor() { |
| super(); |
| |
| /** @type {boolean} */ |
| this.sidenavCollapsed = false; |
| |
| /** @private {!FittingType} */ |
| this.fittingType_ = FittingType.FIT_TO_PAGE; |
| |
| /** @private {boolean} */ |
| this.loading_ = true; |
| |
| /** @private {boolean} */ |
| this.displayAnnotations_ = true; |
| |
| /** @private {boolean} */ |
| this.moreMenuOpen_ = false; |
| } |
| |
| /** |
| * @return {!CrActionMenuElement} |
| * @private |
| */ |
| getMenu_() { |
| return /** @type {!CrActionMenuElement} */ ( |
| this.shadowRoot.querySelector('cr-action-menu')); |
| } |
| |
| /** @private */ |
| onSidenavToggleClick_() { |
| record(UserAction.TOGGLE_SIDENAV); |
| this.dispatchEvent(new CustomEvent('sidenav-toggle-click')); |
| } |
| |
| /** |
| * @return {string} |
| * @private |
| */ |
| computeFitToButtonIcon_() { |
| return this.fittingType_ === FittingType.FIT_TO_PAGE ? 'pdf:fit-to-height' : |
| 'pdf:fit-to-width'; |
| } |
| |
| /** |
| * @return {number} |
| * @private |
| */ |
| computeViewportZoomPercent_() { |
| return Math.round(100 * this.viewportZoom); |
| } |
| |
| /** |
| * @param {string} fitToPageTooltip |
| * @param {string} fitToWidthTooltip |
| * @return {string} The appropriate tooltip for the current state |
| * @private |
| */ |
| getFitToButtonTooltip_(fitToPageTooltip, fitToWidthTooltip) { |
| return this.fittingType_ === FittingType.FIT_TO_PAGE ? fitToPageTooltip : |
| fitToWidthTooltip; |
| } |
| |
| /** @private */ |
| loadProgressChanged_() { |
| this.loading_ = this.loadProgress < 100; |
| } |
| |
| /** @private */ |
| viewportZoomPercentChanged_() { |
| this.getZoomInput_().value = `${this.viewportZoomPercent_}%`; |
| } |
| |
| // <if expr="chromeos"> |
| /** |
| * @return {boolean} |
| * @private |
| */ |
| computeShowAnnotationsBar_() { |
| return this.pdfAnnotationsEnabled && !this.loading_ && this.annotationMode; |
| } |
| // </if> |
| |
| /** @private */ |
| onPrintClick_() { |
| this.dispatchEvent(new CustomEvent('print')); |
| } |
| |
| /** @private */ |
| onRotateClick_() { |
| this.dispatchEvent(new CustomEvent('rotate-left')); |
| } |
| |
| /** @private */ |
| toggleDisplayAnnotations_() { |
| record(UserAction.TOGGLE_DISPLAY_ANNOTATIONS); |
| this.displayAnnotations_ = !this.displayAnnotations_; |
| this.dispatchEvent(new CustomEvent( |
| 'display-annotations-changed', {detail: this.displayAnnotations_})); |
| this.getMenu_().close(); |
| |
| // <if expr="chromeos"> |
| if (!this.displayAnnotations_ && this.annotationMode) { |
| this.toggleAnnotation(); |
| } |
| // </if> |
| } |
| |
| /** @private */ |
| onPresentClick_() { |
| assert(this.presentationModeEnabled); |
| record(UserAction.PRESENT); |
| this.getMenu_().close(); |
| this.dispatchEvent(new CustomEvent('present-click')); |
| } |
| |
| /** @private */ |
| onPropertiesClick_() { |
| assert(this.documentPropertiesEnabled); |
| record(UserAction.PROPERTIES); |
| this.getMenu_().close(); |
| this.dispatchEvent(new CustomEvent('properties-click')); |
| } |
| |
| /** |
| * @param {boolean} checked |
| * @return {string} |
| */ |
| getSinglePageAriaChecked_(checked) { |
| return checked ? 'false' : 'true'; |
| } |
| |
| /** |
| * @param {boolean} checked |
| * @return {string} |
| */ |
| getTwoPageViewAriaChecked_(checked) { |
| return checked ? 'true' : 'false'; |
| } |
| |
| /** |
| * @param {boolean} checked |
| * @return {string} |
| */ |
| getShowAnnotationsAriaChecked_(checked) { |
| return checked ? 'true' : 'false'; |
| } |
| |
| /** @return {string} */ |
| getAriaExpanded_() { |
| return this.sidenavCollapsed ? 'false' : 'true'; |
| } |
| |
| /** @private */ |
| toggleTwoPageViewClick_() { |
| const newTwoUpViewEnabled = !this.twoUpViewEnabled; |
| this.dispatchEvent( |
| new CustomEvent('two-up-view-changed', {detail: newTwoUpViewEnabled})); |
| this.getMenu_().close(); |
| } |
| |
| /** @private */ |
| onZoomInClick_() { |
| this.dispatchEvent(new CustomEvent('zoom-in')); |
| } |
| |
| /** @private */ |
| onZoomOutClick_() { |
| this.dispatchEvent(new CustomEvent('zoom-out')); |
| } |
| |
| /** @param {!FittingType} fittingType */ |
| forceFit(fittingType) { |
| // The fitting type is the new state. We want to set the button fitting type |
| // to the opposite value. |
| this.fittingType_ = fittingType === FittingType.FIT_TO_WIDTH ? |
| FittingType.FIT_TO_PAGE : |
| FittingType.FIT_TO_WIDTH; |
| } |
| |
| fitToggle() { |
| const newState = this.fittingType_ === FittingType.FIT_TO_PAGE ? |
| FittingType.FIT_TO_WIDTH : |
| FittingType.FIT_TO_PAGE; |
| this.dispatchEvent( |
| new CustomEvent('fit-to-changed', {detail: this.fittingType_})); |
| this.fittingType_ = newState; |
| } |
| |
| /** @private */ |
| onFitToButtonClick_() { |
| this.fitToggle(); |
| } |
| |
| /** |
| * @return {!HTMLInputElement} |
| * @private |
| */ |
| getZoomInput_() { |
| return /** @type {!HTMLInputElement} */ ( |
| this.shadowRoot.querySelector('#zoom-controls input')); |
| } |
| |
| /** @private */ |
| onZoomChange_() { |
| const input = this.getZoomInput_(); |
| let value = Number.parseInt(input.value, 10); |
| value = Math.max(Math.min(value, this.zoomBounds.max), this.zoomBounds.min); |
| if (this.sendZoomChanged_(value)) { |
| return; |
| } |
| |
| const zoomString = `${this.viewportZoomPercent_}%`; |
| input.value = zoomString; |
| } |
| |
| /** |
| * @param {number} value The new zoom value |
| * @return {boolean} Whether the zoom-changed event was sent. |
| * @private |
| */ |
| sendZoomChanged_(value) { |
| if (Number.isNaN(value)) { |
| return false; |
| } |
| |
| // The viewport can have non-integer zoom values. |
| if (Math.abs(this.viewportZoom * 100 - value) < 0.5) { |
| return false; |
| } |
| |
| this.dispatchEvent(new CustomEvent('zoom-changed', {detail: value})); |
| return true; |
| } |
| |
| /** |
| * @param {!Event} e |
| * @private |
| */ |
| onZoomInputPointerup_(e) { |
| /* @type {!HTMLInputElement} */ (e.target).select(); |
| } |
| |
| /** @private */ |
| onMoreClick_() { |
| const anchor = |
| /** @type {!HTMLElement} */ (this.shadowRoot.querySelector('#more')); |
| this.getMenu_().showAt(anchor, { |
| anchorAlignmentX: AnchorAlignment.CENTER, |
| anchorAlignmentY: AnchorAlignment.AFTER_END, |
| noOffset: true, |
| }); |
| } |
| |
| /** |
| * @param {!CustomEvent<!{value: boolean}>} e |
| * @private |
| */ |
| onMoreOpenChanged_(e) { |
| this.moreMenuOpen_ = e.detail.value; |
| } |
| |
| /** |
| * @return {boolean} |
| * @private |
| */ |
| isAtMinimumZoom_() { |
| return this.zoomBounds !== undefined && |
| this.viewportZoomPercent_ === this.zoomBounds.min; |
| } |
| |
| /** |
| * @return {boolean} |
| * @private |
| */ |
| isAtMaximumZoom_() { |
| return this.zoomBounds !== undefined && |
| this.viewportZoomPercent_ === this.zoomBounds.max; |
| } |
| |
| // <if expr="chromeos"> |
| /** @private */ |
| onDialogClose_() { |
| const confirmed = |
| /** @type {!ViewerAnnotationsModeDialogElement} */ ( |
| this.shadowRoot.querySelector('viewer-annotations-mode-dialog')) |
| .wasConfirmed(); |
| this.showAnnotationsModeDialog_ = false; |
| if (confirmed) { |
| this.dispatchEvent(new CustomEvent('annotation-mode-dialog-confirmed')); |
| this.toggleAnnotation(); |
| } |
| } |
| |
| /** @private */ |
| onAnnotationClick_() { |
| if (!this.rotated && !this.twoUpViewEnabled) { |
| this.toggleAnnotation(); |
| return; |
| } |
| |
| this.showAnnotationsModeDialog_ = true; |
| } |
| |
| toggleAnnotation() { |
| const newAnnotationMode = !this.annotationMode; |
| this.dispatchEvent(new CustomEvent( |
| 'annotation-mode-toggled', {detail: newAnnotationMode})); |
| |
| if (newAnnotationMode && !this.displayAnnotations_) { |
| this.toggleDisplayAnnotations_(); |
| } |
| } |
| // </if> |
| } |
| |
| customElements.define(ViewerToolbarElement.is, ViewerToolbarElement); |