/** * @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', }; Polymer({ is: '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 */ properties: { /** * 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', }, /** @type {?} */ _patchRange: Object, /** @type {?} */ _commitRange: Object, /** * @type {{ * subject: string, * project: string, * revisions: string, * }} */ _change: Object, /** @type {?} */ _changeComments: Object, _changeNum: String, _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(_fileList, _patchRange.patchNum, ' + '_changeComments)', }, // An sorted array of files, as returned by the rest API. _fileList: { type: Array, value() { return []; }, }, _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: () => { return 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(), }, }, behaviors: [ Gerrit.KeyboardShortcutBehavior, Gerrit.PatchSetBehavior, Gerrit.PathListBehavior, Gerrit.RESTClientBehavior, ], observers: [ '_getProjectConfig(_change.project)', '_getFiles(_changeNum, _patchRange.*)', '_setReviewedObserver(_loggedIn, params.*, _prefs)', ], keyBindings: { 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.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', // Final two are actually handled by gr-diff-comment-thread. [this.Shortcut.EXPAND_ALL_COMMENT_THREADS]: null, [this.Shortcut.COLLAPSE_ALL_COMMENT_THREADS]: null, }; }, attached() { this._getLoggedIn().then(loggedIn => { this._loggedIn = loggedIn; }); 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); }, _getFiles(changeNum, patchRangeRecord) { const patchRange = patchRangeRecord.base; return this.$.restAPI.getChangeFilePathsAsSpeciallySortedArray( changeNum, patchRange).then(files => { this._fileList = files; }); }, _getDiffPreferences() { return this.$.restAPI.getDiffPreferences(); }, _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(); }, _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)) { return; } if (this.$.diffHost.isRangeSelected()) { return; } if (this.modifierPressed(e)) { return; } e.preventDefault(); const line = this.$.cursor.getTargetLineElement(); if (line) { this.$.diffHost.addDraftAtLine(line); } }, _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; } e.preventDefault(); this.$.diffPreferences.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} 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: ""} - 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} 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.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._patchRange = { patchNum: value.patchNum, basePatchNum: value.basePatchNum || PARENT, }; this._path = value.path; // 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 = []; this._localPrefs = this.$.storage.getPreferences(); promises.push(this._getDiffPreferences().then(prefs => { this._prefs = prefs; })); promises.push(this._getPreferences().then(prefs => { this._userPrefs = prefs; })); promises.push(this._getChangeDetail(this._changeNum).then(change => { let commit; let baseCommit; 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(); }).then(() => { 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) { 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) { 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) { 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(fileList, patchNum, changeComments) { if (!fileList) { return; } const dropdownContent = []; for (const path of fileList) { dropdownContent.push({ text: this.computeDisplayPath(path), mobileText: this.computeTruncatedPath(path), value: path, bottomText: this._computeCommentString(changeComments, patchNum, path), }); } return dropdownContent; }, _computeCommentString(changeComments, patchNum, path) { const unresolvedCount = changeComments.computeUnresolvedNum(patchNum, path); const commentCount = changeComments.computeCommentCount(patchNum, path); const commentString = GrCountStringFormatter.computePluralString( commentCount, 'comment'); const unresolvedString = GrCountStringFormatter.computeString( unresolvedCount, 'unresolved'); return commentString + // Add a space if both comments and unresolved (commentString && unresolvedString ? ', ' : '') + // Add parentheses around unresolved if it exists. (unresolvedString ? `${unresolvedString}` : ''); }, _computePrefsButtonHidden(prefs, loggedIn) { return !loggedIn || !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.$.diffPreferences.open(); }, _handlePrefsSave(e) { e.stopPropagation(); const el = Polymer.dom(e).rootTarget; el.disabled = true; this.$.storage.savePreferences(this._localPrefs); this._saveDiffPreferences().then(response => { el.disabled = false; if (!response.ok) { return response; } this.$.prefsOverlay.close(); }).catch(err => { el.disabled = false; }); }, /** * _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); }, _computeDownloadLink(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) { 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() { 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) { return !isImageDiff ? 'show' : ''; }, _getRevisionInfo(change) { return new Gerrit.RevisionInfo(change); }, _computeFileNum(file, files) { return files.findIndex(({value}) => value === file) + 1; }, /** * @param {number} fileNum * @param {!Array} files * @return {string} */ _computeFileNumClass(fileNum, files) { if (files && fileNum > 0) { return 'show'; } return ''; }, _handleExpandAllDiffContext(e) { if (this.shouldSuppressKeyboardShortcut(e)) { return; } this.$.diffHost.expandAllContext(); }, _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); }, }); })();