864 lines
26 KiB
JavaScript
864 lines
26 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 WARN_SHOW_ALL_THRESHOLD = 1000;
|
|
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,
|
|
notify: true,
|
|
},
|
|
_patchChange: {
|
|
type: Object,
|
|
computed: '_calculatePatchChange(_files)',
|
|
},
|
|
fileListIncrement: Number,
|
|
_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.BaseUrlBehavior,
|
|
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);
|
|
},
|
|
|
|
_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;
|
|
});
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Handle all events from the file list dom-repeat so event handleers don't
|
|
* have to get registered for potentially very long lists.
|
|
*/
|
|
_handleFileListTap: function(e) {
|
|
// Handle checkbox mark as reviewed.
|
|
if (e.target.classList.contains('reviewed')) {
|
|
return this._handleReviewedChange(e);
|
|
}
|
|
|
|
// Check to see if the file should be expanded.
|
|
var path = e.target.dataset.path || e.target.parentElement.dataset.path;
|
|
|
|
// If the user prefers to expand inline diffs rather than opening the diff
|
|
// view, intercept the click event.
|
|
if (!path || e.detail.sourceEvent.metaKey ||
|
|
e.detail.sourceEvent.ctrlKey) {
|
|
return;
|
|
}
|
|
if (e.target.dataset.expand ||
|
|
this._userPrefs && this._userPrefs.expand_inline_diffs) {
|
|
e.preventDefault();
|
|
this._togglePathExpanded(path);
|
|
}
|
|
},
|
|
|
|
_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) || this.modifierPressed(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) || this.modifierPressed(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) && !this.isModifierPressed(e, 'shiftKey')) {
|
|
return;
|
|
}
|
|
if (!this._showInlineDiffs) { return; }
|
|
|
|
e.preventDefault();
|
|
if (this.isModifierPressed(e, 'shiftKey')) {
|
|
this.$.diffCursor.moveToNextCommentThread();
|
|
} else {
|
|
this.$.diffCursor.moveToNextChunk();
|
|
}
|
|
},
|
|
|
|
_handlePKey: function(e) {
|
|
if (this.shouldSuppressKeyboardShortcut(e) ||
|
|
this.modifierPressed(e) && !this.isModifierPressed(e, 'shiftKey')) {
|
|
return;
|
|
}
|
|
if (!this._showInlineDiffs) { return; }
|
|
|
|
e.preventDefault();
|
|
if (this.isModifierPressed(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(this.getBaseUrl() + '/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);
|
|
}
|
|
}
|
|
},
|
|
|
|
_updateDiffCursor: 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));
|
|
},
|
|
|
|
_filesChanged: function(files) {
|
|
Polymer.dom.flush();
|
|
var files = Polymer.dom(this.root).querySelectorAll('.file-row');
|
|
this.$.fileCursor.stops = files;
|
|
this.$.fileCursor.setCursorAtIndex(this.selectedIndex, true);
|
|
},
|
|
|
|
_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';
|
|
},
|
|
|
|
_computeWarnShowAll: function(files) {
|
|
return files.length > WARN_SHOW_ALL_THRESHOLD;
|
|
},
|
|
|
|
_computeShowAllWarning: function(files) {
|
|
if (!this._computeWarnShowAll(files)) { return ''; }
|
|
return 'Warning: showing all ' + files.length +
|
|
' files may take several seconds.';
|
|
},
|
|
|
|
_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;
|
|
},
|
|
|
|
_onLineSelected: function(e, detail) {
|
|
this.$.diffCursor.moveToLineNumber(detail.number, detail.side,
|
|
detail.path);
|
|
},
|
|
|
|
/**
|
|
* 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);
|
|
|
|
// Required so that the newly created diff view is included in this.diffs.
|
|
Polymer.dom.flush();
|
|
|
|
this._renderInOrder(newPaths, this.diffs, newPaths.length)
|
|
.then(function() {
|
|
this.$.reporting.timeEnd(timerName);
|
|
this.$.diffCursor.handleDiffUpdate();
|
|
}.bind(this));
|
|
this._updateDiffCursor();
|
|
this.$.diffCursor.handleDiffUpdate();
|
|
},
|
|
|
|
/**
|
|
* 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];
|
|
}
|
|
}
|
|
},
|
|
});
|
|
})();
|