| /** | |
| * @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_COMMENT_ON_EDIT = 'You cannot comment on an edit.'; | |
| const ERR_COMMENT_ON_EDIT_BASE = 'You cannot comment on the base patch set ' + | |
| 'of an edit.'; | |
| const ERR_INVALID_LINE = 'Invalid line number: '; | |
| const MSG_EMPTY_BLAME = 'No blame information for this diff.'; | |
| const EVENT_AGAINST_PARENT = 'diff-against-parent'; | |
| const EVENT_ZERO_REBASE = 'rebase-percent-zero'; | |
| const EVENT_NONZERO_REBASE = 'rebase-percent-nonzero'; | |
| const DiffViewMode = { | |
| SIDE_BY_SIDE: 'SIDE_BY_SIDE', | |
| UNIFIED: 'UNIFIED_DIFF', | |
| }; | |
| const DiffSide = { | |
| LEFT: 'left', | |
| RIGHT: 'right', | |
| }; | |
| const LARGE_DIFF_THRESHOLD_LINES = 10000; | |
| const FULL_CONTEXT = -1; | |
| const LIMITED_CONTEXT = 10; | |
| Polymer({ | |
| is: 'gr-diff', | |
| /** | |
| * Fired when the user selects a line. | |
| * @event line-selected | |
| */ | |
| /** | |
| * Fired if being logged in is required. | |
| * | |
| * @event show-auth-required | |
| */ | |
| /** | |
| * Fired when a comment is saved or discarded | |
| * | |
| * @event diff-comments-modified | |
| */ | |
| properties: { | |
| changeNum: String, | |
| noAutoRender: { | |
| type: Boolean, | |
| value: false, | |
| }, | |
| /** @type {?} */ | |
| patchRange: Object, | |
| path: String, | |
| prefs: { | |
| type: Object, | |
| observer: '_prefsObserver', | |
| }, | |
| projectConfig: { | |
| type: Object, | |
| observer: '_projectConfigChanged', | |
| }, | |
| projectName: String, | |
| displayLine: { | |
| type: Boolean, | |
| value: false, | |
| }, | |
| isImageDiff: { | |
| type: Boolean, | |
| computed: '_computeIsImageDiff(_diff)', | |
| notify: true, | |
| }, | |
| commitRange: Object, | |
| filesWeblinks: { | |
| type: Object, | |
| value() { return {}; }, | |
| notify: true, | |
| }, | |
| hidden: { | |
| type: Boolean, | |
| reflectToAttribute: true, | |
| }, | |
| noRenderOnPrefsChange: Boolean, | |
| comments: Object, | |
| lineWrapping: { | |
| type: Boolean, | |
| value: false, | |
| observer: '_lineWrappingObserver', | |
| }, | |
| viewMode: { | |
| type: String, | |
| value: DiffViewMode.SIDE_BY_SIDE, | |
| observer: '_viewModeObserver', | |
| }, | |
| /** | |
| * Special line number which should not be collapsed into a shared region. | |
| * @type {{ | |
| * number: number, | |
| * leftSide: {boolean} | |
| * }|null} | |
| */ | |
| lineOfInterest: Object, | |
| _loggedIn: { | |
| type: Boolean, | |
| value: false, | |
| }, | |
| _diff: Object, | |
| _diffHeaderItems: { | |
| type: Array, | |
| value: [], | |
| computed: '_computeDiffHeaderItems(_diff.*)', | |
| }, | |
| _diffTableClass: { | |
| type: String, | |
| value: '', | |
| }, | |
| /** @type {?Object} */ | |
| _baseImage: Object, | |
| /** @type {?Object} */ | |
| _revisionImage: Object, | |
| /** | |
| * Whether the safety check for large diffs when whole-file is set has | |
| * been bypassed. If the value is null, then the safety has not been | |
| * bypassed. If the value is a number, then that number represents the | |
| * context preference to use when rendering the bypassed diff. | |
| * | |
| * @type (number|null) | |
| */ | |
| _safetyBypass: { | |
| type: Number, | |
| value: null, | |
| }, | |
| _showWarning: Boolean, | |
| /** @type {?Object} */ | |
| _blame: { | |
| type: Object, | |
| value: null, | |
| }, | |
| isBlameLoaded: { | |
| type: Boolean, | |
| notify: true, | |
| computed: '_computeIsBlameLoaded(_blame)', | |
| }, | |
| _parentIndex: { | |
| type: Number, | |
| computed: '_computeParentIndex(patchRange.*)', | |
| }, | |
| }, | |
| behaviors: [ | |
| Gerrit.PatchSetBehavior, | |
| ], | |
| listeners: { | |
| 'comment-discard': '_handleCommentDiscard', | |
| 'comment-update': '_handleCommentUpdate', | |
| 'comment-save': '_handleCommentSave', | |
| 'create-comment': '_handleCreateComment', | |
| }, | |
| attached() { | |
| this._getLoggedIn().then(loggedIn => { | |
| this._loggedIn = loggedIn; | |
| }); | |
| }, | |
| ready() { | |
| if (this._canRender()) { | |
| this.reload(); | |
| } | |
| }, | |
| /** @return {!Promise} */ | |
| reload() { | |
| this.cancel(); | |
| this.clearBlame(); | |
| this._safetyBypass = null; | |
| this._showWarning = false; | |
| this.clearDiffContent(); | |
| const promises = []; | |
| promises.push(this._getDiff().then(diff => { | |
| this._diff = diff; | |
| return this._loadDiffAssets(); | |
| })); | |
| return Promise.all(promises).then(() => { | |
| if (this.prefs) { | |
| return this._renderDiffTable(); | |
| } | |
| return Promise.resolve(); | |
| }); | |
| }, | |
| /** Cancel any remaining diff builder rendering work. */ | |
| cancel() { | |
| this.$.diffBuilder.cancel(); | |
| }, | |
| /** @return {!Array<!HTMLElement>} */ | |
| getCursorStops() { | |
| if (this.hidden && this.noAutoRender) { | |
| return []; | |
| } | |
| return Polymer.dom(this.root).querySelectorAll('.diff-row'); | |
| }, | |
| /** @return {boolean} */ | |
| isRangeSelected() { | |
| return this.$.highlights.isRangeSelected(); | |
| }, | |
| toggleLeftDiff() { | |
| this.toggleClass('no-left'); | |
| }, | |
| /** | |
| * Load and display blame information for the base of the diff. | |
| * @return {Promise} A promise that resolves when blame finishes rendering. | |
| */ | |
| loadBlame() { | |
| return this.$.restAPI.getBlame(this.changeNum, this.patchRange.patchNum, | |
| this.path, true) | |
| .then(blame => { | |
| if (!blame.length) { | |
| this.fire('show-alert', {message: MSG_EMPTY_BLAME}); | |
| return Promise.reject(MSG_EMPTY_BLAME); | |
| } | |
| this._blame = blame; | |
| this.$.diffBuilder.setBlame(blame); | |
| this.classList.add('showBlame'); | |
| }); | |
| }, | |
| _computeIsBlameLoaded(blame) { | |
| return !!blame; | |
| }, | |
| /** | |
| * Unload blame information for the diff. | |
| */ | |
| clearBlame() { | |
| this._blame = null; | |
| this.$.diffBuilder.setBlame(null); | |
| this.classList.remove('showBlame'); | |
| }, | |
| _handleCommentSaveOrDiscard() { | |
| this.dispatchEvent(new CustomEvent('diff-comments-modified', | |
| {bubbles: true})); | |
| }, | |
| /** @return {boolean}} */ | |
| _canRender() { | |
| return !!this.changeNum && !!this.patchRange && !!this.path && | |
| !this.noAutoRender; | |
| }, | |
| /** @return {!Array<!HTMLElement>} */ | |
| getThreadEls() { | |
| let threads = []; | |
| const threadGroupEls = Polymer.dom(this.root) | |
| .querySelectorAll('gr-diff-comment-thread-group'); | |
| for (const threadGroupEl of threadGroupEls) { | |
| threads = threads.concat(threadGroupEl.threadEls); | |
| } | |
| return threads; | |
| }, | |
| /** @return {string} */ | |
| _computeContainerClass(loggedIn, viewMode, displayLine) { | |
| const classes = ['diffContainer']; | |
| switch (viewMode) { | |
| case DiffViewMode.UNIFIED: | |
| classes.push('unified'); | |
| break; | |
| case DiffViewMode.SIDE_BY_SIDE: | |
| classes.push('sideBySide'); | |
| break; | |
| default: | |
| throw Error('Invalid view mode: ', viewMode); | |
| } | |
| if (Gerrit.hiddenscroll) { | |
| classes.push('hiddenscroll'); | |
| } | |
| if (loggedIn) { | |
| classes.push('canComment'); | |
| } | |
| if (displayLine) { | |
| classes.push('displayLine'); | |
| } | |
| return classes.join(' '); | |
| }, | |
| _handleTap(e) { | |
| const el = Polymer.dom(e).localTarget; | |
| if (el.classList.contains('showContext')) { | |
| this.$.diffBuilder.showContext(e.detail.groups, e.detail.section); | |
| } else if (el.classList.contains('lineNum')) { | |
| this.addDraftAtLine(el); | |
| } else if (el.tagName === 'HL' || | |
| el.classList.contains('content') || | |
| el.classList.contains('contentText')) { | |
| const target = this.$.diffBuilder.getLineElByChild(el); | |
| if (target) { this._selectLine(target); } | |
| } | |
| }, | |
| _selectLine(el) { | |
| this.fire('line-selected', { | |
| side: el.classList.contains('left') ? DiffSide.LEFT : DiffSide.RIGHT, | |
| number: el.getAttribute('data-value'), | |
| path: this.path, | |
| }); | |
| }, | |
| addDraftAtLine(el) { | |
| this._selectLine(el); | |
| this._isValidElForComment(el).then(valid => { | |
| if (!valid) { return; } | |
| const value = el.getAttribute('data-value'); | |
| let lineNum; | |
| if (value !== GrDiffLine.FILE) { | |
| lineNum = parseInt(value, 10); | |
| if (isNaN(lineNum)) { | |
| this.fire('show-alert', {message: ERR_INVALID_LINE + value}); | |
| return; | |
| } | |
| } | |
| this._createComment(el, lineNum); | |
| }); | |
| }, | |
| _handleCreateComment(e) { | |
| const range = e.detail.range; | |
| const side = e.detail.side; | |
| const lineNum = range.endLine; | |
| const lineEl = this.$.diffBuilder.getLineElByNumber(lineNum, side); | |
| this._isValidElForComment(lineEl).then(valid => { | |
| if (!valid) { return; } | |
| this._createComment(lineEl, lineNum, side, range); | |
| }); | |
| }, | |
| _isValidElForComment(el) { | |
| return this._getLoggedIn().then(loggedIn => { | |
| if (!loggedIn) { | |
| this.fire('show-auth-required'); | |
| return false; | |
| } | |
| const patchNum = el.classList.contains(DiffSide.LEFT) ? | |
| this.patchRange.basePatchNum : | |
| this.patchRange.patchNum; | |
| const isEdit = this.patchNumEquals(patchNum, this.EDIT_NAME); | |
| const isEditBase = this.patchNumEquals(patchNum, this.PARENT_NAME) && | |
| this.patchNumEquals(this.patchRange.patchNum, this.EDIT_NAME); | |
| if (isEdit) { | |
| this.fire('show-alert', {message: ERR_COMMENT_ON_EDIT}); | |
| return false; | |
| } else if (isEditBase) { | |
| this.fire('show-alert', {message: ERR_COMMENT_ON_EDIT_BASE}); | |
| return false; | |
| } | |
| return true; | |
| }); | |
| }, | |
| /** | |
| * @param {!Object} lineEl | |
| * @param {number=} opt_lineNum | |
| * @param {string=} opt_side | |
| * @param {!Object=} opt_range | |
| */ | |
| _createComment(lineEl, opt_lineNum, opt_side, opt_range) { | |
| this.$.reporting.recordDraftInteraction(); | |
| const contentText = this.$.diffBuilder.getContentByLineEl(lineEl); | |
| const contentEl = contentText.parentElement; | |
| const side = opt_side || | |
| this._getCommentSideByLineAndContent(lineEl, contentEl); | |
| const patchNum = this._getPatchNumByLineAndContent(lineEl, contentEl); | |
| const isOnParent = | |
| this._getIsParentCommentByLineAndContent(lineEl, contentEl); | |
| const threadEl = this._getOrCreateThread(contentEl, patchNum, | |
| side, isOnParent, opt_range); | |
| threadEl.addOrEditDraft(opt_lineNum, opt_range); | |
| }, | |
| /** | |
| * Fetch the thread group at the given range, or the range-less thread | |
| * on the line if no range is provided. | |
| * | |
| * @param {!Object} threadGroupEl | |
| * @param {string} commentSide | |
| * @param {!Object=} opt_range | |
| * @return {!Object} | |
| */ | |
| _getThread(threadGroupEl, commentSide, opt_range) { | |
| return threadGroupEl.getThread(commentSide, opt_range); | |
| }, | |
| _getThreadGroupForLine(contentEl) { | |
| return contentEl.querySelector('gr-diff-comment-thread-group'); | |
| }, | |
| /** | |
| * @param {string} commentSide | |
| * @param {!Object=} opt_range | |
| */ | |
| _getRangeString(commentSide, opt_range) { | |
| return opt_range ? | |
| 'range-' + | |
| opt_range.startLine + '-' + | |
| opt_range.startChar + '-' + | |
| opt_range.endLine + '-' + | |
| opt_range.endChar + '-' + | |
| commentSide : 'line-' + commentSide; | |
| }, | |
| /** | |
| * Gets or creates a comment thread for a specific spot on a diff. | |
| * May include a range, if the comment is a range comment. | |
| * | |
| * @param {!Object} contentEl | |
| * @param {number} patchNum | |
| * @param {string} commentSide | |
| * @param {boolean} isOnParent | |
| * @param {!Object=} opt_range | |
| * @return {!Object} | |
| */ | |
| _getOrCreateThread(contentEl, patchNum, commentSide, | |
| isOnParent, opt_range) { | |
| // Check if thread group exists. | |
| let threadGroupEl = this._getThreadGroupForLine(contentEl); | |
| if (!threadGroupEl) { | |
| threadGroupEl = this.$.diffBuilder.createCommentThreadGroup( | |
| this.changeNum, patchNum, this.path, isOnParent, commentSide); | |
| contentEl.appendChild(threadGroupEl); | |
| } | |
| let threadEl = this._getThread(threadGroupEl, commentSide, opt_range); | |
| if (!threadEl) { | |
| threadGroupEl.addNewThread(commentSide, opt_range); | |
| Polymer.dom.flush(); | |
| threadEl = this._getThread(threadGroupEl, commentSide, opt_range); | |
| } | |
| return threadEl; | |
| }, | |
| /** | |
| * The value to be used for the patch number of new comments created at the | |
| * given line and content elements. | |
| * | |
| * In two cases of creating a comment on the left side, the patch number to | |
| * be used should actually be right side of the patch range: | |
| * - When the patch range is against the parent comment of a normal change. | |
| * Such comments declare themmselves to be on the left using side=PARENT. | |
| * - If the patch range is against the indexed parent of a merge change. | |
| * Such comments declare themselves to be on the given parent by | |
| * specifying the parent index via parent=i. | |
| * | |
| * @return {number} | |
| */ | |
| _getPatchNumByLineAndContent(lineEl, contentEl) { | |
| let patchNum = this.patchRange.patchNum; | |
| if ((lineEl.classList.contains(DiffSide.LEFT) || | |
| contentEl.classList.contains('remove')) && | |
| this.patchRange.basePatchNum !== 'PARENT' && | |
| !this.isMergeParent(this.patchRange.basePatchNum)) { | |
| patchNum = this.patchRange.basePatchNum; | |
| } | |
| return patchNum; | |
| }, | |
| /** @return {boolean} */ | |
| _getIsParentCommentByLineAndContent(lineEl, contentEl) { | |
| if ((lineEl.classList.contains(DiffSide.LEFT) || | |
| contentEl.classList.contains('remove')) && | |
| (this.patchRange.basePatchNum === 'PARENT' || | |
| this.isMergeParent(this.patchRange.basePatchNum))) { | |
| return true; | |
| } | |
| return false; | |
| }, | |
| /** @return {string} */ | |
| _getCommentSideByLineAndContent(lineEl, contentEl) { | |
| let side = 'right'; | |
| if (lineEl.classList.contains(DiffSide.LEFT) || | |
| contentEl.classList.contains('remove')) { | |
| side = 'left'; | |
| } | |
| return side; | |
| }, | |
| _handleCommentDiscard(e) { | |
| const comment = e.detail.comment; | |
| this._removeComment(comment); | |
| this._handleCommentSaveOrDiscard(); | |
| }, | |
| _removeComment(comment) { | |
| const side = comment.__commentSide; | |
| this._removeCommentFromSide(comment, side); | |
| }, | |
| _handleCommentSave(e) { | |
| const comment = e.detail.comment; | |
| const side = e.detail.comment.__commentSide; | |
| const idx = this._findDraftIndex(comment, side); | |
| this.set(['comments', side, idx], comment); | |
| this._handleCommentSaveOrDiscard(); | |
| }, | |
| /** | |
| * Closure annotation for Polymer.prototype.push is off. Submitted PR: | |
| * https://github.com/Polymer/polymer/pull/4776 | |
| * but for not supressing annotations. | |
| * | |
| * @suppress {checkTypes} */ | |
| _handleCommentUpdate(e) { | |
| const comment = e.detail.comment; | |
| const side = e.detail.comment.__commentSide; | |
| let idx = this._findCommentIndex(comment, side); | |
| if (idx === -1) { | |
| idx = this._findDraftIndex(comment, side); | |
| } | |
| if (idx !== -1) { // Update draft or comment. | |
| this.set(['comments', side, idx], comment); | |
| } else { // Create new draft. | |
| this.push(['comments', side], comment); | |
| } | |
| }, | |
| _removeCommentFromSide(comment, side) { | |
| let idx = this._findCommentIndex(comment, side); | |
| if (idx === -1) { | |
| idx = this._findDraftIndex(comment, side); | |
| } | |
| if (idx !== -1) { | |
| this.splice('comments.' + side, idx, 1); | |
| } | |
| }, | |
| /** @return {number} */ | |
| _findCommentIndex(comment, side) { | |
| if (!comment.id || !this.comments[side]) { | |
| return -1; | |
| } | |
| return this.comments[side].findIndex(item => { | |
| return item.id === comment.id; | |
| }); | |
| }, | |
| /** @return {number} */ | |
| _findDraftIndex(comment, side) { | |
| if (!comment.__draftID || !this.comments[side]) { | |
| return -1; | |
| } | |
| return this.comments[side].findIndex(item => { | |
| return item.__draftID === comment.__draftID; | |
| }); | |
| }, | |
| _prefsObserver(newPrefs, oldPrefs) { | |
| // Scan the preference objects one level deep to see if they differ. | |
| let differ = !oldPrefs; | |
| if (newPrefs && oldPrefs) { | |
| for (const key in newPrefs) { | |
| if (newPrefs[key] !== oldPrefs[key]) { | |
| differ = true; | |
| } | |
| } | |
| } | |
| if (differ) { | |
| this._prefsChanged(newPrefs); | |
| } | |
| }, | |
| _viewModeObserver() { | |
| this._prefsChanged(this.prefs); | |
| }, | |
| _lineWrappingObserver() { | |
| this._prefsChanged(this.prefs); | |
| }, | |
| _prefsChanged(prefs) { | |
| if (!prefs) { return; } | |
| this.clearBlame(); | |
| const stylesToUpdate = {}; | |
| if (prefs.line_wrapping) { | |
| this._diffTableClass = 'full-width'; | |
| if (this.viewMode === 'SIDE_BY_SIDE') { | |
| stylesToUpdate['--content-width'] = 'none'; | |
| stylesToUpdate['--line-limit'] = prefs.line_length + 'ch'; | |
| } | |
| } else { | |
| this._diffTableClass = ''; | |
| stylesToUpdate['--content-width'] = prefs.line_length + 'ch'; | |
| } | |
| if (prefs.font_size) { | |
| stylesToUpdate['--font-size'] = prefs.font_size + 'px'; | |
| } | |
| this.updateStyles(stylesToUpdate); | |
| if (this._diff && this.comments && !this.noRenderOnPrefsChange) { | |
| this._renderDiffTable(); | |
| } | |
| }, | |
| _renderDiffTable() { | |
| if (this.prefs.context === -1 && | |
| this._diffLength(this._diff) >= LARGE_DIFF_THRESHOLD_LINES && | |
| this._safetyBypass === null) { | |
| this._showWarning = true; | |
| return Promise.resolve(); | |
| } | |
| this._showWarning = false; | |
| return this.$.diffBuilder.render(this.comments, this._getBypassPrefs()); | |
| }, | |
| /** | |
| * Get the preferences object including the safety bypass context (if any). | |
| */ | |
| _getBypassPrefs() { | |
| if (this._safetyBypass !== null) { | |
| return Object.assign({}, this.prefs, {context: this._safetyBypass}); | |
| } | |
| return this.prefs; | |
| }, | |
| clearDiffContent() { | |
| this.$.diffTable.innerHTML = null; | |
| }, | |
| _handleGetDiffError(response) { | |
| // Loading the diff may respond with 409 if the file is too large. In this | |
| // case, use a toast error.. | |
| if (response.status === 409) { | |
| this.fire('server-error', {response}); | |
| return; | |
| } | |
| this.fire('page-error', {response}); | |
| }, | |
| /** @return {!Promise<!Object>} */ | |
| _getDiff() { | |
| return this.$.restAPI.getDiff( | |
| this.changeNum, | |
| this.patchRange.basePatchNum, | |
| this.patchRange.patchNum, | |
| this.path, | |
| this._handleGetDiffError.bind(this)).then(diff => { | |
| this._reportDiff(diff); | |
| if (!this.commitRange) { | |
| this.filesWeblinks = {}; | |
| return diff; | |
| } | |
| this.filesWeblinks = { | |
| meta_a: Gerrit.Nav.getFileWebLinks( | |
| this.projectName, this.commitRange.baseCommit, this.path, | |
| {weblinks: diff && diff.meta_a && diff.meta_a.web_links}), | |
| meta_b: Gerrit.Nav.getFileWebLinks( | |
| this.projectName, this.commitRange.commit, this.path, | |
| {weblinks: diff && diff.meta_b && diff.meta_b.web_links}), | |
| }; | |
| return diff; | |
| }); | |
| }, | |
| /** | |
| * Report info about the diff response. | |
| */ | |
| _reportDiff(diff) { | |
| if (!diff || !diff.content) { return; } | |
| // Count the delta lines stemming from normal deltas, and from | |
| // due_to_rebase deltas. | |
| let nonRebaseDelta = 0; | |
| let rebaseDelta = 0; | |
| diff.content.forEach(chunk => { | |
| if (chunk.ab) { return; } | |
| const deltaSize = Math.max( | |
| chunk.a ? chunk.a.length : 0, chunk.b ? chunk.b.length : 0); | |
| if (chunk.due_to_rebase) { | |
| rebaseDelta += deltaSize; | |
| } else { | |
| nonRebaseDelta += deltaSize; | |
| } | |
| }); | |
| // Find the percent of the delta from due_to_rebase chunks rounded to two | |
| // digits. Diffs with no delta are considered 0%. | |
| const totalDelta = rebaseDelta + nonRebaseDelta; | |
| const percentRebaseDelta = !totalDelta ? 0 : | |
| Math.round(100 * rebaseDelta / totalDelta); | |
| // Report the due_to_rebase percentage in the "diff" category when | |
| // applicable. | |
| if (this.patchRange.basePatchNum === 'PARENT') { | |
| this.$.reporting.reportInteraction(EVENT_AGAINST_PARENT); | |
| } else if (percentRebaseDelta === 0) { | |
| this.$.reporting.reportInteraction(EVENT_ZERO_REBASE); | |
| } else { | |
| this.$.reporting.reportInteraction(EVENT_NONZERO_REBASE, | |
| percentRebaseDelta); | |
| } | |
| }, | |
| /** @return {!Promise} */ | |
| _getLoggedIn() { | |
| return this.$.restAPI.getLoggedIn(); | |
| }, | |
| /** @return {boolean} */ | |
| _computeIsImageDiff() { | |
| if (!this._diff) { return false; } | |
| const isA = this._diff.meta_a && | |
| this._diff.meta_a.content_type.startsWith('image/'); | |
| const isB = this._diff.meta_b && | |
| this._diff.meta_b.content_type.startsWith('image/'); | |
| return !!(this._diff.binary && (isA || isB)); | |
| }, | |
| /** @return {!Promise} */ | |
| _loadDiffAssets() { | |
| if (this.isImageDiff) { | |
| return this._getImages().then(images => { | |
| this._baseImage = images.baseImage; | |
| this._revisionImage = images.revisionImage; | |
| }); | |
| } else { | |
| this._baseImage = null; | |
| this._revisionImage = null; | |
| return Promise.resolve(); | |
| } | |
| }, | |
| /** @return {!Promise} */ | |
| _getImages() { | |
| return this.$.restAPI.getImagesForDiff(this.changeNum, this._diff, | |
| this.patchRange); | |
| }, | |
| _projectConfigChanged(projectConfig) { | |
| const threadEls = this.getThreadEls(); | |
| for (let i = 0; i < threadEls.length; i++) { | |
| threadEls[i].projectConfig = projectConfig; | |
| } | |
| }, | |
| /** @return {!Array} */ | |
| _computeDiffHeaderItems(diffInfoRecord) { | |
| const diffInfo = diffInfoRecord.base; | |
| if (!diffInfo || !diffInfo.diff_header || diffInfo.binary) { return []; } | |
| return diffInfo.diff_header.filter(item => { | |
| return !(item.startsWith('diff --git ') || | |
| item.startsWith('index ') || | |
| item.startsWith('+++ ') || | |
| item.startsWith('--- ')); | |
| }); | |
| }, | |
| /** @return {boolean} */ | |
| _computeDiffHeaderHidden(items) { | |
| return items.length === 0; | |
| }, | |
| /** | |
| * The number of lines in the diff. For delta chunks that are different | |
| * sizes on the left and the right, the longer side is used. | |
| * @param {!Object} diff | |
| * @return {number} | |
| */ | |
| _diffLength(diff) { | |
| return diff.content.reduce((sum, sec) => { | |
| if (sec.hasOwnProperty('ab')) { | |
| return sum + sec.ab.length; | |
| } else { | |
| return sum + Math.max( | |
| sec.hasOwnProperty('a') ? sec.a.length : 0, | |
| sec.hasOwnProperty('b') ? sec.b.length : 0 | |
| ); | |
| } | |
| }, 0); | |
| }, | |
| _handleFullBypass() { | |
| this._safetyBypass = FULL_CONTEXT; | |
| this._renderDiffTable(); | |
| }, | |
| _handleLimitedBypass() { | |
| this._safetyBypass = LIMITED_CONTEXT; | |
| this._renderDiffTable(); | |
| }, | |
| /** @return {string} */ | |
| _computeWarningClass(showWarning) { | |
| return showWarning ? 'warn' : ''; | |
| }, | |
| /** | |
| * @return {number|null} | |
| */ | |
| _computeParentIndex(patchRangeRecord) { | |
| if (!this.isMergeParent(patchRangeRecord.base.basePatchNum)) { | |
| return null; | |
| } | |
| return this.getParentIndex(patchRangeRecord.base.basePatchNum); | |
| }, | |
| expandAllContext() { | |
| this._handleFullBypass(); | |
| }, | |
| }); | |
| })(); |