PDF Viewer: Migrate to TypeScript, part 2.
Based on dhoss@'s https://chromium-review.googlesource.com/c/chromium/src/+/2983068 Bug: 1260303 Change-Id: Ibc3d7c1887b9287bf0c4ec5611f8ae1d011472b6 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3500673 Reviewed-by: K. Moon <kmoon@chromium.org> Reviewed-by: Rebekah Potter <rbpotter@chromium.org> Commit-Queue: Demetrios Papadopoulos <dpapad@chromium.org> Cr-Commit-Position: refs/heads/main@{#977013}
This commit is contained in:
chrome
browser
resources
pdf
elements
viewer-bookmark.tsviewer-document-outline.tsviewer-download-controls.tsviewer-page-selector.htmlviewer-page-selector.tsviewer-password-dialog.tsviewer-pdf-sidenav.tsviewer-properties-dialog.htmlviewer-properties-dialog.tsviewer-thumbnail-bar.jsviewer-thumbnail-bar.tsviewer-thumbnail.ts
pdf.gnipdf_viewer.jstest
pdf/ui
@ -8,12 +8,20 @@ import 'chrome://resources/cr_elements/shared_vars_css.m.js';
|
||||
import 'chrome://resources/polymer/v3_0/paper-styles/color.js';
|
||||
import './shared-css.js';
|
||||
|
||||
import {CrIconButtonElement} from 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.m.js';
|
||||
import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
|
||||
|
||||
import {Bookmark} from '../bookmark_type.js';
|
||||
|
||||
/** Amount that each level of bookmarks is indented by (px). */
|
||||
const BOOKMARK_INDENT = 20;
|
||||
const BOOKMARK_INDENT: number = 20;
|
||||
|
||||
export interface ViewerBookmarkElement {
|
||||
$: {
|
||||
item: HTMLElement,
|
||||
expand: CrIconButtonElement,
|
||||
};
|
||||
}
|
||||
|
||||
export class ViewerBookmarkElement extends PolymerElement {
|
||||
static get is() {
|
||||
@ -26,7 +34,6 @@ export class ViewerBookmarkElement extends PolymerElement {
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
/** @type {Bookmark} */
|
||||
bookmark: {
|
||||
type: Object,
|
||||
observer: 'bookmarkChanged_',
|
||||
@ -37,10 +44,8 @@ export class ViewerBookmarkElement extends PolymerElement {
|
||||
observer: 'depthChanged_',
|
||||
},
|
||||
|
||||
/** @private */
|
||||
childDepth_: Number,
|
||||
|
||||
/** @private */
|
||||
childrenShown_: {
|
||||
type: Boolean,
|
||||
reflectToAttribute: true,
|
||||
@ -49,51 +54,45 @@ export class ViewerBookmarkElement extends PolymerElement {
|
||||
};
|
||||
}
|
||||
|
||||
/** @override */
|
||||
bookmark: Bookmark;
|
||||
depth: number;
|
||||
private childDepth_: number;
|
||||
private childrenShown_: boolean;
|
||||
|
||||
ready() {
|
||||
super.ready();
|
||||
|
||||
this.$.item.addEventListener('keydown', e => {
|
||||
const keyboardEvent = /** @type {!KeyboardEvent} */ (e);
|
||||
if (keyboardEvent.key === 'Enter') {
|
||||
this.onEnter_(keyboardEvent);
|
||||
} else if (keyboardEvent.key === ' ') {
|
||||
this.onSpace_(keyboardEvent);
|
||||
if (e.key === 'Enter') {
|
||||
this.onEnter_(e);
|
||||
} else if (e.key === ' ') {
|
||||
this.onSpace_(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} eventName
|
||||
* @param {*=} detail
|
||||
* @private
|
||||
*/
|
||||
fire_(eventName, detail) {
|
||||
private fire_(eventName: string, detail?: any) {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent(eventName, {bubbles: true, composed: true, detail}));
|
||||
}
|
||||
|
||||
/** @private */
|
||||
bookmarkChanged_() {
|
||||
private bookmarkChanged_() {
|
||||
this.$.expand.style.visibility =
|
||||
this.bookmark.children.length > 0 ? 'visible' : 'hidden';
|
||||
}
|
||||
|
||||
/** @private */
|
||||
depthChanged_() {
|
||||
private depthChanged_() {
|
||||
this.childDepth_ = this.depth + 1;
|
||||
this.$.item.style.paddingInlineStart =
|
||||
(this.depth * BOOKMARK_INDENT) + 'px';
|
||||
}
|
||||
|
||||
/** @private */
|
||||
onClick_() {
|
||||
private onClick_() {
|
||||
if (this.bookmark.page != null) {
|
||||
if (this.bookmark.zoom != null) {
|
||||
this.fire_('change-zoom', {zoom: this.bookmark.zoom});
|
||||
}
|
||||
if (this.bookmark.x != null &&
|
||||
this.bookmark.y != null) {
|
||||
if (this.bookmark.x != null && this.bookmark.y != null) {
|
||||
this.fire_('change-page-and-xy', {
|
||||
page: this.bookmark.page,
|
||||
x: this.bookmark.x,
|
||||
@ -109,11 +108,7 @@ export class ViewerBookmarkElement extends PolymerElement {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!KeyboardEvent} e
|
||||
* @private
|
||||
*/
|
||||
onEnter_(e) {
|
||||
private onEnter_(e: KeyboardEvent) {
|
||||
// Don't allow events which have propagated up from the expand button to
|
||||
// trigger a click.
|
||||
if (e.target !== this.$.expand) {
|
||||
@ -121,11 +116,7 @@ export class ViewerBookmarkElement extends PolymerElement {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!KeyboardEvent} e
|
||||
* @private
|
||||
*/
|
||||
onSpace_(e) {
|
||||
private onSpace_(e: KeyboardEvent) {
|
||||
// cr-icon-button stops propagation of space events, so there's no need
|
||||
// to check the event source here.
|
||||
this.onClick_();
|
||||
@ -133,22 +124,20 @@ export class ViewerBookmarkElement extends PolymerElement {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Event} e
|
||||
* @private
|
||||
*/
|
||||
toggleChildren_(e) {
|
||||
private toggleChildren_(e: Event) {
|
||||
this.childrenShown_ = !this.childrenShown_;
|
||||
e.stopPropagation(); // Prevent the above onClick_ handler from firing.
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string}
|
||||
* @private
|
||||
*/
|
||||
getAriaExpanded_() {
|
||||
private getAriaExpanded_(): string {
|
||||
return this.childrenShown_ ? 'true' : 'false';
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'viewer-bookmark': ViewerBookmarkElement;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(ViewerBookmarkElement.is, ViewerBookmarkElement);
|
@ -20,10 +20,17 @@ export class ViewerDocumentOutlineElement extends PolymerElement {
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
/** @type {!Array<!Bookmark>} */
|
||||
bookmarks: Array,
|
||||
};
|
||||
}
|
||||
|
||||
bookmarks: Bookmark[];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'viewer-document-outline': ViewerDocumentOutlineElement;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(
|
@ -2,17 +2,26 @@
|
||||
// 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_action_menu/cr_action_menu.js';
|
||||
import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.m.js';
|
||||
import 'chrome://resources/cr_elements/icons.m.js';
|
||||
import './icons.js';
|
||||
import './shared-css.js';
|
||||
|
||||
import {AnchorAlignment, CrActionMenuElement} from 'chrome://resources/cr_elements/cr_action_menu/cr_action_menu.js';
|
||||
import {CrIconButtonElement} from 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.m.js';
|
||||
import {PromiseResolver} from 'chrome://resources/js/promise_resolver.m.js';
|
||||
import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
|
||||
|
||||
import {SaveRequestType} from '../constants.js';
|
||||
|
||||
export interface ViewerDownloadControlsElement {
|
||||
$: {
|
||||
download: CrIconButtonElement,
|
||||
menu: CrActionMenuElement,
|
||||
};
|
||||
}
|
||||
|
||||
export class ViewerDownloadControlsElement extends PolymerElement {
|
||||
static get is() {
|
||||
return 'viewer-download-controls';
|
||||
@ -39,7 +48,6 @@ export class ViewerDownloadControlsElement extends PolymerElement {
|
||||
'hasEnteredAnnotationMode)',
|
||||
},
|
||||
|
||||
/** @private */
|
||||
menuOpen_: {
|
||||
type: Boolean,
|
||||
reflectToAttribute: true,
|
||||
@ -48,73 +56,38 @@ export class ViewerDownloadControlsElement extends PolymerElement {
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
hasEdits: boolean;
|
||||
hasEnteredAnnotationMode: boolean;
|
||||
isFormFieldFocused: boolean;
|
||||
private downloadHasPopup_: string;
|
||||
private menuOpen_: boolean;
|
||||
private waitForFormFocusChange_: PromiseResolver<boolean>|null = null;
|
||||
|
||||
// Polymer properties
|
||||
/** @private {string} */
|
||||
this.downloadHasPopup_;
|
||||
|
||||
/** @type {boolean} */
|
||||
this.hasEdits;
|
||||
|
||||
/** @type {boolean} */
|
||||
this.hasEnteredAnnotationMode;
|
||||
|
||||
/** @type {boolean} */
|
||||
this.isFormFieldFocused;
|
||||
|
||||
// Non-Polymer properties
|
||||
/** @private {?PromiseResolver<boolean>} */
|
||||
this.waitForFormFocusChange_ = null;
|
||||
}
|
||||
|
||||
/** @return {boolean} */
|
||||
isMenuOpen() {
|
||||
isMenuOpen(): boolean {
|
||||
return this.menuOpen_;
|
||||
}
|
||||
|
||||
closeMenu() {
|
||||
this.getDownloadMenu_().close();
|
||||
this.$.menu.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!CustomEvent<!{value: boolean}>} e
|
||||
* @private
|
||||
*/
|
||||
onOpenChanged_(e) {
|
||||
private onOpenChanged_(e: CustomEvent<{value: boolean}>) {
|
||||
this.menuOpen_ = e.detail.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
* @private
|
||||
*/
|
||||
hasEditsToSave_() {
|
||||
private hasEditsToSave_(): boolean {
|
||||
return this.hasEnteredAnnotationMode || this.hasEdits;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string} The value for the aria-haspopup attribute for the download
|
||||
* button.
|
||||
* @private
|
||||
* @return The value for the aria-haspopup attribute for the download button.
|
||||
*/
|
||||
computeDownloadHasPopup_() {
|
||||
private computeDownloadHasPopup_(): string {
|
||||
return this.hasEditsToSave_() ? 'menu' : 'false';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {!CrActionMenuElement}
|
||||
* @private
|
||||
*/
|
||||
getDownloadMenu_() {
|
||||
return /** @type {!CrActionMenuElement} */ (
|
||||
this.shadowRoot.querySelector('#menu'));
|
||||
}
|
||||
|
||||
/** @private */
|
||||
showDownloadMenu_() {
|
||||
this.getDownloadMenu_().showAt(this.$.download, {
|
||||
private showDownloadMenu_() {
|
||||
this.$.menu.showAt(this.$.download, {
|
||||
anchorAlignmentX: AnchorAlignment.CENTER,
|
||||
});
|
||||
// For tests
|
||||
@ -122,8 +95,7 @@ export class ViewerDownloadControlsElement extends PolymerElement {
|
||||
'download-menu-shown-for-testing', {bubbles: true, composed: true}));
|
||||
}
|
||||
|
||||
/** @private */
|
||||
onDownloadClick_() {
|
||||
private onDownloadClick_() {
|
||||
this.waitForEdits_().then(hasEdits => {
|
||||
if (hasEdits) {
|
||||
this.showDownloadMenu_();
|
||||
@ -134,11 +106,10 @@ export class ViewerDownloadControlsElement extends PolymerElement {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {!Promise<boolean>} Promise that resolves with true if the PDF has
|
||||
* edits and/or annotations, and false otherwise.
|
||||
* @private
|
||||
* @return Promise that resolves with true if the PDF has edits and/or
|
||||
* annotations, and false otherwise.
|
||||
*/
|
||||
waitForEdits_() {
|
||||
private waitForEdits_(): Promise<boolean> {
|
||||
if (this.hasEditsToSave_()) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
@ -149,8 +120,7 @@ export class ViewerDownloadControlsElement extends PolymerElement {
|
||||
return this.waitForFormFocusChange_.promise;
|
||||
}
|
||||
|
||||
/** @private */
|
||||
onFormFieldFocusedChanged_() {
|
||||
private onFormFieldFocusedChanged_() {
|
||||
if (!this.waitForFormFocusChange_) {
|
||||
return;
|
||||
}
|
||||
@ -159,28 +129,29 @@ export class ViewerDownloadControlsElement extends PolymerElement {
|
||||
this.waitForFormFocusChange_ = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!SaveRequestType} type
|
||||
* @private
|
||||
*/
|
||||
dispatchSaveEvent_(type) {
|
||||
private dispatchSaveEvent_(type: SaveRequestType) {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('save', {detail: type, bubbles: true, composed: true}));
|
||||
}
|
||||
|
||||
/** @private */
|
||||
onDownloadOriginalClick_() {
|
||||
private onDownloadOriginalClick_() {
|
||||
this.dispatchSaveEvent_(SaveRequestType.ORIGINAL);
|
||||
this.getDownloadMenu_().close();
|
||||
this.$.menu.close();
|
||||
}
|
||||
|
||||
/** @private */
|
||||
onDownloadEditedClick_() {
|
||||
private onDownloadEditedClick_() {
|
||||
this.dispatchSaveEvent_(
|
||||
this.hasEnteredAnnotationMode ? SaveRequestType.ANNOTATION :
|
||||
SaveRequestType.EDITED);
|
||||
this.getDownloadMenu_().close();
|
||||
this.$.menu.close();
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'viewer-download-controls': ViewerDownloadControlsElement;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(
|
||||
ViewerDownloadControlsElement.is, ViewerDownloadControlsElement);
|
@ -13,7 +13,7 @@
|
||||
--page-selector-spacing: 4px;
|
||||
}
|
||||
|
||||
#pageselector::selection {
|
||||
#pageSelector::selection {
|
||||
background-color: var(--viewer-text-input-selection-color);
|
||||
}
|
||||
|
||||
@ -41,9 +41,9 @@
|
||||
}
|
||||
</style>
|
||||
<div id="content">
|
||||
<input part="input" type="text" id="pageselector" value="[[pageNo]]"
|
||||
<input part="input" type="text" id="pageSelector" value="[[pageNo]]"
|
||||
on-pointerup="select" on-input="onInput_" on-change="pageNoCommitted"
|
||||
aria-label="$i18n{labelPageNumber}">
|
||||
<span id="divider">/</span>
|
||||
<span id="pagelength">[[docLength]]</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -6,6 +6,12 @@ import './shared-vars.js';
|
||||
|
||||
import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
|
||||
|
||||
export interface ViewerPageSelectorElement {
|
||||
$: {
|
||||
pageSelector: HTMLInputElement,
|
||||
};
|
||||
}
|
||||
|
||||
export class ViewerPageSelectorElement extends PolymerElement {
|
||||
static get is() {
|
||||
return 'viewer-page-selector';
|
||||
@ -17,9 +23,7 @@ export class ViewerPageSelectorElement extends PolymerElement {
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
/**
|
||||
* The number of pages the document contains.
|
||||
*/
|
||||
/** The number of pages the document contains. */
|
||||
docLength: {type: Number, value: 1, observer: 'docLengthChanged_'},
|
||||
|
||||
/**
|
||||
@ -35,48 +39,46 @@ export class ViewerPageSelectorElement extends PolymerElement {
|
||||
};
|
||||
}
|
||||
|
||||
/** @return {!HTMLInputElement} */
|
||||
get pageSelector() {
|
||||
return /** @type {!HTMLInputElement} */ (this.$.pageselector);
|
||||
}
|
||||
docLength: number;
|
||||
pageNo: number;
|
||||
|
||||
pageNoCommitted() {
|
||||
const page = parseInt(this.pageSelector.value, 10);
|
||||
const page = parseInt(this.$.pageSelector.value, 10);
|
||||
|
||||
if (!isNaN(page) && page <= this.docLength && page > 0) {
|
||||
this.dispatchEvent(new CustomEvent('change-page', {
|
||||
detail: {page: page - 1, origin: 'pageselector'},
|
||||
detail: {page: page - 1, origin: 'pageSelector'},
|
||||
composed: true,
|
||||
}));
|
||||
} else {
|
||||
this.pageSelector.value = this.pageNo.toString();
|
||||
this.$.pageSelector.value = this.pageNo.toString();
|
||||
}
|
||||
this.pageSelector.blur();
|
||||
this.$.pageSelector.blur();
|
||||
}
|
||||
|
||||
/** @private */
|
||||
docLengthChanged_() {
|
||||
private docLengthChanged_() {
|
||||
const numDigits = this.docLength.toString().length;
|
||||
this.style.setProperty('--page-length-digits', `${numDigits}`);
|
||||
}
|
||||
|
||||
select() {
|
||||
this.pageSelector.select();
|
||||
this.$.pageSelector.select();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean} True if the selector input field is currently focused.
|
||||
*/
|
||||
isActive() {
|
||||
return this.shadowRoot.activeElement === this.pageSelector;
|
||||
/** @return True if the selector input field is currently focused. */
|
||||
isActive(): boolean {
|
||||
return this.shadowRoot!.activeElement === this.$.pageSelector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Immediately remove any non-digit characters.
|
||||
* @private
|
||||
*/
|
||||
onInput_() {
|
||||
this.pageSelector.value = this.pageSelector.value.replace(/[^\d]/, '');
|
||||
/** Immediately remove any non-digit characters. */
|
||||
private onInput_() {
|
||||
this.$.pageSelector.value = this.$.pageSelector.value.replace(/[^\d]/, '');
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'viewer-page-selector': ViewerPageSelectorElement;
|
||||
}
|
||||
}
|
||||
|
@ -8,8 +8,19 @@ import 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
|
||||
import 'chrome://resources/cr_elements/shared_style_css.m.js';
|
||||
import 'chrome://resources/cr_elements/shared_vars_css.m.js';
|
||||
|
||||
import {CrButtonElement} from 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
|
||||
import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
|
||||
import {CrInputElement} from 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
|
||||
import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
|
||||
|
||||
export interface ViewerPasswordDialogElement {
|
||||
$: {
|
||||
dialog: CrDialogElement,
|
||||
password: CrInputElement,
|
||||
submit: CrButtonElement,
|
||||
};
|
||||
}
|
||||
|
||||
export class ViewerPasswordDialogElement extends PolymerElement {
|
||||
static get is() {
|
||||
return 'viewer-password-dialog';
|
||||
@ -25,12 +36,14 @@ export class ViewerPasswordDialogElement extends PolymerElement {
|
||||
};
|
||||
}
|
||||
|
||||
invalid: boolean;
|
||||
|
||||
close() {
|
||||
this.$.dialog.close();
|
||||
}
|
||||
|
||||
deny() {
|
||||
const password = /** @type {!CrInputElement} */ (this.$.password);
|
||||
const password = this.$.password;
|
||||
password.disabled = false;
|
||||
this.$.submit.disabled = false;
|
||||
this.invalid = true;
|
||||
@ -40,7 +53,7 @@ export class ViewerPasswordDialogElement extends PolymerElement {
|
||||
}
|
||||
|
||||
submit() {
|
||||
const password = /** @type {!CrInputElement} */ (this.$.password);
|
||||
const password = this.$.password;
|
||||
if (password.value.length === 0) {
|
||||
return;
|
||||
}
|
||||
@ -52,5 +65,11 @@ export class ViewerPasswordDialogElement extends PolymerElement {
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'viewer-password-dialog': ViewerPasswordDialogElement;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(
|
||||
ViewerPasswordDialogElement.is, ViewerPasswordDialogElement);
|
@ -29,7 +29,6 @@ export class ViewerPdfSidenavElement extends PolymerElement {
|
||||
return {
|
||||
activePage: Number,
|
||||
|
||||
/** @type {!Array<!Bookmark>} */
|
||||
bookmarks: {
|
||||
type: Array,
|
||||
value: () => [],
|
||||
@ -39,7 +38,6 @@ export class ViewerPdfSidenavElement extends PolymerElement {
|
||||
|
||||
docLength: Number,
|
||||
|
||||
/** @private */
|
||||
thumbnailView_: {
|
||||
type: Boolean,
|
||||
value: true,
|
||||
@ -47,49 +45,43 @@ export class ViewerPdfSidenavElement extends PolymerElement {
|
||||
};
|
||||
}
|
||||
|
||||
/** @private */
|
||||
onThumbnailClick_() {
|
||||
activePage: number;
|
||||
bookmarks: Bookmark[];
|
||||
clockwiseRotations: number;
|
||||
docLength: number;
|
||||
private thumbnailView_: boolean;
|
||||
|
||||
private onThumbnailClick_() {
|
||||
record(UserAction.SELECT_SIDENAV_THUMBNAILS);
|
||||
this.thumbnailView_ = true;
|
||||
}
|
||||
|
||||
/** @private */
|
||||
onOutlineClick_() {
|
||||
private onOutlineClick_() {
|
||||
record(UserAction.SELECT_SIDENAV_OUTLINE);
|
||||
this.thumbnailView_ = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string}
|
||||
* @private
|
||||
*/
|
||||
outlineButtonClass_() {
|
||||
private outlineButtonClass_(): string {
|
||||
return this.thumbnailView_ ? '' : 'selected';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string}
|
||||
* @private
|
||||
*/
|
||||
thumbnailButtonClass_() {
|
||||
private thumbnailButtonClass_(): string {
|
||||
return this.thumbnailView_ ? 'selected' : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string}
|
||||
* @private
|
||||
*/
|
||||
getAriaSelectedThumbnails_() {
|
||||
private getAriaSelectedThumbnails_(): string {
|
||||
return this.thumbnailView_ ? 'true' : 'false';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string}
|
||||
* @private
|
||||
*/
|
||||
getAriaSelectedOutline_() {
|
||||
private getAriaSelectedOutline_(): string {
|
||||
return this.thumbnailView_ ? 'false' : 'true';
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'viewer-pdf-sidenav': ViewerPdfSidenavElement;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(ViewerPdfSidenavElement.is, ViewerPdfSidenavElement);
|
@ -41,7 +41,7 @@
|
||||
white-space: normal;
|
||||
}
|
||||
</style>
|
||||
<cr-dialog show-on-attach>
|
||||
<cr-dialog id="dialog" show-on-attach>
|
||||
<div slot="title">$i18n{propertiesDialogTitle}</div>
|
||||
<div slot="body">
|
||||
<table>
|
||||
|
@ -6,10 +6,17 @@ import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
|
||||
import 'chrome://resources/cr_elements/shared_style_css.m.js';
|
||||
import 'chrome://resources/cr_elements/shared_vars_css.m.js';
|
||||
|
||||
import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
|
||||
import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
|
||||
|
||||
import {DocumentMetadata} from '../constants.js';
|
||||
|
||||
export interface ViewerPropertiesDialogElement {
|
||||
$: {
|
||||
dialog: CrDialogElement,
|
||||
};
|
||||
}
|
||||
|
||||
export class ViewerPropertiesDialogElement extends PolymerElement {
|
||||
static get is() {
|
||||
return 'viewer-properties-dialog';
|
||||
@ -21,47 +28,33 @@ export class ViewerPropertiesDialogElement extends PolymerElement {
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
/** @type {!DocumentMetadata} */
|
||||
documentMetadata: Object,
|
||||
|
||||
fileName: String,
|
||||
|
||||
pageCount: Number,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {!CrDialogElement}
|
||||
* @private
|
||||
*/
|
||||
getDialog_() {
|
||||
return /** @type {!CrDialogElement} */ (
|
||||
this.shadowRoot.querySelector('cr-dialog'));
|
||||
}
|
||||
documentMetadata: DocumentMetadata;
|
||||
fileName: string;
|
||||
pageCount: number;
|
||||
|
||||
/**
|
||||
* @param {string} yesLabel
|
||||
* @param {string} noLabel
|
||||
* @param {boolean} linearized
|
||||
* @return {string}
|
||||
* @private
|
||||
*/
|
||||
getFastWebViewValue_(yesLabel, noLabel, linearized) {
|
||||
private getFastWebViewValue_(
|
||||
yesLabel: string, noLabel: string, linearized: boolean): string {
|
||||
return linearized ? yesLabel : noLabel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} value
|
||||
* @return {string}
|
||||
* @private
|
||||
*/
|
||||
getOrPlaceholder_(value) {
|
||||
private getOrPlaceholder_(value: string): string {
|
||||
return value || '-';
|
||||
}
|
||||
|
||||
/** @private */
|
||||
onClickClose_() {
|
||||
this.getDialog_().close();
|
||||
private onClickClose_() {
|
||||
this.$.dialog.close();
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'viewer-properties-dialog': ViewerPropertiesDialogElement;
|
||||
}
|
||||
}
|
||||
|
@ -1,243 +0,0 @@
|
||||
// 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 './viewer-thumbnail.js';
|
||||
|
||||
import {assert} from 'chrome://resources/js/assert.m.js';
|
||||
import {FocusOutlineManager} from 'chrome://resources/js/cr/ui/focus_outline_manager.m.js';
|
||||
import {EventTracker} from 'chrome://resources/js/event_tracker.m.js';
|
||||
import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
|
||||
import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
|
||||
|
||||
import {PluginController, PluginControllerEventType} from '../controller.js';
|
||||
import {ViewerThumbnailElement} from './viewer-thumbnail.js';
|
||||
|
||||
export class ViewerThumbnailBarElement extends PolymerElement {
|
||||
static get is() {
|
||||
return 'viewer-thumbnail-bar';
|
||||
}
|
||||
|
||||
static get template() {
|
||||
return html`{__html_template__}`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
activePage: {
|
||||
type: Number,
|
||||
observer: 'activePageChanged_',
|
||||
},
|
||||
|
||||
clockwiseRotations: Number,
|
||||
|
||||
docLength: Number,
|
||||
|
||||
isPluginActive_: Boolean,
|
||||
|
||||
/** @private {Array<number>} */
|
||||
pageNumbers_: {
|
||||
type: Array,
|
||||
computed: 'computePageNumbers_(docLength)',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
// TODO(dhoss): Remove `this.inTest` when implemented a mock plugin
|
||||
// controller.
|
||||
/** @type {boolean} */
|
||||
this.inTest = false;
|
||||
|
||||
/** @private {!PluginController} */
|
||||
this.pluginController_ = PluginController.getInstance();
|
||||
|
||||
/** @private {boolean} */
|
||||
this.isPluginActive_ = this.pluginController_.isActive;
|
||||
|
||||
/** @private {!EventTracker} */
|
||||
this.tracker_ = new EventTracker();
|
||||
|
||||
// Listen to whether the plugin is active. Thumbnails should be hidden
|
||||
// when the plugin is inactive.
|
||||
this.tracker_.add(
|
||||
this.pluginController_.getEventTarget(),
|
||||
PluginControllerEventType.IS_ACTIVE_CHANGED,
|
||||
e => this.isPluginActive_ = e.detail);
|
||||
}
|
||||
|
||||
ready() {
|
||||
super.ready();
|
||||
|
||||
this.addEventListener('focus', this.onFocus_);
|
||||
this.addEventListener('keydown', this.onKeydown_);
|
||||
|
||||
const thumbnailsDiv = this.shadowRoot.querySelector('#thumbnails');
|
||||
assert(thumbnailsDiv);
|
||||
|
||||
/** @private {!IntersectionObserver} */
|
||||
this.intersectionObserver_ = new IntersectionObserver(entries => {
|
||||
entries.forEach(entry => {
|
||||
const thumbnail = /** @type {!ViewerThumbnailElement} */ (entry.target);
|
||||
|
||||
if (!entry.isIntersecting) {
|
||||
thumbnail.clearImage();
|
||||
return;
|
||||
}
|
||||
|
||||
if (thumbnail.isPainted()) {
|
||||
return;
|
||||
}
|
||||
thumbnail.setPainted();
|
||||
|
||||
if (!this.isPluginActive_ || this.inTest) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.pluginController_.requestThumbnail(thumbnail.pageNumber)
|
||||
.then(response => {
|
||||
const array = new Uint8ClampedArray(response.imageData);
|
||||
const imageData = new ImageData(array, response.width);
|
||||
thumbnail.image = imageData;
|
||||
});
|
||||
});
|
||||
}, {
|
||||
root: thumbnailsDiv,
|
||||
// The root margin is set to 100% on the bottom to prepare thumbnails that
|
||||
// are one standard scroll finger swipe away.
|
||||
// The root margin is set to 500% on the top to discard thumbnails that
|
||||
// far from view, but to avoid regenerating thumbnails that are close.
|
||||
rootMargin: '500% 0% 100%',
|
||||
});
|
||||
|
||||
FocusOutlineManager.forDocument(document);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the focus to the thumbnail of the new active page if the focus was
|
||||
* already on a thumbnail.
|
||||
* @private
|
||||
*/
|
||||
activePageChanged_() {
|
||||
if (this.shadowRoot.activeElement) {
|
||||
this.getThumbnailForPage(this.activePage).focusAndScroll();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pageNumber
|
||||
* @private
|
||||
*/
|
||||
clickThumbnailForPage(pageNumber) {
|
||||
if (pageNumber < 1 || pageNumber > this.docLength) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.getThumbnailForPage(pageNumber).getClickTarget().click();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pageNumber
|
||||
* @return {?ViewerThumbnailElement}
|
||||
*/
|
||||
getThumbnailForPage(pageNumber) {
|
||||
return /** @type {ViewerThumbnailElement} */ (this.shadowRoot.querySelector(
|
||||
`viewer-thumbnail:nth-child(${pageNumber})`));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {!Array<number>} The array of page numbers.
|
||||
* @private
|
||||
*/
|
||||
computePageNumbers_() {
|
||||
return Array.from({length: this.docLength}, (_, i) => i + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pageNumber
|
||||
* @return {string}
|
||||
* @private
|
||||
*/
|
||||
getAriaLabel_(pageNumber) {
|
||||
return loadTimeData.getStringF('thumbnailPageAriaLabel', pageNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} page
|
||||
* @return {boolean} Whether the page is the current page.
|
||||
* @private
|
||||
*/
|
||||
isActivePage_(page) {
|
||||
return this.activePage === page;
|
||||
}
|
||||
|
||||
/** @private */
|
||||
onDomChange_() {
|
||||
this.shadowRoot.querySelectorAll('viewer-thumbnail').forEach(thumbnail => {
|
||||
this.intersectionObserver_.observe(thumbnail);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Forwards focus to a thumbnail when tabbing.
|
||||
* @private
|
||||
*/
|
||||
onFocus_() {
|
||||
// Ignore focus triggered by mouse to allow the focus to go straight to the
|
||||
// thumbnail being clicked.
|
||||
const focusOutlineManager = FocusOutlineManager.forDocument(document);
|
||||
if (!focusOutlineManager.visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Change focus to the thumbnail of the active page.
|
||||
const activeThumbnail =
|
||||
this.shadowRoot.querySelector('viewer-thumbnail[is-active]');
|
||||
if (activeThumbnail) {
|
||||
activeThumbnail.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise change to the first thumbnail, if there is one.
|
||||
const firstThumbnail = this.shadowRoot.querySelector('viewer-thumbnail');
|
||||
if (!firstThumbnail) {
|
||||
return;
|
||||
}
|
||||
firstThumbnail.focus();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Event} e
|
||||
* @private
|
||||
*/
|
||||
onKeydown_(e) {
|
||||
const keyboardEvent = /** @type {!KeyboardEvent} */ (e);
|
||||
if (keyboardEvent.key === 'Tab') {
|
||||
// On shift+tab, first redirect focus from the thumbnails to:
|
||||
// 1) Avoid focusing on the thumbnail bar.
|
||||
// 2) Focus to the element before the thumbnail bar from any thumbnail.
|
||||
if (e.shiftKey) {
|
||||
this.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// On tab, first redirect focus to the last thumbnail to focus to the
|
||||
// element after the thumbnail bar from any thumbnail.
|
||||
this.shadowRoot.querySelector('viewer-thumbnail:last-of-type').focus({
|
||||
preventScroll: true
|
||||
});
|
||||
} else if (keyboardEvent.key === 'ArrowRight') {
|
||||
// Prevent default arrow scroll behavior.
|
||||
keyboardEvent.preventDefault();
|
||||
this.clickThumbnailForPage(this.activePage + 1);
|
||||
} else if (keyboardEvent.key === 'ArrowLeft') {
|
||||
// Prevent default arrow scroll behavior.
|
||||
keyboardEvent.preventDefault();
|
||||
this.clickThumbnailForPage(this.activePage - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(ViewerThumbnailBarElement.is, ViewerThumbnailBarElement);
|
220
chrome/browser/resources/pdf/elements/viewer-thumbnail-bar.ts
Normal file
220
chrome/browser/resources/pdf/elements/viewer-thumbnail-bar.ts
Normal file
@ -0,0 +1,220 @@
|
||||
// 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 './viewer-thumbnail.js';
|
||||
|
||||
import {assert} from 'chrome://resources/js/assert.m.js';
|
||||
import {FocusOutlineManager} from 'chrome://resources/js/cr/ui/focus_outline_manager.m.js';
|
||||
import {EventTracker} from 'chrome://resources/js/event_tracker.m.js';
|
||||
import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
|
||||
import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
|
||||
|
||||
import {PluginController, PluginControllerEventType} from '../controller.js';
|
||||
import {ViewerThumbnailElement} from './viewer-thumbnail.js';
|
||||
|
||||
export class ViewerThumbnailBarElement extends PolymerElement {
|
||||
static get is() {
|
||||
return 'viewer-thumbnail-bar';
|
||||
}
|
||||
|
||||
static get template() {
|
||||
return html`{__html_template__}`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
activePage: {
|
||||
type: Number,
|
||||
observer: 'activePageChanged_',
|
||||
},
|
||||
|
||||
clockwiseRotations: Number,
|
||||
docLength: Number,
|
||||
isPluginActive_: Boolean,
|
||||
|
||||
pageNumbers_: {
|
||||
type: Array,
|
||||
computed: 'computePageNumbers_(docLength)',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
activePage: number;
|
||||
clockwiseRotations: number;
|
||||
docLength: number;
|
||||
private isPluginActive_: boolean;
|
||||
private pageNumbers_: number[];
|
||||
private intersectionObserver_: IntersectionObserver;
|
||||
private pluginController_: PluginController = PluginController.getInstance();
|
||||
private tracker_: EventTracker = new EventTracker();
|
||||
|
||||
// TODO(dhoss): Remove `this.inTest` when implemented a mock plugin
|
||||
// controller.
|
||||
inTest: boolean = false;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.isPluginActive_ = this.pluginController_.isActive;
|
||||
|
||||
// Listen to whether the plugin is active. Thumbnails should be hidden
|
||||
// when the plugin is inactive.
|
||||
this.tracker_.add(
|
||||
this.pluginController_.getEventTarget(),
|
||||
PluginControllerEventType.IS_ACTIVE_CHANGED,
|
||||
(e: CustomEvent<boolean>) => this.isPluginActive_ = e.detail);
|
||||
}
|
||||
|
||||
ready() {
|
||||
super.ready();
|
||||
|
||||
this.addEventListener('focus', this.onFocus_);
|
||||
this.addEventListener('keydown', this.onKeydown_);
|
||||
|
||||
const thumbnailsDiv = this.shadowRoot!.querySelector('#thumbnails');
|
||||
assert(thumbnailsDiv);
|
||||
|
||||
// TODO(crbug.com/1260303): Change `any` to `IntersectionObserverEntry`.
|
||||
this.intersectionObserver_ =
|
||||
new IntersectionObserver((entries: IntersectionObserverEntry[]) => {
|
||||
entries.forEach(entry => {
|
||||
const thumbnail = entry.target as ViewerThumbnailElement;
|
||||
|
||||
if (!entry.isIntersecting) {
|
||||
thumbnail.clearImage();
|
||||
return;
|
||||
}
|
||||
|
||||
if (thumbnail.isPainted()) {
|
||||
return;
|
||||
}
|
||||
thumbnail.setPainted();
|
||||
|
||||
if (!this.isPluginActive_ || this.inTest) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.pluginController_.requestThumbnail(thumbnail.pageNumber)
|
||||
.then(response => {
|
||||
const array = new Uint8ClampedArray(response.imageData);
|
||||
const imageData = new ImageData(array, response.width);
|
||||
thumbnail.image = imageData;
|
||||
});
|
||||
});
|
||||
}, {
|
||||
root: thumbnailsDiv,
|
||||
// The root margin is set to 100% on the bottom to prepare thumbnails
|
||||
// that are one standard scroll finger swipe away. The root margin is
|
||||
// set to 500% on the top to discard thumbnails that are far from
|
||||
// view, but to avoid regenerating thumbnails that are close.
|
||||
rootMargin: '500% 0% 100%',
|
||||
});
|
||||
|
||||
FocusOutlineManager.forDocument(document);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the focus to the thumbnail of the new active page if the focus was
|
||||
* already on a thumbnail.
|
||||
*/
|
||||
private activePageChanged_() {
|
||||
if (this.shadowRoot!.activeElement) {
|
||||
this.getThumbnailForPage(this.activePage)!.focusAndScroll();
|
||||
}
|
||||
}
|
||||
|
||||
private clickThumbnailForPage(pageNumber: number) {
|
||||
const thumbnail = this.getThumbnailForPage(pageNumber);
|
||||
if (!thumbnail) {
|
||||
return;
|
||||
}
|
||||
|
||||
thumbnail.getClickTarget().click();
|
||||
}
|
||||
|
||||
getThumbnailForPage(pageNumber: number): ViewerThumbnailElement|null {
|
||||
return this.shadowRoot!.querySelector(
|
||||
`viewer-thumbnail:nth-child(${pageNumber})`);
|
||||
}
|
||||
|
||||
/** @return The array of page numbers. */
|
||||
private computePageNumbers_(): number[] {
|
||||
return Array.from({length: this.docLength}, (_, i) => i + 1);
|
||||
}
|
||||
|
||||
private getAriaLabel_(pageNumber: number): string {
|
||||
return loadTimeData.getStringF('thumbnailPageAriaLabel', pageNumber);
|
||||
}
|
||||
|
||||
/** @return Whether the page is the current page. */
|
||||
private isActivePage_(page: number): boolean {
|
||||
return this.activePage === page;
|
||||
}
|
||||
|
||||
private onDomChange_() {
|
||||
this.shadowRoot!.querySelectorAll('viewer-thumbnail').forEach(thumbnail => {
|
||||
this.intersectionObserver_.observe(thumbnail);
|
||||
});
|
||||
}
|
||||
|
||||
/** Forwards focus to a thumbnail when tabbing. */
|
||||
private onFocus_() {
|
||||
// Ignore focus triggered by mouse to allow the focus to go straight to the
|
||||
// thumbnail being clicked.
|
||||
const focusOutlineManager = FocusOutlineManager.forDocument(document);
|
||||
if (!focusOutlineManager.visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Change focus to the thumbnail of the active page.
|
||||
const activeThumbnail =
|
||||
this.shadowRoot!.querySelector<ViewerThumbnailElement>(
|
||||
'viewer-thumbnail[is-active]');
|
||||
if (activeThumbnail) {
|
||||
activeThumbnail.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise change to the first thumbnail, if there is one.
|
||||
const firstThumbnail = this.shadowRoot!.querySelector('viewer-thumbnail');
|
||||
if (!firstThumbnail) {
|
||||
return;
|
||||
}
|
||||
firstThumbnail.focus();
|
||||
}
|
||||
|
||||
private onKeydown_(e: KeyboardEvent) {
|
||||
if (e.key === 'Tab') {
|
||||
// On shift+tab, first redirect focus from the thumbnails to:
|
||||
// 1) Avoid focusing on the thumbnail bar.
|
||||
// 2) Focus to the element before the thumbnail bar from any thumbnail.
|
||||
if (e.shiftKey) {
|
||||
this.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// On tab, first redirect focus to the last thumbnail to focus to the
|
||||
// element after the thumbnail bar from any thumbnail.
|
||||
this.shadowRoot!
|
||||
.querySelector<ViewerThumbnailElement>(
|
||||
'viewer-thumbnail:last-of-type')!.focus({preventScroll: true});
|
||||
} else if (e.key === 'ArrowRight') {
|
||||
// Prevent default arrow scroll behavior.
|
||||
e.preventDefault();
|
||||
this.clickThumbnailForPage(this.activePage + 1);
|
||||
} else if (e.key === 'ArrowLeft') {
|
||||
// Prevent default arrow scroll behavior.
|
||||
e.preventDefault();
|
||||
this.clickThumbnailForPage(this.activePage - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'viewer-thumbnail-bar': ViewerThumbnailBarElement;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(ViewerThumbnailBarElement.is, ViewerThumbnailBarElement);
|
@ -10,13 +10,17 @@ import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/poly
|
||||
// The maximum widths of thumbnails for each layout (px).
|
||||
// These constants should be kept in sync with `kMaxWidthPortraitPx` and
|
||||
// `kMaxWidthLandscapePx` in pdf/thumbnail.cc.
|
||||
/** @type {number} */
|
||||
const PORTRAIT_WIDTH = 108;
|
||||
/** @type {number} */
|
||||
const LANDSCAPE_WIDTH = 140;
|
||||
const PORTRAIT_WIDTH: number = 108;
|
||||
|
||||
/** @type {string} */
|
||||
export const PAINTED_ATTRIBUTE = 'painted';
|
||||
const LANDSCAPE_WIDTH: number = 140;
|
||||
|
||||
export const PAINTED_ATTRIBUTE: string = 'painted';
|
||||
|
||||
export interface ViewerThumbnailElement {
|
||||
$: {
|
||||
thumbnail: HTMLElement,
|
||||
};
|
||||
}
|
||||
|
||||
export class ViewerThumbnailElement extends PolymerElement {
|
||||
static get is() {
|
||||
@ -45,14 +49,17 @@ export class ViewerThumbnailElement extends PolymerElement {
|
||||
};
|
||||
}
|
||||
|
||||
clockwiseRotations: number;
|
||||
isActive: boolean;
|
||||
pageNumber: number;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.addEventListener('keydown', this.onKeydown_);
|
||||
}
|
||||
|
||||
/** @param {!ImageData} imageData */
|
||||
set image(imageData) {
|
||||
set image(imageData: ImageData) {
|
||||
let canvas = this.getCanvas_();
|
||||
if (!canvas) {
|
||||
canvas = document.createElement('canvas');
|
||||
@ -61,7 +68,7 @@ export class ViewerThumbnailElement extends PolymerElement {
|
||||
// has restricted access rights.
|
||||
canvas.oncontextmenu = e => e.preventDefault();
|
||||
|
||||
this.shadowRoot.querySelector('#thumbnail').appendChild(canvas);
|
||||
this.$.thumbnail.appendChild(canvas);
|
||||
}
|
||||
|
||||
canvas.width = imageData.width;
|
||||
@ -69,7 +76,7 @@ export class ViewerThumbnailElement extends PolymerElement {
|
||||
|
||||
this.styleCanvas_();
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
const ctx = canvas.getContext('2d')!;
|
||||
ctx.putImageData(imageData, 0, 0);
|
||||
}
|
||||
|
||||
@ -87,26 +94,18 @@ export class ViewerThumbnailElement extends PolymerElement {
|
||||
this.removeAttribute(PAINTED_ATTRIBUTE);
|
||||
}
|
||||
|
||||
/** @return {!HTMLElement} */
|
||||
getClickTarget() {
|
||||
return /** @type {!HTMLElement} */ (
|
||||
this.shadowRoot.querySelector('#thumbnail'));
|
||||
getClickTarget(): HTMLElement {
|
||||
return this.$.thumbnail;
|
||||
}
|
||||
|
||||
/** @private */
|
||||
clockwiseRotationsChanged_() {
|
||||
private clockwiseRotationsChanged_() {
|
||||
if (this.getCanvas_()) {
|
||||
this.styleCanvas_();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {?HTMLCanvasElement}
|
||||
* @private
|
||||
*/
|
||||
getCanvas_() {
|
||||
return /** @type {?HTMLCanvasElement} */ (
|
||||
this.shadowRoot.querySelector('canvas'));
|
||||
private getCanvas_(): HTMLCanvasElement|null {
|
||||
return this.shadowRoot!.querySelector('canvas');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -114,12 +113,10 @@ export class ViewerThumbnailElement extends PolymerElement {
|
||||
* dimensions of the image data, and the screen resolution. The plugin
|
||||
* scales the thumbnail image data by the device to pixel ratio, so that
|
||||
* scaling must be taken into account on the UI.
|
||||
* @param {boolean} rotated
|
||||
* @return {!{width: number, height: number}}
|
||||
* @private
|
||||
*/
|
||||
getThumbnailCssSize_(rotated) {
|
||||
const canvas = this.getCanvas_();
|
||||
private getThumbnailCssSize_(rotated: boolean):
|
||||
{width: number, height: number} {
|
||||
const canvas = this.getCanvas_()!;
|
||||
const isPortrait = canvas.width < canvas.height !== rotated;
|
||||
const orientedWidth = rotated ? canvas.height : canvas.width;
|
||||
const orientedHeight = rotated ? canvas.width : canvas.height;
|
||||
@ -129,9 +126,9 @@ export class ViewerThumbnailElement extends PolymerElement {
|
||||
// thumbnail.
|
||||
const cssWidth = Math.min(
|
||||
isPortrait ? PORTRAIT_WIDTH : LANDSCAPE_WIDTH,
|
||||
parseInt(orientedWidth / window.devicePixelRatio, 10));
|
||||
Math.trunc(orientedWidth / window.devicePixelRatio));
|
||||
const scale = cssWidth / orientedWidth;
|
||||
const cssHeight = parseInt(orientedHeight * scale, 10);
|
||||
const cssHeight = Math.trunc(orientedHeight * scale);
|
||||
return {width: cssWidth, height: cssHeight};
|
||||
}
|
||||
|
||||
@ -146,8 +143,7 @@ export class ViewerThumbnailElement extends PolymerElement {
|
||||
this.focus({preventScroll: true});
|
||||
}
|
||||
|
||||
/** @return {boolean} */
|
||||
isPainted() {
|
||||
isPainted(): boolean {
|
||||
return this.hasAttribute(PAINTED_ATTRIBUTE);
|
||||
}
|
||||
|
||||
@ -155,31 +151,27 @@ export class ViewerThumbnailElement extends PolymerElement {
|
||||
this.toggleAttribute(PAINTED_ATTRIBUTE, true);
|
||||
}
|
||||
|
||||
/** @private */
|
||||
isActiveChanged_() {
|
||||
private isActiveChanged_() {
|
||||
if (this.isActive) {
|
||||
this.scrollIntoView({block: 'nearest'});
|
||||
}
|
||||
}
|
||||
|
||||
/** @private */
|
||||
focusThumbnailNext_() {
|
||||
private focusThumbnailNext_() {
|
||||
if (this.nextElementSibling &&
|
||||
this.nextElementSibling.matches('viewer-thumbnail')) {
|
||||
this.nextElementSibling.focusAndScroll();
|
||||
(this.nextElementSibling as ViewerThumbnailElement).focusAndScroll();
|
||||
}
|
||||
}
|
||||
|
||||
/** @private */
|
||||
focusThumbnailPrev_() {
|
||||
private focusThumbnailPrev_() {
|
||||
if (this.previousElementSibling &&
|
||||
this.previousElementSibling.matches('viewer-thumbnail')) {
|
||||
this.previousElementSibling.focusAndScroll();
|
||||
(this.previousElementSibling as ViewerThumbnailElement).focusAndScroll();
|
||||
}
|
||||
}
|
||||
|
||||
/** @private */
|
||||
onClick_() {
|
||||
private onClick_() {
|
||||
this.dispatchEvent(new CustomEvent('change-page', {
|
||||
detail: {page: this.pageNumber - 1, origin: 'thumbnail'},
|
||||
bubbles: true,
|
||||
@ -187,27 +179,22 @@ export class ViewerThumbnailElement extends PolymerElement {
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Event} e
|
||||
* @private
|
||||
*/
|
||||
onKeydown_(e) {
|
||||
const keyboardEvent = /** @type {!KeyboardEvent} */ (e);
|
||||
switch (keyboardEvent.key) {
|
||||
private onKeydown_(e: KeyboardEvent) {
|
||||
switch (e.key) {
|
||||
case 'ArrowDown':
|
||||
// Prevent default arrow scroll behavior.
|
||||
keyboardEvent.preventDefault();
|
||||
e.preventDefault();
|
||||
this.focusThumbnailNext_();
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
// Prevent default arrow scroll behavior.
|
||||
keyboardEvent.preventDefault();
|
||||
// e default arrow scroll behavior.
|
||||
e.preventDefault();
|
||||
this.focusThumbnailPrev_();
|
||||
break;
|
||||
case 'Enter':
|
||||
case ' ':
|
||||
// Prevent default space scroll behavior.
|
||||
keyboardEvent.preventDefault();
|
||||
e.preventDefault();
|
||||
this.onClick_();
|
||||
break;
|
||||
}
|
||||
@ -216,13 +203,12 @@ export class ViewerThumbnailElement extends PolymerElement {
|
||||
/**
|
||||
* Sets the canvas CSS size to maintain the resolution of the thumbnail at any
|
||||
* rotation.
|
||||
* @private
|
||||
*/
|
||||
styleCanvas_() {
|
||||
private styleCanvas_() {
|
||||
assert(this.clockwiseRotations >= 0 && this.clockwiseRotations < 4);
|
||||
|
||||
const canvas = this.getCanvas_();
|
||||
const div = this.shadowRoot.querySelector('#thumbnail');
|
||||
const canvas = this.getCanvas_()!;
|
||||
const div = this.shadowRoot!.querySelector<HTMLElement>('#thumbnail')!;
|
||||
|
||||
const degreesRotated = this.clockwiseRotations * 90;
|
||||
canvas.style.transform = `rotate(${degreesRotated}deg)`;
|
||||
@ -241,4 +227,10 @@ export class ViewerThumbnailElement extends PolymerElement {
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'viewer-thumbnail': ViewerThumbnailElement;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(ViewerThumbnailElement.is, ViewerThumbnailElement);
|
@ -26,15 +26,15 @@ if (enable_ink) {
|
||||
# Files that need to be passed to html_to_js() that are used only in PDF Viewer.
|
||||
pdf_webcomponents_files = [
|
||||
"elements/shared-css.js",
|
||||
"elements/viewer-bookmark.js",
|
||||
"elements/viewer-document-outline.js",
|
||||
"elements/viewer-download-controls.js",
|
||||
"elements/viewer-page-selector.js",
|
||||
"elements/viewer-password-dialog.js",
|
||||
"elements/viewer-pdf-sidenav.js",
|
||||
"elements/viewer-properties-dialog.js",
|
||||
"elements/viewer-thumbnail-bar.js",
|
||||
"elements/viewer-thumbnail.js",
|
||||
"elements/viewer-bookmark.ts",
|
||||
"elements/viewer-document-outline.ts",
|
||||
"elements/viewer-download-controls.ts",
|
||||
"elements/viewer-page-selector.ts",
|
||||
"elements/viewer-password-dialog.ts",
|
||||
"elements/viewer-pdf-sidenav.ts",
|
||||
"elements/viewer-properties-dialog.ts",
|
||||
"elements/viewer-thumbnail-bar.ts",
|
||||
"elements/viewer-thumbnail.ts",
|
||||
"elements/viewer-toolbar.js",
|
||||
"pdf_viewer.js",
|
||||
]
|
||||
|
@ -1026,7 +1026,7 @@ export class PDFViewerElement extends PDFViewerBaseElement {
|
||||
this.viewport.goToPage(e.detail.page);
|
||||
if (e.detail.origin === 'bookmark') {
|
||||
record(UserAction.FOLLOW_BOOKMARK);
|
||||
} else if (e.detail.origin === 'pageselector') {
|
||||
} else if (e.detail.origin === 'pageSelector') {
|
||||
record(UserAction.PAGE_SELECTOR_NAVIGATE);
|
||||
} else if (e.detail.origin === 'thumbnail') {
|
||||
record(UserAction.THUMBNAIL_NAVIGATE);
|
||||
|
@ -44,7 +44,7 @@ const tests = [
|
||||
|
||||
// Test case where an <input> field is focused.
|
||||
toolbar.shadowRoot.querySelector('viewer-page-selector')
|
||||
.pageSelector.focus();
|
||||
.$.pageSelector.focus();
|
||||
chrome.test.assertTrue(shouldIgnoreKeyEvents());
|
||||
|
||||
// Test case where another field is focused.
|
||||
|
@ -23,7 +23,7 @@ const tests = [
|
||||
selector.docLength = 1234;
|
||||
document.body.appendChild(selector);
|
||||
|
||||
const input = selector.pageSelector;
|
||||
const input = selector.$.pageSelector;
|
||||
// Simulate entering text into `input` and pressing enter.
|
||||
function changeInput(newValue) {
|
||||
input.value = newValue;
|
||||
|
@ -38,7 +38,7 @@ constexpr int kMaxThumbnailPixels = 255 * 1024 / kImageColorChannels;
|
||||
// Maximum CSS dimensions are set to match UX specifications.
|
||||
// These constants should be kept in sync with `PORTRAIT_WIDTH` and
|
||||
// `LANDSCAPE_WIDTH` in
|
||||
// chrome/browser/resources/pdf/elements/viewer-thumbnail.js.
|
||||
// chrome/browser/resources/pdf/elements/viewer-thumbnail.ts.
|
||||
constexpr int kMaxWidthPortraitPx = 108;
|
||||
constexpr int kMaxWidthLandscapePx = 140;
|
||||
|
||||
|
Reference in New Issue
Block a user