/** * @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 NUMBER_FIXED_COLUMNS = 3; const CLOSED_STATUS = ['MERGED', 'ABANDONED']; const LABEL_PREFIX_INVALID_PROLOG = 'Invalid-Prolog-Rules-Label-Name--'; const MAX_SHORTCUT_CHARS = 5; /** * @appliesMixin Gerrit.BaseUrlMixin * @appliesMixin Gerrit.ChangeTableMixin * @appliesMixin Gerrit.FireMixin * @appliesMixin Gerrit.KeyboardShortcutMixin * @appliesMixin Gerrit.RESTClientMixin * @appliesMixin Gerrit.URLEncodingMixin */ class GrChangeList extends Polymer.mixinBehaviors( [ Gerrit.BaseUrlBehavior, Gerrit.ChangeTableBehavior, Gerrit.FireBehavior, Gerrit.KeyboardShortcutBehavior, Gerrit.RESTClientBehavior, Gerrit.URLEncodingBehavior, ], Polymer.GestureEventListeners( Polymer.LegacyElementMixin( Polymer.Element))) { static get is() { return 'gr-change-list'; } /** * Fired when next page key shortcut was pressed. * * @event next-page */ /** * Fired when previous page key shortcut was pressed. * * @event previous-page */ static get properties() { return { /** * The logged-in user's account, or an empty object if no user is logged * in. */ account: { type: Object, value: null, }, /** * An array of ChangeInfo objects to render. * https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#change-info */ changes: { type: Array, observer: '_changesChanged', }, /** * ChangeInfo objects grouped into arrays. The sections and changes * properties should not be used together. * * @type {!Array<{ * name: string, * query: string, * results: !Array * }>} */ sections: { type: Array, value() { return []; }, }, labelNames: { type: Array, computed: '_computeLabelNames(sections)', }, _dynamicHeaderEndpoints: { type: Array, }, selectedIndex: { type: Number, notify: true, }, showNumber: Boolean, // No default value to prevent flickering. showStar: { type: Boolean, value: false, }, showReviewedState: { type: Boolean, value: false, }, keyEventTarget: { type: Object, value() { return document.body; }, }, changeTableColumns: Array, visibleChangeTableColumns: Array, preferences: Object, }; } static get observers() { return [ '_sectionsChanged(sections.*)', '_computePreferences(account, preferences)', ]; } keyboardShortcuts() { return { [this.Shortcut.CURSOR_NEXT_CHANGE]: '_nextChange', [this.Shortcut.CURSOR_PREV_CHANGE]: '_prevChange', [this.Shortcut.NEXT_PAGE]: '_nextPage', [this.Shortcut.PREV_PAGE]: '_prevPage', [this.Shortcut.OPEN_CHANGE]: '_openChange', [this.Shortcut.TOGGLE_CHANGE_REVIEWED]: '_toggleChangeReviewed', [this.Shortcut.TOGGLE_CHANGE_STAR]: '_toggleChangeStar', [this.Shortcut.REFRESH_CHANGE_LIST]: '_refreshChangeList', }; } created() { super.created(); this.addEventListener('keydown', e => this._scopedKeydownHandler(e)); } ready() { super.ready(); this._ensureAttribute('tabindex', 0); } attached() { super.attached(); Gerrit.awaitPluginsLoaded().then(() => { this._dynamicHeaderEndpoints = Gerrit._endpoints.getDynamicEndpoints( 'change-list-header'); }); } /** * Iron-a11y-keys-behavior catches keyboard events globally. Some keyboard * events must be scoped to a component level (e.g. `enter`) in order to not * override native browser functionality. * * Context: Issue 7294 */ _scopedKeydownHandler(e) { if (e.keyCode === 13) { // Enter. this._openChange(e); } } _lowerCase(column) { return column.toLowerCase(); } _computePreferences(account, preferences) { // Polymer 2: check for undefined if ([account, preferences].some(arg => arg === undefined)) { return; } this.changeTableColumns = this.columnNames; if (account) { this.showNumber = !!(preferences && preferences.legacycid_in_change_table); this.visibleChangeTableColumns = preferences.change_table.length > 0 ? this.getVisibleColumns(preferences.change_table) : this.columnNames; } else { // Not logged in. this.showNumber = false; this.visibleChangeTableColumns = this.columnNames; } } _computeColspan(changeTableColumns, labelNames) { if (!changeTableColumns || !labelNames) return; return changeTableColumns.length + labelNames.length + NUMBER_FIXED_COLUMNS; } _computeLabelNames(sections) { if (!sections) { return []; } let labels = []; const nonExistingLabel = function(item) { return !labels.includes(item); }; for (const section of sections) { if (!section.results) { continue; } for (const change of section.results) { if (!change.labels) { continue; } const currentLabels = Object.keys(change.labels); labels = labels.concat(currentLabels.filter(nonExistingLabel)); } } return labels.sort(); } _computeLabelShortcut(labelName) { if (labelName.startsWith(LABEL_PREFIX_INVALID_PROLOG)) { labelName = labelName.slice(LABEL_PREFIX_INVALID_PROLOG.length); } return labelName.split('-') .reduce((a, i) => { if (!i) { return a; } return a + i[0].toUpperCase(); }, '') .slice(0, MAX_SHORTCUT_CHARS); } _changesChanged(changes) { this.sections = changes ? [{results: changes}] : []; } _processQuery(query) { let tokens = query.split(' '); const invalidTokens = ['limit:', 'age:', '-age:']; tokens = tokens.filter(token => { return !invalidTokens.some(invalidToken => { return token.startsWith(invalidToken); }); }); return tokens.join(' '); } _sectionHref(query) { return Gerrit.Nav.getUrlForSearchQuery(this._processQuery(query)); } /** * Maps an index local to a particular section to the absolute index * across all the changes on the page. * * @param {number} sectionIndex index of section * @param {number} localIndex index of row within section * @return {number} absolute index of row in the aggregate dashboard */ _computeItemAbsoluteIndex(sectionIndex, localIndex) { let idx = 0; for (let i = 0; i < sectionIndex; i++) { idx += this.sections[i].results.length; } return idx + localIndex; } _computeItemSelected(sectionIndex, index, selectedIndex) { const idx = this._computeItemAbsoluteIndex(sectionIndex, index); return idx == selectedIndex; } _computeItemNeedsReview(account, change, showReviewedState) { return showReviewedState && !change.reviewed && !change.work_in_progress && this.changeIsOpen(change) && (!account || account._account_id != change.owner._account_id); } _computeItemHighlight(account, change) { // Do not show the assignee highlight if the change is not open. if (!change ||!change.assignee || !account || CLOSED_STATUS.indexOf(change.status) !== -1) { return false; } return account._account_id === change.assignee._account_id; } _nextChange(e) { if (this.shouldSuppressKeyboardShortcut(e) || this.modifierPressed(e)) { return; } e.preventDefault(); this.$.cursor.next(); } _prevChange(e) { if (this.shouldSuppressKeyboardShortcut(e) || this.modifierPressed(e)) { return; } e.preventDefault(); this.$.cursor.previous(); } _openChange(e) { if (this.shouldSuppressKeyboardShortcut(e) || this.modifierPressed(e)) { return; } e.preventDefault(); Gerrit.Nav.navigateToChange(this._changeForIndex(this.selectedIndex)); } _nextPage(e) { if (this.shouldSuppressKeyboardShortcut(e) || this.modifierPressed(e) && !this.isModifierPressed(e, 'shiftKey')) { return; } e.preventDefault(); this.fire('next-page'); } _prevPage(e) { if (this.shouldSuppressKeyboardShortcut(e) || this.modifierPressed(e) && !this.isModifierPressed(e, 'shiftKey')) { return; } e.preventDefault(); this.fire('previous-page'); } _toggleChangeReviewed(e) { if (this.shouldSuppressKeyboardShortcut(e) || this.modifierPressed(e)) { return; } e.preventDefault(); this._toggleReviewedForIndex(this.selectedIndex); } _toggleReviewedForIndex(index) { const changeEls = this._getListItems(); if (index >= changeEls.length || !changeEls[index]) { return; } const changeEl = changeEls[index]; changeEl.toggleReviewed(); } _refreshChangeList(e) { if (this.shouldSuppressKeyboardShortcut(e)) { return; } e.preventDefault(); this._reloadWindow(); } _reloadWindow() { window.location.reload(); } _toggleChangeStar(e) { if (this.shouldSuppressKeyboardShortcut(e) || this.modifierPressed(e)) { return; } e.preventDefault(); this._toggleStarForIndex(this.selectedIndex); } _toggleStarForIndex(index) { const changeEls = this._getListItems(); if (index >= changeEls.length || !changeEls[index]) { return; } const changeEl = changeEls[index]; changeEl.$$('gr-change-star').toggleStar(); } _changeForIndex(index) { const changeEls = this._getListItems(); if (index < changeEls.length && changeEls[index]) { return changeEls[index].change; } return null; } _getListItems() { return Array.from( Polymer.dom(this.root).querySelectorAll('gr-change-list-item')); } _sectionsChanged() { // Flush DOM operations so that the list item elements will be loaded. Polymer.RenderStatus.afterNextRender(this, () => { this.$.cursor.stops = this._getListItems(); this.$.cursor.moveToStart(); }); } _isOutgoing(section) { return !!section.isOutgoing; } _isEmpty(section) { return !section.results.length; } } customElements.define(GrChangeList.is, GrChangeList); })();