
The shownFiles array was referenced in _setReviewedFiles instead of the
files array, causing an undefined error due to accessing an array with
an out of bounds index.
This change fixes that, and removes the stub call that would have caught
this issue.
Bug: Issue 5853
Change-Id: I17175796d8d67ca4d53f6ce2f82a59ff2ebc4010
(cherry picked from commit 7011651093
)
827 lines
25 KiB
JavaScript
827 lines
25 KiB
JavaScript
// 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';
|
|
|
|
// Maximum length for patch set descriptions.
|
|
var PATCH_DESC_MAX_LENGTH = 500;
|
|
|
|
var COMMIT_MESSAGE_PATH = '/COMMIT_MSG';
|
|
|
|
var FileStatus = {
|
|
A: 'Added',
|
|
C: 'Copied',
|
|
D: 'Deleted',
|
|
R: 'Renamed',
|
|
W: 'Rewritten',
|
|
};
|
|
|
|
Polymer({
|
|
is: 'gr-file-list',
|
|
|
|
properties: {
|
|
patchRange: {
|
|
type: Object,
|
|
observer: '_updateSelected',
|
|
},
|
|
patchNum: String,
|
|
changeNum: String,
|
|
comments: Object,
|
|
drafts: Object,
|
|
revisions: Object,
|
|
projectConfig: Object,
|
|
selectedIndex: {
|
|
type: Number,
|
|
notify: true,
|
|
},
|
|
keyEventTarget: {
|
|
type: Object,
|
|
value: function() { return document.body; },
|
|
},
|
|
change: Object,
|
|
diffViewMode: {
|
|
type: String,
|
|
notify: true,
|
|
},
|
|
_files: {
|
|
type: Array,
|
|
observer: '_filesChanged',
|
|
value: function() { return []; },
|
|
},
|
|
_loggedIn: {
|
|
type: Boolean,
|
|
value: false,
|
|
},
|
|
_reviewed: {
|
|
type: Array,
|
|
value: function() { return []; },
|
|
},
|
|
_diffAgainst: String,
|
|
_diffPrefs: Object,
|
|
_userPrefs: Object,
|
|
_localPrefs: Object,
|
|
_showInlineDiffs: Boolean,
|
|
_numFilesShown: {
|
|
type: Number,
|
|
value: 75,
|
|
},
|
|
_patchChange: {
|
|
type: Object,
|
|
computed: '_calculatePatchChange(_files)',
|
|
},
|
|
_fileListIncrement: {
|
|
type: Number,
|
|
readOnly: true,
|
|
value: 75,
|
|
},
|
|
_hideChangeTotals: {
|
|
type: Boolean,
|
|
computed: '_shouldHideChangeTotals(_patchChange)',
|
|
},
|
|
_hideBinaryChangeTotals: {
|
|
type: Boolean,
|
|
computed: '_shouldHideBinaryChangeTotals(_patchChange)',
|
|
},
|
|
_shownFiles: {
|
|
type: Array,
|
|
computed: '_computeFilesShown(_numFilesShown, _files.*)',
|
|
},
|
|
// Caps the number of files that can be shown and have the 'show diffs' /
|
|
// 'hide diffs' buttons still be functional.
|
|
_maxFilesForBulkActions: {
|
|
type: Number,
|
|
readOnly: true,
|
|
value: 225,
|
|
},
|
|
_expandedFilePaths: {
|
|
type: Array,
|
|
value: function() { return []; },
|
|
},
|
|
},
|
|
|
|
behaviors: [
|
|
Gerrit.KeyboardShortcutBehavior,
|
|
Gerrit.PatchSetBehavior,
|
|
Gerrit.URLEncodingBehavior,
|
|
],
|
|
|
|
observers: [
|
|
'_expandedPathsChanged(_expandedFilePaths.splices)',
|
|
'_setReviewedFiles(_shownFiles, _files, _reviewed.*, _loggedIn)',
|
|
],
|
|
|
|
keyBindings: {
|
|
'shift+left': '_handleShiftLeftKey',
|
|
'shift+right': '_handleShiftRightKey',
|
|
'i': '_handleIKey',
|
|
'shift+i': '_handleCapitalIKey',
|
|
'down j': '_handleDownKey',
|
|
'up k': '_handleUpKey',
|
|
'c': '_handleCKey',
|
|
'[': '_handleLeftBracketKey',
|
|
']': '_handleRightBracketKey',
|
|
'o enter': '_handleEnterKey',
|
|
'n': '_handleNKey',
|
|
'p': '_handlePKey',
|
|
'shift+a': '_handleCapitalAKey',
|
|
},
|
|
|
|
reload: function() {
|
|
if (!this.changeNum || !this.patchRange.patchNum) {
|
|
return Promise.resolve();
|
|
}
|
|
this._collapseAllDiffs();
|
|
var promises = [];
|
|
var _this = this;
|
|
|
|
promises.push(this._getFiles().then(function(files) {
|
|
_this._files = files;
|
|
}));
|
|
promises.push(this._getLoggedIn().then(function(loggedIn) {
|
|
return _this._loggedIn = loggedIn;
|
|
}).then(function(loggedIn) {
|
|
if (!loggedIn) { return; }
|
|
|
|
return _this._getReviewedFiles().then(function(reviewed) {
|
|
_this._reviewed = reviewed;
|
|
});
|
|
}));
|
|
|
|
this._localPrefs = this.$.storage.getPreferences();
|
|
promises.push(this._getDiffPreferences().then(function(prefs) {
|
|
this._diffPrefs = prefs;
|
|
}.bind(this)));
|
|
|
|
promises.push(this._getPreferences().then(function(prefs) {
|
|
this._userPrefs = prefs;
|
|
if (!this.diffViewMode) {
|
|
this.set('diffViewMode', prefs.default_diff_view);
|
|
}
|
|
}.bind(this)));
|
|
},
|
|
|
|
get diffs() {
|
|
return Polymer.dom(this.root).querySelectorAll('gr-diff');
|
|
},
|
|
|
|
_calculatePatchChange: function(files) {
|
|
var filesNoCommitMsg = files.filter(function(files) {
|
|
return files.__path !== '/COMMIT_MSG';
|
|
});
|
|
|
|
return filesNoCommitMsg.reduce(function(acc, obj) {
|
|
var inserted = obj.lines_inserted ? obj.lines_inserted : 0;
|
|
var deleted = obj.lines_deleted ? obj.lines_deleted : 0;
|
|
var total_size = (obj.size && obj.binary) ? obj.size : 0;
|
|
var size_delta_inserted =
|
|
obj.binary && obj.size_delta > 0 ? obj.size_delta : 0;
|
|
var size_delta_deleted =
|
|
obj.binary && obj.size_delta < 0 ? obj.size_delta : 0;
|
|
|
|
return {
|
|
inserted: acc.inserted + inserted,
|
|
deleted: acc.deleted + deleted,
|
|
size_delta_inserted: acc.size_delta_inserted + size_delta_inserted,
|
|
size_delta_deleted: acc.size_delta_deleted + size_delta_deleted,
|
|
total_size: acc.total_size + total_size,
|
|
};
|
|
}, {inserted: 0, deleted: 0, size_delta_inserted: 0,
|
|
size_delta_deleted: 0, total_size: 0});
|
|
},
|
|
|
|
_getDiffPreferences: function() {
|
|
return this.$.restAPI.getDiffPreferences();
|
|
},
|
|
|
|
_getPreferences: function() {
|
|
return this.$.restAPI.getPreferences();
|
|
},
|
|
|
|
_computePatchSets: function(revisionRecord) {
|
|
var revisions = revisionRecord.base;
|
|
var patchNums = [];
|
|
for (var commit in revisions) {
|
|
if (revisions.hasOwnProperty(commit)) {
|
|
patchNums.push({
|
|
num: revisions[commit]._number,
|
|
desc: revisions[commit].description,
|
|
});
|
|
}
|
|
}
|
|
return patchNums.sort(function(a, b) { return a.num - b.num; });
|
|
},
|
|
|
|
_computePatchSetDisabled: function(patchNum, currentPatchNum) {
|
|
return parseInt(patchNum, 10) >= parseInt(currentPatchNum, 10);
|
|
},
|
|
|
|
_handleHiddenChange: function(e) {
|
|
this._togglePathExpanded(e.model.file.__path);
|
|
},
|
|
|
|
_togglePathExpanded: function(path) {
|
|
// Is the path in the list of expanded diffs? IF so remove it, otherwise
|
|
// add it to the list.
|
|
var pathIndex = this._expandedFilePaths.indexOf(path);
|
|
if (pathIndex === -1) {
|
|
this.push('_expandedFilePaths', path);
|
|
} else {
|
|
this.splice('_expandedFilePaths', pathIndex, 1);
|
|
}
|
|
},
|
|
|
|
_togglePathExpandedByIndex: function(index) {
|
|
this._togglePathExpanded(this._files[index].__path);
|
|
},
|
|
|
|
_handlePatchChange: function(e) {
|
|
var patchRange = Object.assign({}, this.patchRange);
|
|
patchRange.basePatchNum = Polymer.dom(e).rootTarget.value;
|
|
page.show(this.encodeURL('/c/' + this.changeNum + '/' +
|
|
this._patchRangeStr(patchRange), true));
|
|
},
|
|
|
|
_forEachDiff: function(fn) {
|
|
var diffs = this.diffs;
|
|
for (var i = 0; i < diffs.length; i++) {
|
|
fn(diffs[i]);
|
|
}
|
|
},
|
|
|
|
_expandAllDiffs: function(e) {
|
|
this._showInlineDiffs = true;
|
|
|
|
// Find the list of paths that are in the file list, but not in the
|
|
// expanded list.
|
|
var newPaths = [];
|
|
var path;
|
|
for (var i = 0; i < this._shownFiles.length; i++) {
|
|
path = this._shownFiles[i].__path;
|
|
if (this._expandedFilePaths.indexOf(path) === -1) {
|
|
newPaths.push(path);
|
|
}
|
|
}
|
|
|
|
this.splice.apply(this, ['_expandedFilePaths', 0, 0].concat(newPaths));
|
|
},
|
|
|
|
_collapseAllDiffs: function(e) {
|
|
this._showInlineDiffs = false;
|
|
this._expandedFilePaths = [];
|
|
this.$.diffCursor.handleDiffUpdate();
|
|
},
|
|
|
|
_computeCommentsString: function(comments, patchNum, path) {
|
|
return this._computeCountString(comments, patchNum, path, 'comment');
|
|
},
|
|
|
|
_computeDraftsString: function(drafts, patchNum, path) {
|
|
return this._computeCountString(drafts, patchNum, path, 'draft');
|
|
},
|
|
|
|
_computeDraftsStringMobile: function(drafts, patchNum, path) {
|
|
var draftCount = this._computeCountString(drafts, patchNum, path);
|
|
return draftCount ? draftCount + 'd' : '';
|
|
},
|
|
|
|
_computeCommentsStringMobile: function(comments, patchNum, path) {
|
|
var commentCount = this._computeCountString(comments, patchNum, path);
|
|
return commentCount ? commentCount + 'c' : '';
|
|
},
|
|
|
|
_getCommentsForPath: function(comments, patchNum, path) {
|
|
return (comments[path] || []).filter(function(c) {
|
|
return parseInt(c.patch_set, 10) === parseInt(patchNum, 10);
|
|
});
|
|
},
|
|
|
|
_computeCountString: function(comments, patchNum, path, opt_noun) {
|
|
if (!comments) { return ''; }
|
|
|
|
var patchComments = this._getCommentsForPath(comments, patchNum, path);
|
|
var num = patchComments.length;
|
|
if (num === 0) { return ''; }
|
|
if (!opt_noun) { return num; }
|
|
var output = num + ' ' + opt_noun + (num > 1 ? 's' : '');
|
|
return output;
|
|
},
|
|
|
|
/**
|
|
* Computes a string counting the number of unresolved comment threads in a
|
|
* given file and path.
|
|
*
|
|
* @param {Object} comments
|
|
* @param {Object} drafts
|
|
* @param {number} patchNum
|
|
* @param {string} path
|
|
* @return {string}
|
|
*/
|
|
_computeUnresolvedString: function(comments, drafts, patchNum, path) {
|
|
comments = this._getCommentsForPath(comments, patchNum, path);
|
|
drafts = this._getCommentsForPath(drafts, patchNum, path);
|
|
comments = comments.concat(drafts);
|
|
|
|
// Create an object where every comment ID is the key of an unresolved
|
|
// comment.
|
|
|
|
var idMap = comments.reduce(function(acc, comment) {
|
|
if (comment.unresolved) {
|
|
acc[comment.id] = true;
|
|
}
|
|
return acc;
|
|
}, {});
|
|
|
|
// Set false for the comments that are marked as parents.
|
|
comments.forEach(function(comment) {
|
|
idMap[comment.in_reply_to] = false;
|
|
});
|
|
|
|
// The unresolved comments are the comments that still have true.
|
|
var unresolvedLeaves = Object.keys(idMap).filter(function(key) {
|
|
return idMap[key];
|
|
});
|
|
|
|
return unresolvedLeaves.length === 0 ?
|
|
'' : '(' + unresolvedLeaves.length + ' unresolved)';
|
|
},
|
|
|
|
_computeReviewed: function(file, _reviewed) {
|
|
return _reviewed.indexOf(file.__path) !== -1;
|
|
},
|
|
|
|
_handleReviewedChange: function(e) {
|
|
this._reviewFile(Polymer.dom(e).rootTarget.getAttribute('data-path'));
|
|
},
|
|
|
|
_reviewFile: function(path) {
|
|
var index = this._reviewed.indexOf(path);
|
|
var reviewed = index !== -1;
|
|
if (reviewed) {
|
|
this.splice('_reviewed', index, 1);
|
|
} else {
|
|
this.push('_reviewed', path);
|
|
}
|
|
|
|
this._saveReviewedState(path, !reviewed);
|
|
},
|
|
|
|
_saveReviewedState: function(path, reviewed) {
|
|
return this.$.restAPI.saveFileReviewed(this.changeNum,
|
|
this.patchRange.patchNum, path, reviewed);
|
|
},
|
|
|
|
_getLoggedIn: function() {
|
|
return this.$.restAPI.getLoggedIn();
|
|
},
|
|
|
|
_getReviewedFiles: function() {
|
|
return this.$.restAPI.getReviewedFiles(this.changeNum,
|
|
this.patchRange.patchNum);
|
|
},
|
|
|
|
_getFiles: function() {
|
|
return this.$.restAPI.getChangeFilesAsSpeciallySortedArray(
|
|
this.changeNum, this.patchRange).then(function(files) {
|
|
// Append UI-specific properties.
|
|
return files.map(function(file) {
|
|
return file;
|
|
});
|
|
});
|
|
},
|
|
|
|
_handleFileTap: function(e) {
|
|
// If the user prefers to expand inline diffs rather than opening the diff
|
|
// view, intercept the click event.
|
|
if (e.detail.sourceEvent.metaKey || e.detail.sourceEvent.ctrlKey) {
|
|
return;
|
|
}
|
|
if (this._userPrefs && this._userPrefs.expand_inline_diffs) {
|
|
e.preventDefault();
|
|
this._handleHiddenChange(e);
|
|
}
|
|
},
|
|
|
|
_handleShiftLeftKey: function(e) {
|
|
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
|
|
if (!this._showInlineDiffs) { return; }
|
|
|
|
e.preventDefault();
|
|
this.$.diffCursor.moveLeft();
|
|
},
|
|
|
|
_handleShiftRightKey: function(e) {
|
|
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
|
|
if (!this._showInlineDiffs) { return; }
|
|
|
|
e.preventDefault();
|
|
this.$.diffCursor.moveRight();
|
|
},
|
|
|
|
_handleIKey: function(e) {
|
|
if (this.shouldSuppressKeyboardShortcut(e) ||
|
|
this.modifierPressed(e) ||
|
|
this.$.fileCursor.index === -1) { return; }
|
|
|
|
e.preventDefault();
|
|
this._togglePathExpandedByIndex(this.$.fileCursor.index);
|
|
},
|
|
|
|
_handleCapitalIKey: function(e) {
|
|
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
|
|
|
|
e.preventDefault();
|
|
this._toggleInlineDiffs();
|
|
},
|
|
|
|
_handleDownKey: function(e) {
|
|
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
|
|
e.preventDefault();
|
|
if (this._showInlineDiffs) {
|
|
this.$.diffCursor.moveDown();
|
|
} else {
|
|
this.$.fileCursor.next();
|
|
this.selectedIndex = this.$.fileCursor.index;
|
|
}
|
|
},
|
|
|
|
_handleUpKey: function(e) {
|
|
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
|
|
|
|
e.preventDefault();
|
|
if (this._showInlineDiffs) {
|
|
this.$.diffCursor.moveUp();
|
|
} else {
|
|
this.$.fileCursor.previous();
|
|
this.selectedIndex = this.$.fileCursor.index;
|
|
}
|
|
},
|
|
|
|
_handleCKey: function(e) {
|
|
if (this.shouldSuppressKeyboardShortcut(e) ||
|
|
this.modifierPressed(e)) { return; }
|
|
|
|
var isRangeSelected = this.diffs.some(function(diff) {
|
|
return diff.isRangeSelected();
|
|
}, this);
|
|
if (this._showInlineDiffs && !isRangeSelected) {
|
|
e.preventDefault();
|
|
this._addDraftAtTarget();
|
|
}
|
|
},
|
|
|
|
_handleLeftBracketKey: function(e) {
|
|
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
|
|
|
|
e.preventDefault();
|
|
this._openSelectedFile(this._files.length - 1);
|
|
},
|
|
|
|
_handleRightBracketKey: function(e) {
|
|
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
|
|
|
|
e.preventDefault();
|
|
this._openSelectedFile(0);
|
|
},
|
|
|
|
_handleEnterKey: function(e) {
|
|
if (this.shouldSuppressKeyboardShortcut(e) ||
|
|
this.modifierPressed(e)) { return; }
|
|
|
|
// Use native handling if an anchor is selected. @see Issue 5754
|
|
if (e.detail && e.detail.keyboardEvent && e.detail.keyboardEvent.target &&
|
|
e.detail.keyboardEvent.target.tagName === 'A') { return; }
|
|
|
|
e.preventDefault();
|
|
if (this._showInlineDiffs) {
|
|
this._openCursorFile();
|
|
} else {
|
|
this._openSelectedFile();
|
|
}
|
|
},
|
|
|
|
_handleNKey: function(e) {
|
|
if (this.shouldSuppressKeyboardShortcut(e) ||
|
|
this.modifierPressed(e)) { return; }
|
|
if (!this._showInlineDiffs) { return; }
|
|
|
|
e.preventDefault();
|
|
if (e.shiftKey) {
|
|
this.$.diffCursor.moveToNextCommentThread();
|
|
} else {
|
|
this.$.diffCursor.moveToNextChunk();
|
|
}
|
|
},
|
|
|
|
_handlePKey: function(e) {
|
|
if (this.shouldSuppressKeyboardShortcut(e) ||
|
|
this.modifierPressed(e)) { return; }
|
|
if (!this._showInlineDiffs) { return; }
|
|
|
|
e.preventDefault();
|
|
if (e.shiftKey) {
|
|
this.$.diffCursor.moveToPreviousCommentThread();
|
|
} else {
|
|
this.$.diffCursor.moveToPreviousChunk();
|
|
}
|
|
},
|
|
|
|
_handleCapitalAKey: function(e) {
|
|
if (this.shouldSuppressKeyboardShortcut(e)) { return; }
|
|
|
|
e.preventDefault();
|
|
this._forEachDiff(function(diff) {
|
|
diff.toggleLeftDiff();
|
|
});
|
|
},
|
|
|
|
_toggleInlineDiffs: function() {
|
|
if (this._showInlineDiffs) {
|
|
this._collapseAllDiffs();
|
|
} else {
|
|
this._expandAllDiffs();
|
|
}
|
|
},
|
|
|
|
_openCursorFile: function() {
|
|
var diff = this.$.diffCursor.getTargetDiffElement();
|
|
page.show(this._computeDiffURL(diff.changeNum, diff.patchRange,
|
|
diff.path));
|
|
},
|
|
|
|
_openSelectedFile: function(opt_index) {
|
|
if (opt_index != null) {
|
|
this.$.fileCursor.setCursorAtIndex(opt_index);
|
|
}
|
|
page.show(this._computeDiffURL(this.changeNum, this.patchRange,
|
|
this._files[this.$.fileCursor.index].__path));
|
|
},
|
|
|
|
_addDraftAtTarget: function() {
|
|
var diff = this.$.diffCursor.getTargetDiffElement();
|
|
var target = this.$.diffCursor.getTargetLineElement();
|
|
if (diff && target) {
|
|
diff.addDraftAtLine(target);
|
|
}
|
|
},
|
|
|
|
_shouldHideChangeTotals: function(_patchChange) {
|
|
return _patchChange.inserted === 0 && _patchChange.deleted === 0;
|
|
},
|
|
|
|
_shouldHideBinaryChangeTotals: function(_patchChange) {
|
|
return _patchChange.size_delta_inserted === 0 &&
|
|
_patchChange.size_delta_deleted === 0;
|
|
},
|
|
|
|
_computeFileStatus: function(status) {
|
|
return status || 'M';
|
|
},
|
|
|
|
_computeDiffURL: function(changeNum, patchRange, path) {
|
|
return this.encodeURL('/c/' + changeNum + '/' +
|
|
this._patchRangeStr(patchRange) + '/' + path, true);
|
|
},
|
|
|
|
_patchRangeStr: function(patchRange) {
|
|
return patchRange.basePatchNum !== 'PARENT' ?
|
|
patchRange.basePatchNum + '..' + patchRange.patchNum :
|
|
patchRange.patchNum + '';
|
|
},
|
|
|
|
_computeFileDisplayName: function(path) {
|
|
return path === COMMIT_MESSAGE_PATH ? 'Commit message' : path;
|
|
},
|
|
|
|
_computeTruncatedFileDisplayName: function(path) {
|
|
return path === COMMIT_MESSAGE_PATH ?
|
|
'Commit message' : util.truncatePath(path);
|
|
},
|
|
|
|
_formatBytes: function(bytes) {
|
|
if (bytes == 0) return '+/-0 B';
|
|
var bits = 1024;
|
|
var decimals = 1;
|
|
var sizes = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
|
|
var exponent = Math.floor(Math.log(Math.abs(bytes)) / Math.log(bits));
|
|
var prepend = bytes > 0 ? '+' : '';
|
|
return prepend + parseFloat((bytes / Math.pow(bits, exponent))
|
|
.toFixed(decimals)) + ' ' + sizes[exponent];
|
|
},
|
|
|
|
_formatPercentage: function(size, delta) {
|
|
var oldSize = size - delta;
|
|
|
|
if (oldSize === 0) { return ''; }
|
|
|
|
var percentage = Math.round(Math.abs(delta * 100 / oldSize));
|
|
return '(' + (delta > 0 ? '+' : '-') + percentage + '%)';
|
|
},
|
|
|
|
_computeBinaryClass: function(delta) {
|
|
if (delta === 0) { return; }
|
|
return delta >= 0 ? 'added' : 'removed';
|
|
},
|
|
|
|
_computeClass: function(baseClass, path) {
|
|
var classes = [baseClass];
|
|
if (path === COMMIT_MESSAGE_PATH) {
|
|
classes.push('invisible');
|
|
}
|
|
return classes.join(' ');
|
|
},
|
|
|
|
_computeExpandInlineClass: function(userPrefs) {
|
|
return userPrefs.expand_inline_diffs ? 'expandInline' : '';
|
|
},
|
|
|
|
_computePathClass: function(path, expandedFilesRecord) {
|
|
return this._isFileExpanded(path, expandedFilesRecord) ? 'path expanded' :
|
|
'path';
|
|
},
|
|
|
|
_computeShowHideText: function(path, expandedFilesRecord) {
|
|
return this._isFileExpanded(path, expandedFilesRecord) ? '▼' : '◀';
|
|
},
|
|
|
|
_computeFilesShown: function(numFilesShown, files) {
|
|
return files.base.slice(0, numFilesShown);
|
|
},
|
|
|
|
_setReviewedFiles: function(shownFiles, files, reviewedRecord, loggedIn) {
|
|
if (!loggedIn) { return; }
|
|
var reviewed = reviewedRecord.base;
|
|
var fileReviewed;
|
|
for (var i = 0; i < files.length; i++) {
|
|
fileReviewed = this._computeReviewed(files[i], reviewed);
|
|
this._files[i].isReviewed = fileReviewed;
|
|
if (i < shownFiles.length) {
|
|
this.set(['_shownFiles', i, 'isReviewed'], fileReviewed);
|
|
}
|
|
}
|
|
},
|
|
|
|
_filesChanged: function() {
|
|
this.async(function() {
|
|
var diffElements = Polymer.dom(this.root).querySelectorAll('gr-diff');
|
|
|
|
// Overwrite the cursor's list of diffs:
|
|
this.$.diffCursor.splice.apply(this.$.diffCursor,
|
|
['diffs', 0, this.$.diffCursor.diffs.length].concat(diffElements));
|
|
|
|
var files = Polymer.dom(this.root).querySelectorAll('.file-row');
|
|
this.$.fileCursor.stops = files;
|
|
this.$.fileCursor.setCursorAtIndex(this.selectedIndex, true);
|
|
}.bind(this), 1);
|
|
},
|
|
|
|
_incrementNumFilesShown: function() {
|
|
this._numFilesShown += this._fileListIncrement;
|
|
},
|
|
|
|
_computeFileListButtonHidden: function(numFilesShown, files) {
|
|
return numFilesShown >= files.length;
|
|
},
|
|
|
|
_computeIncrementText: function(numFilesShown, files) {
|
|
if (!files) { return ''; }
|
|
var text =
|
|
Math.min(this._fileListIncrement, files.length - numFilesShown);
|
|
return 'Show ' + text + ' more';
|
|
},
|
|
|
|
_computeShowAllText: function(files) {
|
|
if (!files) { return ''; }
|
|
return 'Show all ' + files.length + ' files';
|
|
},
|
|
|
|
_showAllFiles: function() {
|
|
this._numFilesShown = this._files.length;
|
|
},
|
|
|
|
_updateSelected: function(patchRange) {
|
|
this._diffAgainst = patchRange.basePatchNum;
|
|
},
|
|
|
|
/**
|
|
* _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.
|
|
*
|
|
* Use side-by-side if there is no view mode or preferences.
|
|
*
|
|
* @return {String}
|
|
*/
|
|
_getDiffViewMode: function(diffViewMode, userPrefs) {
|
|
if (diffViewMode) {
|
|
return diffViewMode;
|
|
} else if (userPrefs) {
|
|
return this.diffViewMode = userPrefs.default_diff_view;
|
|
}
|
|
return 'SIDE_BY_SIDE';
|
|
},
|
|
|
|
_fileListActionsVisible: function(shownFilesRecord,
|
|
maxFilesForBulkActions) {
|
|
return shownFilesRecord.base.length <= maxFilesForBulkActions;
|
|
},
|
|
|
|
_computePatchSetDescription: function(revisions, patchNum) {
|
|
var rev = this.getRevisionByPatchNum(revisions, patchNum);
|
|
return (rev && rev.description) ?
|
|
rev.description.substring(0, PATCH_DESC_MAX_LENGTH) : '';
|
|
},
|
|
|
|
_computeFileStatusLabel: function(status) {
|
|
var statusCode = this._computeFileStatus(status);
|
|
return FileStatus.hasOwnProperty(statusCode) ?
|
|
FileStatus[statusCode] : 'Status Unknown';
|
|
},
|
|
|
|
_isFileExpanded: function(path, expandedFilesRecord) {
|
|
return expandedFilesRecord.base.indexOf(path) !== -1;
|
|
},
|
|
|
|
/**
|
|
* Handle splices to the list of expanded file paths. If there are any new
|
|
* entries in the expanded list, then render each diff corresponding in
|
|
* order by waiting for the previous diff to finish before starting the next
|
|
* one.
|
|
* @param {splice} record The splice record in the expanded paths list.
|
|
*/
|
|
_expandedPathsChanged: function(record) {
|
|
if (!record) { return; }
|
|
|
|
// Find the paths introduced by the new index splices:
|
|
var newPaths = record.indexSplices
|
|
.map(function(splice) {
|
|
return splice.object.slice(splice.index,
|
|
splice.index + splice.addedCount);
|
|
})
|
|
.reduce(function(acc, paths) { return acc.concat(paths); }, []);
|
|
|
|
var timerName = 'Expand ' + newPaths.length + ' diffs';
|
|
this.$.reporting.time(timerName);
|
|
|
|
this._renderInOrder(newPaths, this.diffs, newPaths.length)
|
|
.then(function() {
|
|
this.$.reporting.timeEnd(timerName);
|
|
this.$.diffCursor.handleDiffUpdate();
|
|
}.bind(this));
|
|
},
|
|
|
|
/**
|
|
* Given an array of paths and a NodeList of diff elements, render the diff
|
|
* for each path in order, awaiting the previous render to complete before
|
|
* continung.
|
|
* @param {!Array<!String>} paths
|
|
* @param {!NodeList<!GrDiffElement>} diffElements
|
|
* @param {Number} initialCount The total number of paths in the pass. This
|
|
* is used to generate log messages.
|
|
* @return {!Promise}
|
|
*/
|
|
_renderInOrder: function(paths, diffElements, initialCount) {
|
|
if (!paths.length) {
|
|
console.log('Finished expanding', initialCount, 'diff(s)');
|
|
return Promise.resolve();
|
|
}
|
|
console.log('Expanding diff', 1 + initialCount - paths.length, 'of',
|
|
initialCount, ':', paths[0]);
|
|
var diffElem = this._findDiffByPath(paths[0], diffElements);
|
|
var promises = [diffElem.reload()];
|
|
if (this._isLoggedIn) {
|
|
promises.push(this._reviewFile(paths[0]));
|
|
}
|
|
return Promise.all(promises).then(function() {
|
|
return this._renderInOrder(paths.slice(1), diffElements, initialCount);
|
|
}.bind(this));
|
|
},
|
|
|
|
/**
|
|
* In the given NodeList of diff elements, find the diff for the given path.
|
|
* @param {!String} path
|
|
* @param {!NodeList<!GrDiffElement>} diffElements
|
|
* @return {!GrDiffElement}
|
|
*/
|
|
_findDiffByPath: function(path, diffElements) {
|
|
for (var i = 0; i < diffElements.length; i++) {
|
|
if (diffElements[i].path === path) {
|
|
return diffElements[i];
|
|
}
|
|
}
|
|
},
|
|
});
|
|
})();
|