// 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 []; }, }, comments: Object, projectName: String, showReplyButtons: { type: Boolean, value: false, }, _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 []; }, }, }, 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); }, _handleAutomatedMessageToggleTap(e) { e.preventDefault(); this._hideAutomated = !this._hideAutomated; }, _handleScrollTo(e) { this.scrollToMessage(e.detail.message.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'; }, _computeAutomatedToggleText(hideAutomated) { return hideAutomated ? 'Show all messages' : 'Show comments only'; }, /** * 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} comments Hash of arrays of comments, filename as key. * @param {!Object} message * @return {!Object} Hash of arrays of comments, filename as key. */ _computeCommentsForMessage(comments, message) { 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); }, }); })();