Viktar Donich 1057e0505f Add padding to diff when scrollbar is inline
Inline scrollbars are invisible until scrolling starts, or, for
ChromeOS, scrollbar area is hovered.
When scrollbar appears, it overlaps last line of diff.
This change adds a padding to prevent scrollbar overlapping last line of
diff.

Bug: Issue 5964
Change-Id: I072f8e8a4d4d2750f122bc6177b8db492b258f5f
2017-05-12 11:24:31 -07:00

603 lines
17 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';
var DiffViewMode = {
SIDE_BY_SIDE: 'SIDE_BY_SIDE',
UNIFIED: 'UNIFIED_DIFF',
};
var DiffSide = {
LEFT: 'left',
RIGHT: 'right',
};
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
*/
properties: {
changeNum: String,
noAutoRender: {
type: Boolean,
value: false,
},
patchRange: Object,
path: String,
prefs: {
type: Object,
observer: '_prefsObserver',
},
projectConfig: {
type: Object,
observer: '_projectConfigChanged',
},
displayLine: {
type: Boolean,
value: false,
},
isImageDiff: {
type: Boolean,
computed: '_computeIsImageDiff(_diff)',
notify: true,
},
filesWeblinks: {
type: Object,
value: function() { return {}; },
notify: true,
},
hidden: {
type: Boolean,
reflectToAttribute: true,
},
noRenderOnPrefsChange: Boolean,
_loggedIn: {
type: Boolean,
value: false,
},
lineWrapping: {
type: Boolean,
value: false,
observer: '_lineWrappingObserver',
},
viewMode: {
type: String,
value: DiffViewMode.SIDE_BY_SIDE,
observer: '_viewModeObserver',
},
_diff: Object,
_diffHeaderItems: {
type: Array,
value: [],
computed: '_computeDiffHeaderItems(_diff.*)',
},
_diffTableClass: {
type: String,
value: '',
},
_comments: Object,
_baseImage: Object,
_revisionImage: Object,
},
listeners: {
'thread-discard': '_handleThreadDiscard',
'comment-discard': '_handleCommentDiscard',
'comment-update': '_handleCommentUpdate',
'comment-save': '_handleCommentSave',
'create-comment': '_handleCreateComment',
},
attached: function() {
this._getLoggedIn().then(function(loggedIn) {
this._loggedIn = loggedIn;
}.bind(this));
},
ready: function() {
if (this._canRender()) {
this.reload();
}
},
reload: function() {
this._clearDiffContent();
var promises = [];
promises.push(this._getDiff().then(function(diff) {
this._diff = diff;
return this._loadDiffAssets();
}.bind(this)));
promises.push(this._getDiffCommentsAndDrafts().then(function(comments) {
this._comments = comments;
}.bind(this)));
return Promise.all(promises).then(function() {
if (this.prefs) {
return this._renderDiffTable();
}
return Promise.resolve();
}.bind(this));
},
getCursorStops: function() {
if (this.hidden && this.noAutoRender) {
return [];
}
return Polymer.dom(this.root).querySelectorAll('.diff-row');
},
addDraftAtLine: function(el) {
this._selectLine(el);
this._getLoggedIn().then(function(loggedIn) {
if (!loggedIn) {
this.fire('show-auth-required');
return;
}
var value = el.getAttribute('data-value');
if (value === GrDiffLine.FILE) {
this._addDraft(el);
return;
}
var lineNum = parseInt(value, 10);
if (isNaN(lineNum)) {
throw Error('Invalid line number: ' + value);
}
this._addDraft(el, lineNum);
}.bind(this));
},
isRangeSelected: function() {
return this.$.highlights.isRangeSelected();
},
toggleLeftDiff: function() {
this.toggleClass('no-left');
},
_canRender: function() {
return this.changeNum && this.patchRange && this.path &&
!this.noAutoRender;
},
_getCommentThreads: function() {
return Polymer.dom(this.root).querySelectorAll('gr-diff-comment-thread');
},
_computeContainerClass: function(loggedIn, viewMode, displayLine) {
var 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: function(e) {
var el = Polymer.dom(e).rootTarget;
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')) {
var target = this.$.diffBuilder.getLineElByChild(el);
if (target) { this._selectLine(target); }
}
},
_selectLine: function(el) {
this.fire('line-selected', {
side: el.classList.contains('left') ? DiffSide.LEFT : DiffSide.RIGHT,
number: el.getAttribute('data-value'),
path: this.path,
});
},
_handleCreateComment: function(e) {
var range = e.detail.range;
var diffSide = e.detail.side;
var line = range.endLine;
var lineEl = this.$.diffBuilder.getLineElByNumber(line, diffSide);
var contentText = this.$.diffBuilder.getContentByLineEl(lineEl);
var contentEl = contentText.parentElement;
var patchNum = this._getPatchNumByLineAndContent(lineEl, contentEl);
var isOnParent =
this._getIsParentCommentByLineAndContent(lineEl, contentEl);
var threadEl = this._getOrCreateThreadAtLineRange(contentEl, patchNum,
diffSide, isOnParent, range);
threadEl.addOrEditDraft(line, range);
},
_addDraft: function(lineEl, opt_lineNum) {
var contentText = this.$.diffBuilder.getContentByLineEl(lineEl);
var contentEl = contentText.parentElement;
var patchNum = this._getPatchNumByLineAndContent(lineEl, contentEl);
var commentSide = this._getCommentSideByLineAndContent(lineEl, contentEl);
var isOnParent =
this._getIsParentCommentByLineAndContent(lineEl, contentEl);
var threadEl = this._getOrCreateThreadAtLineRange(contentEl, patchNum,
commentSide, isOnParent);
threadEl.addOrEditDraft(opt_lineNum);
},
_getThreadForRange: function(threadGroupEl, rangeToCheck) {
return threadGroupEl.getThreadForRange(rangeToCheck);
},
_getThreadGroupForLine: function(contentEl) {
return contentEl.querySelector('gr-diff-comment-thread-group');
},
_getOrCreateThreadAtLineRange:
function(contentEl, patchNum, commentSide, isOnParent, range) {
var rangeToCheck = range ?
'range-' +
range.startLine + '-' +
range.startChar + '-' +
range.endLine + '-' +
range.endChar + '-' +
commentSide : 'line-' + commentSide;
// Check if thread group exists.
var threadGroupEl = this._getThreadGroupForLine(contentEl);
if (!threadGroupEl) {
threadGroupEl = this.$.diffBuilder.createCommentThreadGroup(
this.changeNum, patchNum, this.path, isOnParent,
this.projectConfig);
contentEl.appendChild(threadGroupEl);
}
var threadEl = this._getThreadForRange(threadGroupEl, rangeToCheck);
if (!threadEl) {
threadGroupEl.addNewThread(rangeToCheck, commentSide);
Polymer.dom.flush();
threadEl = this._getThreadForRange(threadGroupEl, rangeToCheck);
threadEl.commentSide = commentSide;
}
return threadEl;
},
_getPatchNumByLineAndContent: function(lineEl, contentEl) {
var patchNum = this.patchRange.patchNum;
if ((lineEl.classList.contains(DiffSide.LEFT) ||
contentEl.classList.contains('remove')) &&
this.patchRange.basePatchNum !== 'PARENT') {
patchNum = this.patchRange.basePatchNum;
}
return patchNum;
},
_getIsParentCommentByLineAndContent: function(lineEl, contentEl) {
var isOnParent = false;
if ((lineEl.classList.contains(DiffSide.LEFT) ||
contentEl.classList.contains('remove')) &&
this.patchRange.basePatchNum === 'PARENT') {
isOnParent = true;
}
return isOnParent;
},
_getCommentSideByLineAndContent: function(lineEl, contentEl) {
var side = 'right';
if (lineEl.classList.contains(DiffSide.LEFT) ||
contentEl.classList.contains('remove')) {
side = 'left';
}
return side;
},
_handleThreadDiscard: function(e) {
var el = Polymer.dom(e).rootTarget;
el.parentNode.removeThread(el.locationRange);
},
_handleCommentDiscard: function(e) {
var comment = e.detail.comment;
this._removeComment(comment, e.detail.patchNum);
},
_removeComment: function(comment) {
var side = comment.__commentSide;
this._removeCommentFromSide(comment, side);
},
_handleCommentSave: function(e) {
var comment = e.detail.comment;
var side = e.detail.comment.__commentSide;
var idx = this._findDraftIndex(comment, side);
this.set(['_comments', side, idx], comment);
},
_handleCommentUpdate: function(e) {
var comment = e.detail.comment;
var side = e.detail.comment.__commentSide;
var 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: function(comment, side) {
var idx = this._findCommentIndex(comment, side);
if (idx === -1) {
idx = this._findDraftIndex(comment, side);
}
if (idx !== -1) {
this.splice('_comments.' + side, idx, 1);
}
},
_findCommentIndex: function(comment, side) {
if (!comment.id || !this._comments[side]) {
return -1;
}
return this._comments[side].findIndex(function(item) {
return item.id === comment.id;
});
},
_findDraftIndex: function(comment, side) {
if (!comment.__draftID || !this._comments[side]) {
return -1;
}
return this._comments[side].findIndex(function(item) {
return item.__draftID === comment.__draftID;
});
},
_prefsObserver: function(newPrefs, oldPrefs) {
// Scan the preference objects one level deep to see if they differ.
var differ = !oldPrefs;
if (newPrefs && oldPrefs) {
for (var key in newPrefs) {
if (newPrefs[key] !== oldPrefs[key]) {
differ = true;
}
}
}
if (differ) {
this._prefsChanged(newPrefs);
}
},
_viewModeObserver: function() {
this._prefsChanged(this.prefs);
},
_lineWrappingObserver: function() {
this._prefsChanged(this.prefs);
},
_prefsChanged: function(prefs) {
if (!prefs) { return; }
if (prefs.line_wrapping) {
this._diffTableClass = 'full-width';
if (this.viewMode === 'SIDE_BY_SIDE') {
this.customStyle['--content-width'] = 'none';
}
} else {
this._diffTableClass = '';
this.customStyle['--content-width'] = prefs.line_length + 'ch';
}
if (!!prefs.font_size) {
this.customStyle['--font-size'] = prefs.font_size + 'px';
}
this.updateStyles();
if (this._diff && this._comments && !this.noRenderOnPrefsChange) {
this._renderDiffTable();
}
},
_renderDiffTable: function() {
return this.$.diffBuilder.render(this._comments, this.prefs);
},
_clearDiffContent: function() {
this.$.diffTable.innerHTML = null;
},
_handleGetDiffError: function(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: response});
return;
}
this.fire('page-error', {response: response});
},
_getDiff: function() {
return this.$.restAPI.getDiff(
this.changeNum,
this.patchRange.basePatchNum,
this.patchRange.patchNum,
this.path,
this._handleGetDiffError.bind(this)).then(function(diff) {
this.filesWeblinks = {
meta_a: diff && diff.meta_a && diff.meta_a.web_links,
meta_b: diff && diff.meta_b && diff.meta_b.web_links,
};
return diff;
}.bind(this));
},
_getDiffComments: function() {
return this.$.restAPI.getDiffComments(
this.changeNum,
this.patchRange.basePatchNum,
this.patchRange.patchNum,
this.path);
},
_getDiffDrafts: function() {
return this._getLoggedIn().then(function(loggedIn) {
if (!loggedIn) {
return Promise.resolve({baseComments: [], comments: []});
}
return this.$.restAPI.getDiffDrafts(
this.changeNum,
this.patchRange.basePatchNum,
this.patchRange.patchNum,
this.path);
}.bind(this));
},
_getDiffRobotComments: function() {
return this.$.restAPI.getDiffRobotComments(
this.changeNum,
this.patchRange.basePatchNum,
this.patchRange.patchNum,
this.path);
},
_getDiffCommentsAndDrafts: function() {
var promises = [];
promises.push(this._getDiffComments());
promises.push(this._getDiffDrafts());
promises.push(this._getDiffRobotComments());
return Promise.all(promises).then(function(results) {
return Promise.resolve({
comments: results[0],
drafts: results[1],
robotComments: results[2],
});
}).then(this._normalizeDiffCommentsAndDrafts.bind(this));
},
_normalizeDiffCommentsAndDrafts: function(results) {
function markAsDraft(d) {
d.__draft = true;
return d;
}
var baseDrafts = results.drafts.baseComments.map(markAsDraft);
var drafts = results.drafts.comments.map(markAsDraft);
var baseRobotComments = results.robotComments.baseComments;
var robotComments = results.robotComments.comments;
return Promise.resolve({
meta: {
path: this.path,
changeNum: this.changeNum,
patchRange: this.patchRange,
projectConfig: this.projectConfig,
},
left: results.comments.baseComments.concat(baseDrafts)
.concat(baseRobotComments),
right: results.comments.comments.concat(drafts)
.concat(robotComments),
});
},
_getLoggedIn: function() {
return this.$.restAPI.getLoggedIn();
},
_computeIsImageDiff: function() {
if (!this._diff) { return false; }
var isA = this._diff.meta_a &&
this._diff.meta_a.content_type.indexOf('image/') === 0;
var isB = this._diff.meta_b &&
this._diff.meta_b.content_type.indexOf('image/') === 0;
return this._diff.binary && (isA || isB);
},
_loadDiffAssets: function() {
if (this.isImageDiff) {
return this._getImages().then(function(images) {
this._baseImage = images.baseImage;
this._revisionImage = images.revisionImage;
}.bind(this));
} else {
this._baseImage = null;
this._revisionImage = null;
return Promise.resolve();
}
},
_getImages: function() {
return this.$.restAPI.getImagesForDiff(this.changeNum, this._diff,
this.patchRange);
},
_projectConfigChanged: function(projectConfig) {
var threadEls = this._getCommentThreads();
for (var i = 0; i < threadEls.length; i++) {
threadEls[i].projectConfig = projectConfig;
}
},
_computeDiffHeaderItems: function(diffInfoRecord) {
var diffInfo = diffInfoRecord.base;
if (!diffInfo || !diffInfo.diff_header || diffInfo.binary) { return []; }
return diffInfo.diff_header.filter(function(item) {
return !(item.indexOf('diff --git ') === 0 ||
item.indexOf('index ') === 0 ||
item.indexOf('+++ ') === 0 ||
item.indexOf('--- ') === 0);
});
},
_computeDiffHeaderHidden: function(items) {
return items.length === 0;
},
});
})();