|  | /** | 
|  | * @license | 
|  | * Copyright (C) 2016 The Android Open Source Project | 
|  | * | 
|  | * 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. | 
|  | */ | 
|  | (function() { | 
|  | 'use strict'; | 
|  |  | 
|  | const ERR_REVIEW_STATUS = 'Couldn’t change file review status.'; | 
|  | const MSG_LOADING_BLAME = 'Loading blame...'; | 
|  | const MSG_LOADED_BLAME = 'Blame loaded'; | 
|  |  | 
|  | const PARENT = 'PARENT'; | 
|  |  | 
|  | const DiffSides = { | 
|  | LEFT: 'left', | 
|  | RIGHT: 'right', | 
|  | }; | 
|  |  | 
|  | const DiffViewMode = { | 
|  | SIDE_BY_SIDE: 'SIDE_BY_SIDE', | 
|  | UNIFIED: 'UNIFIED_DIFF', | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * @appliesMixin Gerrit.FireMixin | 
|  | * @appliesMixin Gerrit.KeyboardShortcutMixin | 
|  | * @appliesMixin Gerrit.PatchSetMixin | 
|  | * @appliesMixin Gerrit.PathListMixin | 
|  | * @appliesMixin Gerrit.RESTClientMixin | 
|  | * @extends Polymer.Element | 
|  | */ | 
|  | class GrDiffView extends Polymer.mixinBehaviors( [ | 
|  | Gerrit.FireBehavior, | 
|  | Gerrit.KeyboardShortcutBehavior, | 
|  | Gerrit.PatchSetBehavior, | 
|  | Gerrit.PathListBehavior, | 
|  | Gerrit.RESTClientBehavior, | 
|  | ], Polymer.GestureEventListeners( | 
|  | Polymer.LegacyElementMixin( | 
|  | Polymer.Element))) { | 
|  | static get is() { return 'gr-diff-view'; } | 
|  | /** | 
|  | * Fired when the title of the page should change. | 
|  | * | 
|  | * @event title-change | 
|  | */ | 
|  |  | 
|  | /** | 
|  | * Fired when user tries to navigate away while comments are pending save. | 
|  | * | 
|  | * @event show-alert | 
|  | */ | 
|  |  | 
|  | static get properties() { | 
|  | return { | 
|  | /** | 
|  | * URL params passed from the router. | 
|  | */ | 
|  | params: { | 
|  | type: Object, | 
|  | observer: '_paramsChanged', | 
|  | }, | 
|  | keyEventTarget: { | 
|  | type: Object, | 
|  | value() { return document.body; }, | 
|  | }, | 
|  | /** | 
|  | * @type {{ diffMode: (string|undefined) }} | 
|  | */ | 
|  | changeViewState: { | 
|  | type: Object, | 
|  | notify: true, | 
|  | value() { return {}; }, | 
|  | observer: '_changeViewStateChanged', | 
|  | }, | 
|  | disableDiffPrefs: { | 
|  | type: Boolean, | 
|  | value: false, | 
|  | }, | 
|  | _diffPrefsDisabled: { | 
|  | type: Boolean, | 
|  | computed: '_computeDiffPrefsDisabled(disableDiffPrefs, _loggedIn)', | 
|  | }, | 
|  | /** @type {?} */ | 
|  | _patchRange: Object, | 
|  | /** @type {?} */ | 
|  | _commitRange: Object, | 
|  | /** | 
|  | * @type {{ | 
|  | *  subject: string, | 
|  | *  project: string, | 
|  | *  revisions: string, | 
|  | * }} | 
|  | */ | 
|  | _change: Object, | 
|  | /** @type {?} */ | 
|  | _changeComments: Object, | 
|  | _changeNum: String, | 
|  | /** | 
|  | * This is a DiffInfo object. | 
|  | * This is retrieved and owned by a child component. | 
|  | */ | 
|  | _diff: Object, | 
|  | // An array specifically formatted to be used in a gr-dropdown-list | 
|  | // element for selected a file to view. | 
|  | _formattedFiles: { | 
|  | type: Array, | 
|  | computed: '_formatFilesForDropdown(_files, ' + | 
|  | '_patchRange.patchNum, _changeComments)', | 
|  | }, | 
|  | // An sorted array of files, as returned by the rest API. | 
|  | _fileList: { | 
|  | type: Array, | 
|  | computed: '_getSortedFileList(_files)', | 
|  | }, | 
|  | /** | 
|  | * Contains information about files as returned by the rest API. | 
|  | * | 
|  | * @type {{ sortedFileList: Array<string>, changeFilesByPath: Object }} | 
|  | */ | 
|  | _files: { | 
|  | type: Object, | 
|  | value() { return {sortedFileList: [], changeFilesByPath: {}}; }, | 
|  | }, | 
|  |  | 
|  | _path: { | 
|  | type: String, | 
|  | observer: '_pathChanged', | 
|  | }, | 
|  | _fileNum: { | 
|  | type: Number, | 
|  | computed: '_computeFileNum(_path, _formattedFiles)', | 
|  | }, | 
|  | _loggedIn: { | 
|  | type: Boolean, | 
|  | value: false, | 
|  | }, | 
|  | _loading: { | 
|  | type: Boolean, | 
|  | value: true, | 
|  | }, | 
|  | _prefs: Object, | 
|  | _localPrefs: Object, | 
|  | _projectConfig: Object, | 
|  | _userPrefs: Object, | 
|  | _diffMode: { | 
|  | type: String, | 
|  | computed: '_getDiffViewMode(changeViewState.diffMode, _userPrefs)', | 
|  | }, | 
|  | _isImageDiff: Boolean, | 
|  | _filesWeblinks: Object, | 
|  |  | 
|  | /** | 
|  | * Map of paths in the current change and patch range that have comments | 
|  | * or drafts or robot comments. | 
|  | */ | 
|  | _commentMap: Object, | 
|  |  | 
|  | _commentsForDiff: Object, | 
|  |  | 
|  | /** | 
|  | * Object to contain the path of the next and previous file in the current | 
|  | * change and patch range that has comments. | 
|  | */ | 
|  | _commentSkips: { | 
|  | type: Object, | 
|  | computed: '_computeCommentSkips(_commentMap, _fileList, _path)', | 
|  | }, | 
|  | _panelFloatingDisabled: { | 
|  | type: Boolean, | 
|  | value: () => window.PANEL_FLOATING_DISABLED, | 
|  | }, | 
|  | _editMode: { | 
|  | type: Boolean, | 
|  | computed: '_computeEditMode(_patchRange.*)', | 
|  | }, | 
|  | _isBlameLoaded: Boolean, | 
|  | _isBlameLoading: { | 
|  | type: Boolean, | 
|  | value: false, | 
|  | }, | 
|  | _allPatchSets: { | 
|  | type: Array, | 
|  | computed: 'computeAllPatchSets(_change, _change.revisions.*)', | 
|  | }, | 
|  | _revisionInfo: { | 
|  | type: Object, | 
|  | computed: '_getRevisionInfo(_change)', | 
|  | }, | 
|  | _reviewedFiles: { | 
|  | type: Object, | 
|  | value: () => new Set(), | 
|  | }, | 
|  |  | 
|  | /** | 
|  | * gr-diff-view has gr-fixed-panel on top. The panel can | 
|  | * intersect a main element and partially hides a content of | 
|  | * the main element. To correctly calculates visibility of an | 
|  | * element, the cursor must know how much height occuped by a fixed | 
|  | * panel. | 
|  | * The scrollTopMargin defines margin occuped by fixed panel. | 
|  | */ | 
|  | _scrollTopMargin: { | 
|  | type: Number, | 
|  | value: 0, | 
|  | }, | 
|  | }; | 
|  | } | 
|  |  | 
|  | static get observers() { | 
|  | return [ | 
|  | '_getProjectConfig(_change.project)', | 
|  | '_getFiles(_changeNum, _patchRange.*, _changeComments)', | 
|  | '_setReviewedObserver(_loggedIn, params.*, _prefs)', | 
|  | ]; | 
|  | } | 
|  |  | 
|  | get keyBindings() { | 
|  | return { | 
|  | esc: '_handleEscKey', | 
|  | }; | 
|  | } | 
|  |  | 
|  | keyboardShortcuts() { | 
|  | return { | 
|  | [this.Shortcut.LEFT_PANE]: '_handleLeftPane', | 
|  | [this.Shortcut.RIGHT_PANE]: '_handleRightPane', | 
|  | [this.Shortcut.NEXT_LINE]: '_handleNextLineOrFileWithComments', | 
|  | [this.Shortcut.PREV_LINE]: '_handlePrevLineOrFileWithComments', | 
|  | [this.Shortcut.VISIBLE_LINE]: '_handleVisibleLine', | 
|  | [this.Shortcut.NEXT_FILE_WITH_COMMENTS]: | 
|  | '_handleNextLineOrFileWithComments', | 
|  | [this.Shortcut.PREV_FILE_WITH_COMMENTS]: | 
|  | '_handlePrevLineOrFileWithComments', | 
|  | [this.Shortcut.NEW_COMMENT]: '_handleNewComment', | 
|  | [this.Shortcut.SAVE_COMMENT]: null, // DOC_ONLY binding | 
|  | [this.Shortcut.NEXT_FILE]: '_handleNextFile', | 
|  | [this.Shortcut.PREV_FILE]: '_handlePrevFile', | 
|  | [this.Shortcut.NEXT_CHUNK]: '_handleNextChunkOrCommentThread', | 
|  | [this.Shortcut.NEXT_COMMENT_THREAD]: '_handleNextChunkOrCommentThread', | 
|  | [this.Shortcut.PREV_CHUNK]: '_handlePrevChunkOrCommentThread', | 
|  | [this.Shortcut.PREV_COMMENT_THREAD]: '_handlePrevChunkOrCommentThread', | 
|  | [this.Shortcut.OPEN_REPLY_DIALOG]: | 
|  | '_handleOpenReplyDialogOrToggleLeftPane', | 
|  | [this.Shortcut.TOGGLE_LEFT_PANE]: | 
|  | '_handleOpenReplyDialogOrToggleLeftPane', | 
|  | [this.Shortcut.UP_TO_CHANGE]: '_handleUpToChange', | 
|  | [this.Shortcut.OPEN_DIFF_PREFS]: '_handleCommaKey', | 
|  | [this.Shortcut.TOGGLE_DIFF_MODE]: '_handleToggleDiffMode', | 
|  | [this.Shortcut.TOGGLE_FILE_REVIEWED]: '_handleToggleFileReviewed', | 
|  | [this.Shortcut.EXPAND_ALL_DIFF_CONTEXT]: '_handleExpandAllDiffContext', | 
|  | [this.Shortcut.NEXT_UNREVIEWED_FILE]: '_handleNextUnreviewedFile', | 
|  | [this.Shortcut.TOGGLE_BLAME]: '_toggleBlame', | 
|  |  | 
|  | // Final two are actually handled by gr-comment-thread. | 
|  | [this.Shortcut.EXPAND_ALL_COMMENT_THREADS]: null, | 
|  | [this.Shortcut.COLLAPSE_ALL_COMMENT_THREADS]: null, | 
|  | }; | 
|  | } | 
|  |  | 
|  | /** @override */ | 
|  | attached() { | 
|  | super.attached(); | 
|  | this._getLoggedIn().then(loggedIn => { | 
|  | this._loggedIn = loggedIn; | 
|  | }); | 
|  |  | 
|  | this.addEventListener('open-fix-preview', | 
|  | this._onOpenFixPreview.bind(this)); | 
|  | this.$.cursor.push('diffs', this.$.diffHost); | 
|  | } | 
|  |  | 
|  | _getLoggedIn() { | 
|  | return this.$.restAPI.getLoggedIn(); | 
|  | } | 
|  |  | 
|  | _getProjectConfig(project) { | 
|  | return this.$.restAPI.getProjectConfig(project).then( | 
|  | config => { | 
|  | this._projectConfig = config; | 
|  | }); | 
|  | } | 
|  |  | 
|  | _getChangeDetail(changeNum) { | 
|  | return this.$.restAPI.getDiffChangeDetail(changeNum).then(change => { | 
|  | this._change = change; | 
|  | return change; | 
|  | }); | 
|  | } | 
|  |  | 
|  | _getChangeEdit(changeNum) { | 
|  | return this.$.restAPI.getChangeEdit(this._changeNum); | 
|  | } | 
|  |  | 
|  | _getSortedFileList(files) { | 
|  | return files.sortedFileList; | 
|  | } | 
|  |  | 
|  | _getFiles(changeNum, patchRangeRecord, changeComments) { | 
|  | // Polymer 2: check for undefined | 
|  | if ([changeNum, patchRangeRecord, patchRangeRecord.base, changeComments] | 
|  | .some(arg => arg === undefined)) { | 
|  | return Promise.resolve(); | 
|  | } | 
|  |  | 
|  | const patchRange = patchRangeRecord.base; | 
|  | return this.$.restAPI.getChangeFiles( | 
|  | changeNum, patchRange).then(changeFiles => { | 
|  | if (!changeFiles) return; | 
|  | const commentedPaths = changeComments.getPaths(patchRange); | 
|  | const files = Object.assign({}, changeFiles); | 
|  | Object.keys(commentedPaths).forEach(commentedPath => { | 
|  | if (files.hasOwnProperty(commentedPath)) { return; } | 
|  | files[commentedPath] = {status: 'U'}; | 
|  | }); | 
|  | this._files = { | 
|  | sortedFileList: Object.keys(files).sort(this.specialFilePathCompare), | 
|  | changeFilesByPath: files, | 
|  | }; | 
|  | }); | 
|  | } | 
|  |  | 
|  | _getDiffPreferences() { | 
|  | return this.$.restAPI.getDiffPreferences().then(prefs => { | 
|  | this._prefs = prefs; | 
|  | }); | 
|  | } | 
|  |  | 
|  | _getPreferences() { | 
|  | return this.$.restAPI.getPreferences(); | 
|  | } | 
|  |  | 
|  | _getWindowWidth() { | 
|  | return window.innerWidth; | 
|  | } | 
|  |  | 
|  | _handleReviewedChange(e) { | 
|  | this._setReviewed(Polymer.dom(e).rootTarget.checked); | 
|  | } | 
|  |  | 
|  | _setReviewed(reviewed) { | 
|  | if (this._editMode) { return; } | 
|  | this.$.reviewed.checked = reviewed; | 
|  | this._saveReviewedState(reviewed).catch(err => { | 
|  | this.fire('show-alert', {message: ERR_REVIEW_STATUS}); | 
|  | throw err; | 
|  | }); | 
|  | } | 
|  |  | 
|  | _saveReviewedState(reviewed) { | 
|  | return this.$.restAPI.saveFileReviewed(this._changeNum, | 
|  | this._patchRange.patchNum, this._path, reviewed); | 
|  | } | 
|  |  | 
|  | _handleToggleFileReviewed(e) { | 
|  | if (this.shouldSuppressKeyboardShortcut(e) || | 
|  | this.modifierPressed(e)) { return; } | 
|  |  | 
|  | e.preventDefault(); | 
|  | this._setReviewed(!this.$.reviewed.checked); | 
|  | } | 
|  |  | 
|  | _handleEscKey(e) { | 
|  | if (this.shouldSuppressKeyboardShortcut(e) || | 
|  | this.modifierPressed(e)) { return; } | 
|  |  | 
|  | e.preventDefault(); | 
|  | this.$.diffHost.displayLine = false; | 
|  | } | 
|  |  | 
|  | _handleLeftPane(e) { | 
|  | if (this.shouldSuppressKeyboardShortcut(e)) { return; } | 
|  |  | 
|  | e.preventDefault(); | 
|  | this.$.cursor.moveLeft(); | 
|  | } | 
|  |  | 
|  | _handleRightPane(e) { | 
|  | if (this.shouldSuppressKeyboardShortcut(e)) { return; } | 
|  |  | 
|  | e.preventDefault(); | 
|  | this.$.cursor.moveRight(); | 
|  | } | 
|  |  | 
|  | _handlePrevLineOrFileWithComments(e) { | 
|  | if (this.shouldSuppressKeyboardShortcut(e)) { return; } | 
|  | if (e.detail.keyboardEvent.shiftKey && | 
|  | e.detail.keyboardEvent.keyCode === 75) { // 'K' | 
|  | this._moveToPreviousFileWithComment(); | 
|  | return; | 
|  | } | 
|  | if (this.modifierPressed(e)) { return; } | 
|  |  | 
|  | e.preventDefault(); | 
|  | this.$.diffHost.displayLine = true; | 
|  | this.$.cursor.moveUp(); | 
|  | } | 
|  |  | 
|  | _handleVisibleLine(e) { | 
|  | if (this.shouldSuppressKeyboardShortcut(e)) { return; } | 
|  |  | 
|  | e.preventDefault(); | 
|  | this.$.cursor.moveToVisibleArea(); | 
|  | } | 
|  |  | 
|  | _onOpenFixPreview(e) { | 
|  | this.$.applyFixDialog.open(e); | 
|  | } | 
|  |  | 
|  | _handleNextLineOrFileWithComments(e) { | 
|  | if (this.shouldSuppressKeyboardShortcut(e)) { return; } | 
|  | if (e.detail.keyboardEvent.shiftKey && | 
|  | e.detail.keyboardEvent.keyCode === 74) { // 'J' | 
|  | this._moveToNextFileWithComment(); | 
|  | return; | 
|  | } | 
|  | if (this.modifierPressed(e)) { return; } | 
|  |  | 
|  | e.preventDefault(); | 
|  | this.$.diffHost.displayLine = true; | 
|  | this.$.cursor.moveDown(); | 
|  | } | 
|  |  | 
|  | _moveToPreviousFileWithComment() { | 
|  | if (!this._commentSkips) { return; } | 
|  |  | 
|  | // If there is no previous diff with comments, then return to the change | 
|  | // view. | 
|  | if (!this._commentSkips.previous) { | 
|  | this._navToChangeView(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | Gerrit.Nav.navigateToDiff(this._change, this._commentSkips.previous, | 
|  | this._patchRange.patchNum, this._patchRange.basePatchNum); | 
|  | } | 
|  |  | 
|  | _moveToNextFileWithComment() { | 
|  | if (!this._commentSkips) { return; } | 
|  |  | 
|  | // If there is no next diff with comments, then return to the change view. | 
|  | if (!this._commentSkips.next) { | 
|  | this._navToChangeView(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | Gerrit.Nav.navigateToDiff(this._change, this._commentSkips.next, | 
|  | this._patchRange.patchNum, this._patchRange.basePatchNum); | 
|  | } | 
|  |  | 
|  | _handleNewComment(e) { | 
|  | if (this.shouldSuppressKeyboardShortcut(e) || | 
|  | this.modifierPressed(e)) { return; } | 
|  | e.preventDefault(); | 
|  | this.$.cursor.createCommentInPlace(); | 
|  | } | 
|  |  | 
|  | _handlePrevFile(e) { | 
|  | // Check for meta key to avoid overriding native chrome shortcut. | 
|  | if (this.shouldSuppressKeyboardShortcut(e) || | 
|  | this.getKeyboardEvent(e).metaKey) { return; } | 
|  |  | 
|  | e.preventDefault(); | 
|  | this._navToFile(this._path, this._fileList, -1); | 
|  | } | 
|  |  | 
|  | _handleNextFile(e) { | 
|  | // Check for meta key to avoid overriding native chrome shortcut. | 
|  | if (this.shouldSuppressKeyboardShortcut(e) || | 
|  | this.getKeyboardEvent(e).metaKey) { return; } | 
|  |  | 
|  | e.preventDefault(); | 
|  | this._navToFile(this._path, this._fileList, 1); | 
|  | } | 
|  |  | 
|  | _handleNextChunkOrCommentThread(e) { | 
|  | if (this.shouldSuppressKeyboardShortcut(e)) { return; } | 
|  |  | 
|  | e.preventDefault(); | 
|  | if (e.detail.keyboardEvent.shiftKey) { | 
|  | this.$.cursor.moveToNextCommentThread(); | 
|  | } else { | 
|  | if (this.modifierPressed(e)) { return; } | 
|  | this.$.cursor.moveToNextChunk(); | 
|  | } | 
|  | } | 
|  |  | 
|  | _handlePrevChunkOrCommentThread(e) { | 
|  | if (this.shouldSuppressKeyboardShortcut(e)) { return; } | 
|  |  | 
|  | e.preventDefault(); | 
|  | if (e.detail.keyboardEvent.shiftKey) { | 
|  | this.$.cursor.moveToPreviousCommentThread(); | 
|  | } else { | 
|  | if (this.modifierPressed(e)) { return; } | 
|  | this.$.cursor.moveToPreviousChunk(); | 
|  | } | 
|  | } | 
|  |  | 
|  | _handleOpenReplyDialogOrToggleLeftPane(e) { | 
|  | if (this.shouldSuppressKeyboardShortcut(e)) { return; } | 
|  |  | 
|  | if (e.detail.keyboardEvent.shiftKey) { // Hide left diff. | 
|  | e.preventDefault(); | 
|  | this.$.diffHost.toggleLeftDiff(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (this.modifierPressed(e)) { return; } | 
|  |  | 
|  | if (!this._loggedIn) { return; } | 
|  |  | 
|  | this.set('changeViewState.showReplyDialog', true); | 
|  | e.preventDefault(); | 
|  | this._navToChangeView(); | 
|  | } | 
|  |  | 
|  | _handleUpToChange(e) { | 
|  | if (this.shouldSuppressKeyboardShortcut(e) || | 
|  | this.modifierPressed(e)) { return; } | 
|  |  | 
|  | e.preventDefault(); | 
|  | this._navToChangeView(); | 
|  | } | 
|  |  | 
|  | _handleCommaKey(e) { | 
|  | if (this.shouldSuppressKeyboardShortcut(e) || | 
|  | this.modifierPressed(e)) { return; } | 
|  | if (this._diffPrefsDisabled) { return; } | 
|  |  | 
|  | e.preventDefault(); | 
|  | this.$.diffPreferencesDialog.open(); | 
|  | } | 
|  |  | 
|  | _handleToggleDiffMode(e) { | 
|  | if (this.shouldSuppressKeyboardShortcut(e) || | 
|  | this.modifierPressed(e)) { return; } | 
|  |  | 
|  | e.preventDefault(); | 
|  | if (this._getDiffViewMode() === DiffViewMode.SIDE_BY_SIDE) { | 
|  | this.$.modeSelect.setMode(DiffViewMode.UNIFIED); | 
|  | } else { | 
|  | this.$.modeSelect.setMode(DiffViewMode.SIDE_BY_SIDE); | 
|  | } | 
|  | } | 
|  |  | 
|  | _navToChangeView() { | 
|  | if (!this._changeNum || !this._patchRange.patchNum) { return; } | 
|  | this._navigateToChange( | 
|  | this._change, | 
|  | this._patchRange, | 
|  | this._change && this._change.revisions); | 
|  | } | 
|  |  | 
|  | _navToFile(path, fileList, direction) { | 
|  | const newPath = this._getNavLinkPath(path, fileList, direction); | 
|  | if (!newPath) { return; } | 
|  |  | 
|  | if (newPath.up) { | 
|  | this._navigateToChange( | 
|  | this._change, | 
|  | this._patchRange, | 
|  | this._change && this._change.revisions); | 
|  | return; | 
|  | } | 
|  |  | 
|  | Gerrit.Nav.navigateToDiff(this._change, newPath.path, | 
|  | this._patchRange.patchNum, this._patchRange.basePatchNum); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {?string} path The path of the current file being shown. | 
|  | * @param {!Array<string>} fileList The list of files in this change and | 
|  | *     patch range. | 
|  | * @param {number} direction Either 1 (next file) or -1 (prev file). | 
|  | * @param {(number|boolean)} opt_noUp Whether to return to the change view | 
|  | *     when advancing the file goes outside the bounds of fileList. | 
|  | * | 
|  | * @return {?string} The next URL when proceeding in the specified | 
|  | *     direction. | 
|  | */ | 
|  | _computeNavLinkURL(change, path, fileList, direction, opt_noUp) { | 
|  | const newPath = this._getNavLinkPath(path, fileList, direction, opt_noUp); | 
|  | if (!newPath) { return null; } | 
|  |  | 
|  | if (newPath.up) { | 
|  | return this._getChangePath( | 
|  | this._change, | 
|  | this._patchRange, | 
|  | this._change && this._change.revisions); | 
|  | } | 
|  | return this._getDiffUrl(this._change, this._patchRange, newPath.path); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Gives an object representing the target of navigating either left or | 
|  | * right through the change. The resulting object will have one of the | 
|  | * following forms: | 
|  | *   * {path: "<target file path>"} - When another file path should be the | 
|  | *     result of the navigation. | 
|  | *   * {up: true} - When the result of navigating should go back to the | 
|  | *     change view. | 
|  | *   * null - When no navigation is possible for the given direction. | 
|  | * | 
|  | * @param {?string} path The path of the current file being shown. | 
|  | * @param {!Array<string>} fileList The list of files in this change and | 
|  | *     patch range. | 
|  | * @param {number} direction Either 1 (next file) or -1 (prev file). | 
|  | * @param {?number|boolean=} opt_noUp Whether to return to the change view | 
|  | *     when advancing the file goes outside the bounds of fileList. | 
|  | * @return {?Object} | 
|  | */ | 
|  | _getNavLinkPath(path, fileList, direction, opt_noUp) { | 
|  | if (!path || !fileList || fileList.length === 0) { return null; } | 
|  |  | 
|  | let idx = fileList.indexOf(path); | 
|  | if (idx === -1) { | 
|  | const file = direction > 0 ? | 
|  | fileList[0] : | 
|  | fileList[fileList.length - 1]; | 
|  | return {path: file}; | 
|  | } | 
|  |  | 
|  | idx += direction; | 
|  | // Redirect to the change view if opt_noUp isn’t truthy and idx falls | 
|  | // outside the bounds of [0, fileList.length). | 
|  | if (idx < 0 || idx > fileList.length - 1) { | 
|  | if (opt_noUp) { return null; } | 
|  | return {up: true}; | 
|  | } | 
|  |  | 
|  | return {path: fileList[idx]}; | 
|  | } | 
|  |  | 
|  | _getReviewedFiles(changeNum, patchNum) { | 
|  | return this.$.restAPI.getReviewedFiles(changeNum, patchNum) | 
|  | .then(files => { | 
|  | this._reviewedFiles = new Set(files); | 
|  | return this._reviewedFiles; | 
|  | }); | 
|  | } | 
|  |  | 
|  | _getReviewedStatus(editMode, changeNum, patchNum, path) { | 
|  | if (editMode) { return Promise.resolve(false); } | 
|  | return this._getReviewedFiles(changeNum, patchNum) | 
|  | .then(files => files.has(path)); | 
|  | } | 
|  |  | 
|  | _paramsChanged(value) { | 
|  | if (value.view !== Gerrit.Nav.View.DIFF) { return; } | 
|  |  | 
|  | if (value.changeNum && value.project) { | 
|  | this.$.restAPI.setInProjectLookup(value.changeNum, value.project); | 
|  | } | 
|  |  | 
|  | this.$.diffHost.lineOfInterest = this._getLineOfInterest(this.params); | 
|  | this._initCursor(this.params); | 
|  |  | 
|  | this._changeNum = value.changeNum; | 
|  | this._path = value.path; | 
|  | this._patchRange = { | 
|  | patchNum: value.patchNum, | 
|  | basePatchNum: value.basePatchNum || PARENT, | 
|  | }; | 
|  |  | 
|  | // NOTE: This may be called before attachment (e.g. while parentElement is | 
|  | // null). Fire title-change in an async so that, if attachment to the DOM | 
|  | // has been queued, the event can bubble up to the handler in gr-app. | 
|  | this.async(() => { | 
|  | this.fire('title-change', | 
|  | {title: this.computeTruncatedPath(this._path)}); | 
|  | }); | 
|  |  | 
|  | // When navigating away from the page, there is a possibility that the | 
|  | // patch number is no longer a part of the URL (say when navigating to | 
|  | // the top-level change info view) and therefore undefined in `params`. | 
|  | if (!this._patchRange.patchNum) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | const promises = []; | 
|  |  | 
|  | promises.push(this._getDiffPreferences()); | 
|  |  | 
|  | promises.push(this._getPreferences().then(prefs => { | 
|  | this._userPrefs = prefs; | 
|  | })); | 
|  |  | 
|  | promises.push(this._getChangeDetail(this._changeNum).then(change => { | 
|  | let commit; | 
|  | let baseCommit; | 
|  | if (change) { | 
|  | for (const commitSha in change.revisions) { | 
|  | if (!change.revisions.hasOwnProperty(commitSha)) continue; | 
|  | const revision = change.revisions[commitSha]; | 
|  | const patchNum = revision._number.toString(); | 
|  | if (patchNum === this._patchRange.patchNum) { | 
|  | commit = commitSha; | 
|  | const commitObj = revision.commit || {}; | 
|  | const parents = commitObj.parents || []; | 
|  | if (this._patchRange.basePatchNum === PARENT && parents.length) { | 
|  | baseCommit = parents[parents.length - 1].commit; | 
|  | } | 
|  | } else if (patchNum === this._patchRange.basePatchNum) { | 
|  | baseCommit = commitSha; | 
|  | } | 
|  | } | 
|  | this._commitRange = {commit, baseCommit}; | 
|  | } | 
|  | })); | 
|  |  | 
|  | promises.push(this._loadComments()); | 
|  |  | 
|  | promises.push(this._getChangeEdit(this._changeNum)); | 
|  |  | 
|  | this._loading = true; | 
|  | return Promise.all(promises) | 
|  | .then(r => { | 
|  | const edit = r[4]; | 
|  | if (edit) { | 
|  | this.set('_change.revisions.' + edit.commit.commit, { | 
|  | _number: this.EDIT_NAME, | 
|  | basePatchNum: edit.base_patch_set_number, | 
|  | commit: edit.commit, | 
|  | }); | 
|  | } | 
|  | this._loading = false; | 
|  | this.$.diffHost.comments = this._commentsForDiff; | 
|  | return this.$.diffHost.reload(true); | 
|  | }) | 
|  | .then(() => { | 
|  | this.$.reporting.diffViewFullyLoaded(); | 
|  | // If diff view displayed has not ended yet, it ends here. | 
|  | this.$.reporting.diffViewDisplayed(); | 
|  | }); | 
|  | } | 
|  |  | 
|  | _changeViewStateChanged(changeViewState) { | 
|  | if (changeViewState.diffMode === null) { | 
|  | // If screen size is small, always default to unified view. | 
|  | this.$.restAPI.getPreferences().then(prefs => { | 
|  | this.set('changeViewState.diffMode', prefs.default_diff_view); | 
|  | }); | 
|  | } | 
|  | } | 
|  |  | 
|  | _setReviewedObserver(_loggedIn, paramsRecord, _prefs) { | 
|  | // Polymer 2: check for undefined | 
|  | if ([_loggedIn, paramsRecord, _prefs].some(arg => arg === undefined)) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | const params = paramsRecord.base || {}; | 
|  | if (!_loggedIn) { return; } | 
|  |  | 
|  | if (_prefs.manual_review) { | 
|  | // Checkbox state needs to be set explicitly only when manual_review | 
|  | // is specified. | 
|  | this._getReviewedStatus(this.editMode, this._changeNum, | 
|  | this._patchRange.patchNum, this._path).then(status => { | 
|  | this.$.reviewed.checked = status; | 
|  | }); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (params.view === Gerrit.Nav.View.DIFF) { | 
|  | this._setReviewed(true); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * If the params specify a diff address then configure the diff cursor. | 
|  | */ | 
|  | _initCursor(params) { | 
|  | if (params.lineNum === undefined) { return; } | 
|  | if (params.leftSide) { | 
|  | this.$.cursor.side = DiffSides.LEFT; | 
|  | } else { | 
|  | this.$.cursor.side = DiffSides.RIGHT; | 
|  | } | 
|  | this.$.cursor.initialLineNumber = params.lineNum; | 
|  | } | 
|  |  | 
|  | _getLineOfInterest(params) { | 
|  | // If there is a line number specified, pass it along to the diff so that | 
|  | // it will not get collapsed. | 
|  | if (!params.lineNum) { return null; } | 
|  | return {number: params.lineNum, leftSide: params.leftSide}; | 
|  | } | 
|  |  | 
|  | _pathChanged(path) { | 
|  | if (path) { | 
|  | this.fire('title-change', | 
|  | {title: this.computeTruncatedPath(path)}); | 
|  | } | 
|  |  | 
|  | if (this._fileList.length == 0) { return; } | 
|  |  | 
|  | this.set('changeViewState.selectedFileIndex', | 
|  | this._fileList.indexOf(path)); | 
|  | } | 
|  |  | 
|  | _getDiffUrl(change, patchRange, path) { | 
|  | if ([change, patchRange, path].some(arg => arg === undefined)) { | 
|  | return ''; | 
|  | } | 
|  | return Gerrit.Nav.getUrlForDiff(change, path, patchRange.patchNum, | 
|  | patchRange.basePatchNum); | 
|  | } | 
|  |  | 
|  | _patchRangeStr(patchRange) { | 
|  | let patchStr = patchRange.patchNum; | 
|  | if (patchRange.basePatchNum != null && | 
|  | patchRange.basePatchNum != PARENT) { | 
|  | patchStr = patchRange.basePatchNum + '..' + patchRange.patchNum; | 
|  | } | 
|  | return patchStr; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * When the latest patch of the change is selected (and there is no base | 
|  | * patch) then the patch range need not appear in the URL. Return a patch | 
|  | * range object with undefined values when a range is not needed. | 
|  | * | 
|  | * @param {!Object} patchRange | 
|  | * @param {!Object} revisions | 
|  | * @return {!Object} | 
|  | */ | 
|  | _getChangeUrlRange(patchRange, revisions) { | 
|  | let patchNum = undefined; | 
|  | let basePatchNum = undefined; | 
|  | let latestPatchNum = -1; | 
|  | for (const rev of Object.values(revisions || {})) { | 
|  | latestPatchNum = Math.max(latestPatchNum, rev._number); | 
|  | } | 
|  | if (patchRange.basePatchNum !== PARENT || | 
|  | parseInt(patchRange.patchNum, 10) !== latestPatchNum) { | 
|  | patchNum = patchRange.patchNum; | 
|  | basePatchNum = patchRange.basePatchNum; | 
|  | } | 
|  | return {patchNum, basePatchNum}; | 
|  | } | 
|  |  | 
|  | _getChangePath(change, patchRange, revisions) { | 
|  | if ([change, patchRange].some(arg => arg === undefined)) { | 
|  | return ''; | 
|  | } | 
|  | const range = this._getChangeUrlRange(patchRange, revisions); | 
|  | return Gerrit.Nav.getUrlForChange(change, range.patchNum, | 
|  | range.basePatchNum); | 
|  | } | 
|  |  | 
|  | _navigateToChange(change, patchRange, revisions) { | 
|  | const range = this._getChangeUrlRange(patchRange, revisions); | 
|  | Gerrit.Nav.navigateToChange(change, range.patchNum, range.basePatchNum); | 
|  | } | 
|  |  | 
|  | _computeChangePath(change, patchRangeRecord, revisions) { | 
|  | return this._getChangePath(change, patchRangeRecord.base, revisions); | 
|  | } | 
|  |  | 
|  | _formatFilesForDropdown(files, patchNum, changeComments) { | 
|  | // Polymer 2: check for undefined | 
|  | if ([ | 
|  | files, | 
|  | patchNum, | 
|  | changeComments, | 
|  | ].some(arg => arg === undefined)) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (!files) { return; } | 
|  | const dropdownContent = []; | 
|  | for (const path of files.sortedFileList) { | 
|  | dropdownContent.push({ | 
|  | text: this.computeDisplayPath(path), | 
|  | mobileText: this.computeTruncatedPath(path), | 
|  | value: path, | 
|  | bottomText: this._computeCommentString(changeComments, patchNum, | 
|  | path, files.changeFilesByPath[path]), | 
|  | }); | 
|  | } | 
|  | return dropdownContent; | 
|  | } | 
|  |  | 
|  | _computeCommentString(changeComments, patchNum, path, changeFileInfo) { | 
|  | const unresolvedCount = changeComments.computeUnresolvedNum(patchNum, | 
|  | path); | 
|  | const commentCount = changeComments.computeCommentCount(patchNum, path); | 
|  | const commentString = GrCountStringFormatter.computePluralString( | 
|  | commentCount, 'comment'); | 
|  | const unresolvedString = GrCountStringFormatter.computeString( | 
|  | unresolvedCount, 'unresolved'); | 
|  |  | 
|  | const unmodifiedString = changeFileInfo.status === 'U' ? 'no changes': ''; | 
|  |  | 
|  | return [ | 
|  | unmodifiedString, | 
|  | commentString, | 
|  | unresolvedString] | 
|  | .filter(v => v && v.length > 0).join(', '); | 
|  | } | 
|  |  | 
|  | _computePrefsButtonHidden(prefs, prefsDisabled) { | 
|  | return prefsDisabled || !prefs; | 
|  | } | 
|  |  | 
|  | _handleFileChange(e) { | 
|  | // This is when it gets set initially. | 
|  | const path = e.detail.value; | 
|  | if (path === this._path) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | Gerrit.Nav.navigateToDiff(this._change, path, this._patchRange.patchNum, | 
|  | this._patchRange.basePatchNum); | 
|  | } | 
|  |  | 
|  | _handleFileTap(e) { | 
|  | // async is needed so that that the click event is fired before the | 
|  | // dropdown closes (This was a bug for touch devices). | 
|  | this.async(() => { | 
|  | this.$.dropdown.close(); | 
|  | }, 1); | 
|  | } | 
|  |  | 
|  | _handlePatchChange(e) { | 
|  | const {basePatchNum, patchNum} = e.detail; | 
|  | if (this.patchNumEquals(basePatchNum, this._patchRange.basePatchNum) && | 
|  | this.patchNumEquals(patchNum, this._patchRange.patchNum)) { return; } | 
|  | Gerrit.Nav.navigateToDiff( | 
|  | this._change, this._path, patchNum, basePatchNum); | 
|  | } | 
|  |  | 
|  | _handlePrefsTap(e) { | 
|  | e.preventDefault(); | 
|  | this.$.diffPreferencesDialog.open(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * _getDiffViewMode: Get the diff view (side-by-side or unified) based on | 
|  | * the current state. | 
|  | * | 
|  | * The expected behavior is to use the mode specified in the user's | 
|  | * preferences unless they have manually chosen the alternative view or they | 
|  | * are on a mobile device. If the user navigates up to the change view, it | 
|  | * should clear this choice and revert to the preference the next time a | 
|  | * diff is viewed. | 
|  | * | 
|  | * Use side-by-side if the user is not logged in. | 
|  | * | 
|  | * @return {string} | 
|  | */ | 
|  | _getDiffViewMode() { | 
|  | if (this.changeViewState.diffMode) { | 
|  | return this.changeViewState.diffMode; | 
|  | } else if (this._userPrefs) { | 
|  | this.set('changeViewState.diffMode', this._userPrefs.default_diff_view); | 
|  | return this._userPrefs.default_diff_view; | 
|  | } else { | 
|  | return 'SIDE_BY_SIDE'; | 
|  | } | 
|  | } | 
|  |  | 
|  | _computeModeSelectHideClass(isImageDiff) { | 
|  | return isImageDiff ? 'hide' : ''; | 
|  | } | 
|  |  | 
|  | _onLineSelected(e, detail) { | 
|  | this.$.cursor.moveToLineNumber(detail.number, detail.side); | 
|  | if (!this._change) { return; } | 
|  | const cursorAddress = this.$.cursor.getAddress(); | 
|  | const number = cursorAddress ? cursorAddress.number : undefined; | 
|  | const leftSide = cursorAddress ? cursorAddress.leftSide : undefined; | 
|  | const url = Gerrit.Nav.getUrlForDiffById(this._changeNum, | 
|  | this._change.project, this._path, this._patchRange.patchNum, | 
|  | this._patchRange.basePatchNum, number, leftSide); | 
|  | history.replaceState(null, '', url); | 
|  | } | 
|  |  | 
|  | _computeDownloadDropdownLinks( | 
|  | project, changeNum, patchRange, path, diff) { | 
|  | if (!patchRange || !patchRange.patchNum) { return []; } | 
|  |  | 
|  | const links = [ | 
|  | { | 
|  | url: this._computeDownloadPatchLink( | 
|  | project, changeNum, patchRange, path), | 
|  | name: 'Patch', | 
|  | }, | 
|  | ]; | 
|  |  | 
|  | if (diff && diff.meta_a) { | 
|  | let leftPath = path; | 
|  | if (diff.change_type === 'RENAMED') { | 
|  | leftPath = diff.meta_a.name; | 
|  | } | 
|  | links.push( | 
|  | { | 
|  | url: this._computeDownloadFileLink( | 
|  | project, changeNum, patchRange, leftPath, true), | 
|  | name: 'Left Content', | 
|  | } | 
|  | ); | 
|  | } | 
|  |  | 
|  | if (diff && diff.meta_b) { | 
|  | links.push( | 
|  | { | 
|  | url: this._computeDownloadFileLink( | 
|  | project, changeNum, patchRange, path, false), | 
|  | name: 'Right Content', | 
|  | } | 
|  | ); | 
|  | } | 
|  |  | 
|  | return links; | 
|  | } | 
|  |  | 
|  | _computeDownloadFileLink( | 
|  | project, changeNum, patchRange, path, isBase) { | 
|  | let patchNum = patchRange.patchNum; | 
|  |  | 
|  | const comparedAgainsParent = patchRange.basePatchNum === 'PARENT'; | 
|  |  | 
|  | if (isBase && !comparedAgainsParent) { | 
|  | patchNum = patchRange.basePatchNum; | 
|  | } | 
|  |  | 
|  | let url = this.changeBaseURL(project, changeNum, patchNum) + | 
|  | `/files/${encodeURIComponent(path)}/download`; | 
|  |  | 
|  | if (isBase && comparedAgainsParent) { | 
|  | url += '?parent=1'; | 
|  | } | 
|  |  | 
|  | return url; | 
|  | } | 
|  |  | 
|  | _computeDownloadPatchLink(project, changeNum, patchRange, path) { | 
|  | let url = this.changeBaseURL(project, changeNum, patchRange.patchNum); | 
|  | url += '/patch?zip&path=' + encodeURIComponent(path); | 
|  | return url; | 
|  | } | 
|  |  | 
|  | _loadComments() { | 
|  | return this.$.commentAPI.loadAll(this._changeNum).then(comments => { | 
|  | this._changeComments = comments; | 
|  | this._commentMap = this._getPaths(this._patchRange); | 
|  |  | 
|  | this._commentsForDiff = this._getCommentsForPath(this._path, | 
|  | this._patchRange, this._projectConfig); | 
|  | }); | 
|  | } | 
|  |  | 
|  | _getPaths(patchRange) { | 
|  | return this._changeComments.getPaths(patchRange); | 
|  | } | 
|  |  | 
|  | _getCommentsForPath(path, patchRange, projectConfig) { | 
|  | return this._changeComments.getCommentsBySideForPath(path, patchRange, | 
|  | projectConfig); | 
|  | } | 
|  |  | 
|  | _getDiffDrafts() { | 
|  | return this.$.restAPI.getDiffDrafts(this._changeNum); | 
|  | } | 
|  |  | 
|  | _computeCommentSkips(commentMap, fileList, path) { | 
|  | // Polymer 2: check for undefined | 
|  | if ([ | 
|  | commentMap, | 
|  | fileList, | 
|  | path, | 
|  | ].some(arg => arg === undefined)) { | 
|  | return undefined; | 
|  | } | 
|  |  | 
|  | const skips = {previous: null, next: null}; | 
|  | if (!fileList.length) { return skips; } | 
|  | const pathIndex = fileList.indexOf(path); | 
|  |  | 
|  | // Scan backward for the previous file. | 
|  | for (let i = pathIndex - 1; i >= 0; i--) { | 
|  | if (commentMap[fileList[i]]) { | 
|  | skips.previous = fileList[i]; | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Scan forward for the next file. | 
|  | for (let i = pathIndex + 1; i < fileList.length; i++) { | 
|  | if (commentMap[fileList[i]]) { | 
|  | skips.next = fileList[i]; | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | return skips; | 
|  | } | 
|  |  | 
|  | _computeDiffClass(panelFloatingDisabled) { | 
|  | if (panelFloatingDisabled) { | 
|  | return 'noOverflow'; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!Object} patchRangeRecord | 
|  | */ | 
|  | _computeEditMode(patchRangeRecord) { | 
|  | const patchRange = patchRangeRecord.base || {}; | 
|  | return this.patchNumEquals(patchRange.patchNum, this.EDIT_NAME); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {boolean} editMode | 
|  | */ | 
|  | _computeContainerClass(editMode) { | 
|  | return editMode ? 'editMode' : ''; | 
|  | } | 
|  |  | 
|  | _computeBlameToggleLabel(loaded, loading) { | 
|  | if (loaded) { return 'Hide blame'; } | 
|  | return 'Show blame'; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Load and display blame information if it has not already been loaded. | 
|  | * Otherwise hide it. | 
|  | */ | 
|  | _toggleBlame(e) { | 
|  | if (this.shouldSuppressKeyboardShortcut(e) || | 
|  | this.modifierPressed(e)) { return; } | 
|  |  | 
|  | if (this._isBlameLoaded) { | 
|  | this.$.diffHost.clearBlame(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | this._isBlameLoading = true; | 
|  | this.fire('show-alert', {message: MSG_LOADING_BLAME}); | 
|  | this.$.diffHost.loadBlame() | 
|  | .then(() => { | 
|  | this._isBlameLoading = false; | 
|  | this.fire('show-alert', {message: MSG_LOADED_BLAME}); | 
|  | }) | 
|  | .catch(() => { | 
|  | this._isBlameLoading = false; | 
|  | }); | 
|  | } | 
|  |  | 
|  | _computeBlameLoaderClass(isImageDiff, path) { | 
|  | return !this.isMagicPath(path) && !isImageDiff ? 'show' : ''; | 
|  | } | 
|  |  | 
|  | _getRevisionInfo(change) { | 
|  | return new Gerrit.RevisionInfo(change); | 
|  | } | 
|  |  | 
|  | _computeFileNum(file, files) { | 
|  | // Polymer 2: check for undefined | 
|  | if ([file, files].some(arg => arg === undefined)) { | 
|  | return undefined; | 
|  | } | 
|  |  | 
|  | return files.findIndex(({value}) => value === file) + 1; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {number} fileNum | 
|  | * @param {!Array<string>} files | 
|  | * @return {string} | 
|  | */ | 
|  | _computeFileNumClass(fileNum, files) { | 
|  | if (files && fileNum > 0) { | 
|  | return 'show'; | 
|  | } | 
|  | return ''; | 
|  | } | 
|  |  | 
|  | _handleExpandAllDiffContext(e) { | 
|  | if (this.shouldSuppressKeyboardShortcut(e)) { return; } | 
|  | this.$.diffHost.expandAllContext(); | 
|  | } | 
|  |  | 
|  | _computeDiffPrefsDisabled(disableDiffPrefs, loggedIn) { | 
|  | return disableDiffPrefs || !loggedIn; | 
|  | } | 
|  |  | 
|  | _handleNextUnreviewedFile(e) { | 
|  | if (this.shouldSuppressKeyboardShortcut(e)) { return; } | 
|  | this._setReviewed(true); | 
|  | // Ensure that the currently viewed file always appears in unreviewedFiles | 
|  | // so we resolve the right "next" file. | 
|  | const unreviewedFiles = this._fileList | 
|  | .filter(file => | 
|  | (file === this._path || !this._reviewedFiles.has(file))); | 
|  | this._navToFile(this._path, unreviewedFiles, 1); | 
|  | } | 
|  |  | 
|  | _handleReloadingDiffPreference() { | 
|  | this._getDiffPreferences(); | 
|  | } | 
|  |  | 
|  | _onChangeHeaderPanelHeightChanged(e) { | 
|  | this._scrollTopMargin = e.detail.value; | 
|  | } | 
|  | } | 
|  |  | 
|  | customElements.define(GrDiffView.is, GrDiffView); | 
|  | })(); |