Files
gerrit/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js
Kasper Nilsson 9c1a3db8e4 Handle message timestamp taps with Nav API
Very similar to Ib045b8f. Removes more references to page.show, adds
'messageHash' field to getUrlForChange, and utilizes it in the change
view.

Change-Id: I496d731ceeba57843d617fd5b978cd377ea39787
2018-10-26 10:18:40 -07:00

364 lines
11 KiB
JavaScript

/**
* @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 MAX_INITIAL_SHOWN_MESSAGES = 20;
const MESSAGES_INCREMENT = 5;
const ReportingEvent = {
SHOW_ALL: 'show-all-messages',
SHOW_MORE: 'show-more-messages',
};
Polymer({
is: 'gr-messages-list',
properties: {
changeNum: Number,
messages: {
type: Array,
value() { return []; },
},
reviewerUpdates: {
type: Array,
value() { return []; },
},
changeComments: Object,
projectName: String,
showReplyButtons: {
type: Boolean,
value: false,
},
labels: Object,
_expanded: {
type: Boolean,
value: false,
observer: '_expandedChanged',
},
_hideAutomated: {
type: Boolean,
value: false,
},
/**
* The messages after processing and including merged reviewer updates.
*/
_processedMessages: {
type: Array,
computed: '_computeItems(messages, reviewerUpdates)',
observer: '_processedMessagesChanged',
},
/**
* The subset of _processedMessages that is visible to the user.
*/
_visibleMessages: {
type: Array,
value() { return []; },
},
_labelExtremes: {
type: Object,
computed: '_computeLabelExtremes(labels.*)',
},
},
scrollToMessage(messageID) {
let el = this.$$('[data-message-id="' + messageID + '"]');
// If the message is hidden, expand the hidden messages back to that
// point.
if (!el) {
let index;
for (index = 0; index < this._processedMessages.length; index++) {
if (this._processedMessages[index].id === messageID) {
break;
}
}
if (index === this._processedMessages.length) { return; }
const newMessages = this._processedMessages.slice(index,
-this._visibleMessages.length);
// Add newMessages to the beginning of _visibleMessages.
this.splice(...['_visibleMessages', 0, 0].concat(newMessages));
// Allow the dom-repeat to stamp.
Polymer.dom.flush();
el = this.$$('[data-message-id="' + messageID + '"]');
}
el.set('message.expanded', true);
let top = el.offsetTop;
for (let offsetParent = el.offsetParent;
offsetParent;
offsetParent = offsetParent.offsetParent) {
top += offsetParent.offsetTop;
}
window.scrollTo(0, top);
this._highlightEl(el);
},
_isAutomated(message) {
return !!(message.reviewer ||
(message.tag && message.tag.startsWith('autogenerated')));
},
_computeItems(messages, reviewerUpdates) {
messages = messages || [];
reviewerUpdates = reviewerUpdates || [];
let mi = 0;
let ri = 0;
let result = [];
let mDate;
let rDate;
for (let i = 0; i < messages.length; i++) {
messages[i]._index = i;
}
while (mi < messages.length || ri < reviewerUpdates.length) {
if (mi >= messages.length) {
result = result.concat(reviewerUpdates.slice(ri));
break;
}
if (ri >= reviewerUpdates.length) {
result = result.concat(messages.slice(mi));
break;
}
mDate = mDate || util.parseDate(messages[mi].date);
rDate = rDate || util.parseDate(reviewerUpdates[ri].date);
if (rDate < mDate) {
result.push(reviewerUpdates[ri++]);
rDate = null;
} else {
result.push(messages[mi++]);
mDate = null;
}
}
return result;
},
_expandedChanged(exp) {
for (let i = 0; i < this._processedMessages.length; i++) {
this._processedMessages[i].expanded = exp;
if (i < this._visibleMessages.length) {
this.set(['_visibleMessages', i, 'expanded'], exp);
}
}
},
_highlightEl(el) {
const highlightedEls =
Polymer.dom(this.root).querySelectorAll('.highlighted');
for (const highlighedEl of highlightedEls) {
highlighedEl.classList.remove('highlighted');
}
function handleAnimationEnd() {
el.removeEventListener('animationend', handleAnimationEnd);
el.classList.remove('highlighted');
}
el.addEventListener('animationend', handleAnimationEnd);
el.classList.add('highlighted');
},
/**
* @param {boolean} expand
*/
handleExpandCollapse(expand) {
this._expanded = expand;
},
_handleExpandCollapseTap(e) {
e.preventDefault();
this.handleExpandCollapse(!this._expanded);
},
_handleAnchorTap(e) {
this.scrollToMessage(e.detail.id);
},
_hasAutomatedMessages(messages) {
if (!messages) { return false; }
for (const message of messages) {
if (this._isAutomated(message)) {
return true;
}
}
return false;
},
_computeExpandCollapseMessage(expanded) {
return expanded ? 'Collapse all' : 'Expand all';
},
/**
* Computes message author's file comments for change's message.
* Method uses this.messages to find next message and relies on messages
* to be sorted by date field descending.
* @param {!Object} changeComments changeComment object, which includes
* a method to get all published comments (including robot comments),
* which returns a Hash of arrays of comments, filename as key.
* @param {!Object} message
* @return {!Object} Hash of arrays of comments, filename as key.
*/
_computeCommentsForMessage(changeComments, message) {
const comments = changeComments.getAllPublishedComments();
if (message._index === undefined || !comments || !this.messages) {
return [];
}
const messages = this.messages || [];
const index = message._index;
const authorId = message.author && message.author._account_id;
const mDate = util.parseDate(message.date).getTime();
// NB: Messages array has oldest messages first.
let nextMDate;
if (index > 0) {
for (let i = index - 1; i >= 0; i--) {
if (messages[i] && messages[i].author &&
messages[i].author._account_id === authorId) {
nextMDate = util.parseDate(messages[i].date).getTime();
break;
}
}
}
const msgComments = {};
for (const file in comments) {
if (!comments.hasOwnProperty(file)) { continue; }
const fileComments = comments[file];
for (let i = 0; i < fileComments.length; i++) {
if (fileComments[i].author &&
fileComments[i].author._account_id !== authorId) {
continue;
}
const cDate = util.parseDate(fileComments[i].updated).getTime();
if (cDate <= mDate) {
if (nextMDate && cDate <= nextMDate) {
continue;
}
msgComments[file] = msgComments[file] || [];
msgComments[file].push(fileComments[i]);
}
}
}
return msgComments;
},
/**
* Returns the number of messages to splice to the beginning of
* _visibleMessages. This is the minimum of the total number of messages
* remaining in the list and the number of messages needed to display five
* more visible messages in the list.
*/
_getDelta(visibleMessages, messages, hideAutomated) {
let delta = MESSAGES_INCREMENT;
const msgsRemaining = messages.length - visibleMessages.length;
if (hideAutomated) {
let counter = 0;
let i;
for (i = msgsRemaining; i > 0 && counter < MESSAGES_INCREMENT; i--) {
if (!this._isAutomated(messages[i - 1])) { counter++; }
}
delta = msgsRemaining - i;
}
return Math.min(msgsRemaining, delta);
},
/**
* Gets the number of messages that would be visible, but do not currently
* exist in _visibleMessages.
*/
_numRemaining(visibleMessages, messages, hideAutomated) {
if (hideAutomated) {
return this._getHumanMessages(messages).length -
this._getHumanMessages(visibleMessages).length;
}
return messages.length - visibleMessages.length;
},
_computeIncrementText(visibleMessages, messages, hideAutomated) {
let delta = this._getDelta(visibleMessages, messages, hideAutomated);
delta = Math.min(
this._numRemaining(visibleMessages, messages, hideAutomated), delta);
return 'Show ' + Math.min(MESSAGES_INCREMENT, delta) + ' more';
},
_getHumanMessages(messages) {
return messages.filter(msg => {
return !this._isAutomated(msg);
});
},
_computeShowHideTextHidden(visibleMessages, messages,
hideAutomated) {
if (hideAutomated) {
messages = this._getHumanMessages(messages);
visibleMessages = this._getHumanMessages(visibleMessages);
}
return visibleMessages.length >= messages.length;
},
_handleShowAllTap() {
this._visibleMessages = this._processedMessages;
this.$.reporting.reportInteraction(ReportingEvent.SHOW_ALL);
},
_handleIncrementShownMessages() {
const delta = this._getDelta(this._visibleMessages,
this._processedMessages, this._hideAutomated);
const len = this._visibleMessages.length;
const newMessages = this._processedMessages.slice(-(len + delta), -len);
// Add newMessages to the beginning of _visibleMessages
this.splice(...['_visibleMessages', 0, 0].concat(newMessages));
this.$.reporting.reportInteraction(ReportingEvent.SHOW_MORE);
},
_processedMessagesChanged(messages) {
this._visibleMessages = messages.slice(-MAX_INITIAL_SHOWN_MESSAGES);
},
_computeNumMessagesText(visibleMessages, messages,
hideAutomated) {
const total =
this._numRemaining(visibleMessages, messages, hideAutomated);
return total === 1 ? 'Show 1 message' : 'Show all ' + total + ' messages';
},
_computeIncrementHidden(visibleMessages, messages,
hideAutomated) {
const total =
this._numRemaining(visibleMessages, messages, hideAutomated);
return total <= this._getDelta(visibleMessages, messages, hideAutomated);
},
/**
* Compute a mapping from label name to objects representing the minimum and
* maximum possible values for that label.
*/
_computeLabelExtremes(labelRecord) {
const extremes = {};
const labels = labelRecord.base;
if (!labels) { return extremes; }
for (const key of Object.keys(labels)) {
if (!labels[key] || !labels[key].values) { continue; }
const values = Object.keys(labels[key].values)
.map(v => parseInt(v, 10));
values.sort();
if (!values.length) { continue; }
extremes[key] = {min: values[0], max: values[values.length - 1]};
}
return extremes;
},
});
})();