/** * @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; Polymer({ is: '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 */ hostAttributes: { tabindex: 0, }, properties: { /** * 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<{ * sectionName: string, * query: string, * results: !Array * }>} */ sections: { type: Array, value() { return []; }, }, labelNames: { type: Array, computed: '_computeLabelNames(sections)', }, 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, }, behaviors: [ Gerrit.BaseUrlBehavior, Gerrit.ChangeTableBehavior, Gerrit.KeyboardShortcutBehavior, Gerrit.RESTClientBehavior, Gerrit.URLEncodingBehavior, ], keyBindings: { 'j': '_handleJKey', 'k': '_handleKKey', 'n ]': '_handleNKey', 'o': '_handleOKey', 'p [': '_handlePKey', 'r': '_handleRKey', 'shift+r': '_handleShiftRKey', 's': '_handleSKey', }, listeners: { keydown: '_scopedKeydownHandler', }, observers: [ '_sectionsChanged(sections.*)', '_computePreferences(account, preferences)', ], /** * 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._handleOKey(e); } }, _lowerCase(column) { return column.toLowerCase(); }, _computePreferences(account, preferences) { this.changeTableColumns = this.columnNames; if (account) { this.showNumber = !!(preferences && preferences.legacycid_in_change_table); this.visibleChangeTableColumns = preferences.change_table.length > 0 ? preferences.change_table : this.columnNames; } else { // Not logged in. this.showNumber = false; this.visibleChangeTableColumns = this.columnNames; } }, _computeColspan(changeTableColumns, labelNames) { 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}] : []; }, _sectionHref(query) { return Gerrit.Nav.getUrlForSearchQuery(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 && this.changeIsOpen(change.status) && (!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.assignee || !account || CLOSED_STATUS.indexOf(change.status) !== -1) { return false; } return account._account_id === change.assignee._account_id; }, _handleJKey(e) { if (this.shouldSuppressKeyboardShortcut(e) || this.modifierPressed(e)) { return; } e.preventDefault(); this.$.cursor.next(); }, _handleKKey(e) { if (this.shouldSuppressKeyboardShortcut(e) || this.modifierPressed(e)) { return; } e.preventDefault(); this.$.cursor.previous(); }, _handleOKey(e) { if (this.shouldSuppressKeyboardShortcut(e) || this.modifierPressed(e)) { return; } e.preventDefault(); Gerrit.Nav.navigateToChange(this._changeForIndex(this.selectedIndex)); }, _handleNKey(e) { if (this.shouldSuppressKeyboardShortcut(e) || this.modifierPressed(e) && !this.isModifierPressed(e, 'shiftKey')) { return; } e.preventDefault(); this.fire('next-page'); }, _handlePKey(e) { if (this.shouldSuppressKeyboardShortcut(e) || this.modifierPressed(e) && !this.isModifierPressed(e, 'shiftKey')) { return; } e.preventDefault(); this.fire('previous-page'); }, _handleRKey(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(); }, _handleShiftRKey(e) { if (this.shouldSuppressKeyboardShortcut(e)) { return; } e.preventDefault(); this._reloadWindow(); }, _reloadWindow() { window.location.reload(); }, _handleSKey(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 Polymer.dom(this.root).querySelectorAll('gr-change-list-item'); }, _sectionsChanged() { // Flush DOM operations so that the list item elements will be loaded. Polymer.dom.flush(); this.$.cursor.stops = this._getListItems(); this.$.cursor.moveToStart(); }, }); })();