`$$` is deprecated: https://polymer-library.polymer-project.org/2.0/docs/upgrade One exception here is for gr-select as its not a shadow dom component, so use `this.querySelector` instead Change-Id: Ib3fd45298da1b44325d47932e53e6185d25c13de
		
			
				
	
	
		
			430 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			430 lines
		
	
	
		
			14 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',
 | 
						|
  };
 | 
						|
 | 
						|
  /**
 | 
						|
   * @appliesMixin Gerrit.KeyboardShortcutMixin
 | 
						|
   * @extends Polymer.Element
 | 
						|
   */
 | 
						|
  class GrMessagesList extends Polymer.mixinBehaviors( [
 | 
						|
    Gerrit.KeyboardShortcutBehavior,
 | 
						|
  ], Polymer.GestureEventListeners(
 | 
						|
      Polymer.LegacyElementMixin(
 | 
						|
          Polymer.Element))) {
 | 
						|
    static get is() { return 'gr-messages-list'; }
 | 
						|
 | 
						|
    static get properties() {
 | 
						|
      return {
 | 
						|
        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',
 | 
						|
        },
 | 
						|
 | 
						|
        _expandCollapseTitle: {
 | 
						|
          type: String,
 | 
						|
        },
 | 
						|
 | 
						|
        _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.shadowRoot
 | 
						|
          .querySelector('[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.shadowRoot
 | 
						|
            .querySelector('[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) {
 | 
						|
      // Polymer 2: check for undefined
 | 
						|
      if ([messages, reviewerUpdates].some(arg => arg === undefined)) {
 | 
						|
        return [];
 | 
						|
      }
 | 
						|
 | 
						|
      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) {
 | 
						|
      if (this._processedMessages) {
 | 
						|
        for (let i = 0; i < this._processedMessages.length; i++) {
 | 
						|
          this._processedMessages[i].expanded = exp;
 | 
						|
        }
 | 
						|
      }
 | 
						|
      // _visibleMessages is a subarray of _processedMessages
 | 
						|
      // _processedMessages contains all items from _visibleMessages
 | 
						|
      // At this point all _visibleMessages.expanded values are set,
 | 
						|
      // and notifyPath must be used to notify Polymer about changes.
 | 
						|
      if (this._visibleMessages) {
 | 
						|
        for (let i = 0; i < this._visibleMessages.length; i++) {
 | 
						|
          this.notifyPath(`_visibleMessages.${i}.expanded`);
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      if (this._expanded) {
 | 
						|
        this._expandCollapseTitle = this.createTitle(
 | 
						|
            this.Shortcut.COLLAPSE_ALL_MESSAGES, this.ShortcutSection.ACTIONS);
 | 
						|
      } else {
 | 
						|
        this._expandCollapseTitle = this.createTitle(
 | 
						|
            this.Shortcut.EXPAND_ALL_MESSAGES, this.ShortcutSection.ACTIONS);
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    _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);
 | 
						|
    }
 | 
						|
 | 
						|
    _handleAnchorClick(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) {
 | 
						|
      if ([changeComments, message].some(arg => arg === undefined)) {
 | 
						|
        return [];
 | 
						|
      }
 | 
						|
      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) {
 | 
						|
      if ([visibleMessages, messages].some(arg => arg === undefined)) {
 | 
						|
        return 0;
 | 
						|
      }
 | 
						|
 | 
						|
      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 ([visibleMessages, messages].some(arg => arg === undefined)) {
 | 
						|
        return 0;
 | 
						|
      }
 | 
						|
 | 
						|
      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 => !this._isAutomated(msg));
 | 
						|
    }
 | 
						|
 | 
						|
    _computeShowHideTextHidden(visibleMessages, messages,
 | 
						|
        hideAutomated) {
 | 
						|
      if ([visibleMessages, messages].some(arg => arg === undefined)) {
 | 
						|
        return 0;
 | 
						|
      }
 | 
						|
 | 
						|
      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) {
 | 
						|
      if (messages) {
 | 
						|
        this._visibleMessages = messages.slice(-MAX_INITIAL_SHOWN_MESSAGES);
 | 
						|
 | 
						|
        if (messages.length === 0) return;
 | 
						|
        const tags = messages.map(message => message.tag || message.type ||
 | 
						|
            (message.comments ? 'comments' : 'none'));
 | 
						|
        const tagsCounted = tags.reduce((acc, val) => {
 | 
						|
          acc[val] = (acc[val] || 0) + 1;
 | 
						|
          return acc;
 | 
						|
        }, {all: messages.length});
 | 
						|
        this.$.reporting.reportInteraction('messages-count', tagsCounted);
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    _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((a, b) => a - b);
 | 
						|
        if (!values.length) { continue; }
 | 
						|
        extremes[key] = {min: values[0], max: values[values.length - 1]};
 | 
						|
      }
 | 
						|
      return extremes;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  customElements.define(GrMessagesList.is, GrMessagesList);
 | 
						|
})();
 |